Insurgently

API reference

Every declaration in Model Hub's API for other plugins, generated from the code.

For people building on Model Hub.

For tools and coding agentsmodel-hub-api.d.tsapi.json

HubApi

The Hub, as getHub returns it. Every call another plugin makes goes through this.

version

readonly version: string

Semantic version of this API. Check it with isCompatible (or use getHub).

registerRoles

registerRoles(pluginId: string, roles: RoleDefinition[]): void

Declares the roles your plugin uses. Each role names a tier (default balanced) and/or its own ordered candidates, with an optional per-call ceiling and monthly budget. The first plugin to use a namespace owns it. A custom tier is named by id or by name: store the id createTier returns; names keep working across renames via aliases (a renamed tier keeps its old names, and its id reads as the name it was made with). Harmless to repeat, for example on model-hub:ready (see onHubReady).

roles

roles(pluginId: string): ResolvedRole[]

Your plugin's roles, with the tier, candidates and limits each resolves to now. tier is the id of the tier a role runs on. A role may name a tier by id or by name (in registerRoles or setRole): store the id createTier returns; names keep working across renames via aliases. When the tier it names doesn't exist (not made yet, or deleted), it runs on balanced and comes back with tierMissing: true and missingTier holding the name as given. (In earlier versions, an unknown tier became balanced unflagged.)

setRole

setRole(pluginId: string, role: string, override: RoleOverride | null): void

Changes one of your roles (a tier, own candidates, limits); null puts it back to your default. Only the owning plugin may.

openRoleEditor

openRoleEditor(pluginId: string): void

Opens the Hub's role editor for your plugin's roles: one consistent screen, no UI to write.

tiers

tiers(): Tier[]

Every tier as the user has set them (candidates, ceiling, monthly budget): the seven built-in tiers first, then the user's own in the order they were made. Each has a stable id (a built-in tier's is its name; a custom tier's never changes, even when the user renames it), a display name, and custom.

createTier

createTier(pluginId: string, name: string, policy: ModelPolicy): Promise<Tier | null>

Asks the user to add a tier of their own: the Hub opens a dialog naming your plugin (the id is as you declare it; the dialog shows the name from that plugin's manifest, or "(unknown plugin)" when no installed plugin has that id) and showing the tier's models and limits, which the user may change before confirming. Resolves to the tier made (its name may be the user's), or null if the user declines. Rejects with a HubError for a name that is taken, empty, over 40 characters or a built-in tier's (invalid-tier-name), or a malformed policy (invalid-request).

deleteTier

deleteTier(pluginId: string, id: string): Promise<boolean>

Asks the user to delete a tier of their own (by id, or name): the Hub's dialog names your plugin as createTier's does (the id is self-declared) and lists the roles mapped to it, and each the user moved there goes back to its plugin's default. Resolves true once deleted, false if the user declines. Rejects for an unknown tier (unknown-tier) or a built-in one (invalid-request).

complete

complete(role: string, messages: HubMessage[], opts?: CompleteOptions): Promise<CompleteResult>

One call through a role. Rejects with an error whose code is a HubErrorCode.

stream

stream(role: string, messages: HubMessage[], opts?: StreamOptions): StreamResult

The same, streamed.

decide

decide(role: string, request: DecisionRequest, opts?: DecideOptions): Promise<DecideResult>

Typed decisions about a state: yes/no (noul), pick one (choice), rate on levels (score). Through a role on the decision tier; calibrated says whether the probabilities come from a decision model (Jev).

batch

batch<T, R extends {
    billedUsd?: number;
    listedUsd: number | null;
    overcharged: boolean;
}>(items: T[], fn: (item: T, signal: AbortSignal) => Promise<R>, opts?: BatchOptions): Promise<BatchResult<R>>

Runs many calls with limited concurrency, reporting progress and cost, and stops early when a call is overcharged, a spend limit is reached or the signal aborts. Pass the given signal to each Hub call.

embed

embed(role: string, values: string[], opts?: EmbedOptions): Promise<EmbedResult>

Embeddings for a batch of texts. Pass spec to keep an index on its own model, and expectFingerprint to check before paying.

describeEmbedding

describeEmbedding(spec: EmbeddingSpec): Promise<EmbeddingDescription>

What an embedding spec would give, without a call or a cost: its fingerprint, the prefixes it adds, its price per million tokens, and whether it can run now (available, with a code and a sentence when it can't). Use it to check an index's model at load, and to price a re-embed.

estimate

estimate(role: string, req: EstimateRequest): Promise<CostEstimate>

What a call (or an agent run of steps steps) through one of your roles would cost, without making it: no request, no ledger row. Resolves { ok: true, low, high, usd, … } from the first model that could serve it, or { ok: false, reason } with the code the call itself would get (no-models, unpriced-model, over-ceiling, …). Put high in an automation plan's cost.

models

models(filter?: {
    provider?: string;
    needs?: Capability[];
}): ModelInfo[]

Models the Hub knows, filtered by provider or by what they can do (for pickers).

skills

skills(): SkillSummary[]

Skills the Hub can apply (name them in skills on a call): the Hub's own, those plugins registered, and the vault's. Vault skills arrive at layout-ready: await skillsReady() before relying on them.

registerSkills

registerSkills(pluginId: string, skills: SkillRegistration[]): void

Registers your plugin's built-in skills, each as its folder's text files: { files: { 'SKILL.md': …, 'reader.yml': …, 'prompts/extract.md': … } }. Replaces what your plugin registered before ([] removes them). They stay through every reload of the vault skills, and are removed when your plugin unloads. A vault skill of the same name overrides one; a name belongs to the first plugin to register it. Throws a HubError: skill-taken (another plugin's, or the Hub's own), invalid-skill (no or bad SKILL.md, a path outside the folder, a name given twice, mode: always: a plugin's skill is used only when a call names it; the user can make a vault copy always-on), invalid-request (no plugin id).

Registrations live in the Hub that is loaded: when the user disables and enables Model Hub, the new Hub starts without them and fires model-hub:ready on the workspace. Register again then: onHubReady (below getHub) does both, or this.registerEvent(this.app.workspace.on('model-hub:ready', (api) => …)).

skillsReady

skillsReady(): Promise<void>

Resolves once the vault's skills have loaded (at layout-ready); registered skills are there as soon as registered.

onSkillsChanged

onSkillsChanged(cb: () => void): () => void

Calls cb whenever the skills change: a plugin registers or unregisters, or the skills folder is edited (files included) or reloaded. Returns the function that stops it; pass it to this.register().

skillsFolder

skillsFolder(): string | null

The vault folder the user's own skills load from (Settings → Model Hub → Skills), with no leading or trailing slash, or null when the user has set none. A plugin that writes a skill for the user to edit puts it there; the Hub loads it on its own. Read it when you write, not once at load: the user can change it. A Hub older than 0.11.1 has no such method, so check typeof hub.skillsFolder === 'function'.

usage

usage(filter?: LedgerFilter): Promise<LedgerRow[]>

Ledger rows: every call, with tokens and cost.

spend

spend(): Promise<SpendSummary>

What has been spent and what is left: each tier's metered spend this calendar month (UTC) against its monthly budget, and a report per provider that is on. A subscription reports its last 5 hours and 7 days (calls and the API-price equivalent), a local provider its tokens today and this month, OpenRouter its key's limit (asked of openrouter.ai at most every 5 minutes) and this month's spend, any other provider this month's spend through the Hub. A figure that couldn't be had is listed in unavailable with why, never guessed.

registerTools

registerTools(pluginId: string, tools: HubTool[], opts?: {
    undo?: (handle: string) => Promise<string>;
}): void

Registers your plugin's tools: { name: 'books.list_shelves', description, input, effect, handler }. Names are namespace.name in lowercase; the first plugin to register a namespace owns it, across restarts. Replaces what your plugin registered before, and the tools go when your plugin unloads. opts.undo takes the handle a write tool returned and puts the change back, reporting what it did in plain words. Throws a HubError: tool-name-taken (another plugin's namespace), invalid-request (a bad or repeated name). The user sets which tools run freely, which ask and which are off.

unregisterTools

unregisterTools(pluginId: string): void

Removes every tool your plugin registered. Namespace claims are kept.

tools

tools(): ToolInfo[]

The registered tools with their owner and the policy now in force.

callTool

callTool(pluginId: string, name: string, input: Record<string, unknown>, opts?: {
    run?: string;
    signal?: AbortSignal;
}): Promise<ToolCallResult>

Calls a tool by name. With no opts.run, the call is its own short run for your plugin and ends when it does; pass the id from startRun to share a budget, grants and an undo list across calls. pluginId names the caller for the approval dialog and the log; it grants nothing. A run belongs to the plugin that started it: another caller's run id is refused (invalid-request). Goes through the user's policy (a tool set to ask shows a dialog) and is written to the tool-call log. Rejects with a HubError: tool-unknown, tool-invalid-input, tool-denied, tool-failed, tool-timeout, aborted, run-budget.

startRun

startRun(pluginId: string, opts?: {
    budgetUsd?: number;
    tools?: string[];
    label?: string;
}): RunInfo

Opens a run for your plugin: a budget (budgetUsd, none by default) and, if tools is given, the only tools it may call. Pass its id to callTool and to model calls; end it with endRun. pluginId names the caller for the approval dialog and the log; it grants nothing. The run belongs to your plugin: only calls made as pluginId may use it, including model calls for your roles. budgetUsd must be 0 or more. The returned RunInfo is the run as it starts; call run to read its current spend and undo handles. label names what the run is for, such as index Garden notes, in at most 80 characters; it goes out in the run's trace span (run plugin:<id>: <label>, attribute hub.run.label) when traces are on, so put no note text in it that the user wouldn't send.

run

run(pluginId: string, runId: string): RunInfo | undefined

The run as it is now: what it has spent and the undo handles of its writes. Undefined if it has ended, never existed or belongs to another plugin.

endRun

endRun(pluginId: string, runId: string): void

Ends a run and stops its calls in flight. Ending an unknown or ended run does nothing; ending another plugin's run is refused (invalid-request).

agent

agent(role: string, opts: AgentRunOptions): AgentRun

Runs a tool loop for one of your roles: streams a step, runs the tool calls the model asks for through the user's policy, and goes on until the model answers, a finish tool succeeds, maxSteps (8) runs out, the budget is spent or stop() is called. tools names registered tools; finishTools is the subset that ends the run when it returns without an error. Cost is credited to the plugin that owns the role, and tool calls are made as plugin:<owner>. Pass run to work in a run you opened with startRun (the loop then leaves it open); otherwise the loop opens one with budgetUsd and ends it. A run is lost if the Hub reloads. Throws a HubError (invalid-request) at once, with no run started, when run belongs to another caller, a tool is unknown, panel-only or not offered, a finish tool isn't in tools, maxSteps is not a whole number of 1 or more, or this Hub has no tool registry. Every other end, including errors, resolves result.

undo

undo(owner: string, handle: string): Promise<string>

Puts back a write using its owner's undo and the handle the write returned. Resolves to what it put back.

toolCalls

toolCalls(filter?: ToolLogFilter): Promise<ToolCallRow[]>

Tool-call log rows: every call, whether it ran, was refused or failed. Arguments are kept only as a hash.

onRolesChanged

onRolesChanged(cb: () => void): () => void

Calls cb when what a role resolves to may have changed: the user moved a role to another tier or gave it models, a tier's models or limits changed, providers were switched on or off, or a plugin's roles came or went. Read roles() again in it. Several changes at once call it once. Returns the function that stops it; pass it to this.register().

onToolsChanged

onToolsChanged(cb: () => void): () => void

Calls cb when tools are registered or removed. Returns the function that stops it; pass it to this.register().

previewEdits

previewEdits(pluginId: string, files: {
    path: string;
    ops: EditOp[];
}[], opts?: {
    allowFuzzy?: boolean;
    riskChecks?: RiskCheck[];
}): Promise<EditPreview>

Places edit ops in one or more notes and shows what would change, writing nothing. Each op is found by the matching ladder (exact, then normalized quotes, dashes and spaces, then a strict fuzzy match for long ops, each placed op saying which), and carries risk flags (numbers, dates, links, negations, plus any riskChecks you pass). All or nothing: one op that can't be placed rejects the preview with edit-no-match, naming it. Keep the id to apply within 30 minutes. Files under Excluded files read as missing.

applyEdits

applyEdits(pluginId: string, previewId: string, opts?: {
    ops?: {
        path: string;
        op: number;
    }[];
    run?: string;
}): Promise<ApplyResult>

Applies a preview's ops (all, or those in ops), all or nothing: every range is checked first, and if any changed since the preview, nothing is written (edit-stale). Each op is written as its own step (one Cmd+Z in an open note) with a snapshot, and returns an undo handle. If a write fails part-way, files already written are put back.

undoEdit

undoEdit(pluginId: string, handle: string): Promise<string>

Undoes one applied op by its handle, if what it wrote is still there; otherwise keeps the note and rejects with edit-stale. Resolves to what it did. Handles last 30 days.

Functions

formatTranscript

declare function formatTranscript(t: Transcript): string;

A transcript as note text: frontmatter, the header comment, then each turn under a heading.

parseTranscript

declare function parseTranscript(text: string): {
    ok: true;
    transcript: Transcript;
} | {
    ok: false;
    problem: TranscriptProblem;
};

Reads a transcript note's text. Only the body's comments are read, so a note whose frontmatter someone edited still reads; { ok: false } names why a note isn't one.

isBuiltinTier

declare function isBuiltinTier(s: unknown): s is BuiltinTierName;

True for the id of a built-in tier (quick, balanced…); false for a custom tier's id or any name.

isCompatible

declare function isCompatible(actual: string, wanted: string): boolean;

True when a consumer built against wanted can use an API at actual: same major and a minor at least as new. Before 1.0, minors break by default, so a 0.x Hub serves only the minors it promises (0.1 and up).

getHub

declare function getHub(app: unknown, wanted: string, opts?: {
    by?: string;
    for?: string;
}): GetHubResult;

Finds the Hub in this vault and checks its API version against the one you built for. The Hub may be disabled and enabled again while your plugin runs, and the new Hub has none of your registered skills: listen for model-hub:ready (or use onHubReady) and call getHub + registerSkills again there.

hasMethod

declare function hasMethod<K extends keyof HubApi>(api: HubApi, name: K): api is HubApi & Required<Pick<HubApi, K>>;

Whether this Hub has a method newer than the version you gate on: if (hasMethod(hub, 'skillsFolder')) …. Methods tagged "Since" a later version than your gate may be missing on an older Hub.

onHubReady

declare function onHubReady(app: unknown, plugin: {
    registerEvent(ref: unknown): void;
}, wanted: string, cb: (hub: HubApi) => void): void;

Runs cb with the Hub now if it is loaded (and compatible with wanted), and again each time the Hub loads: the Hub fires model-hub:ready on the workspace when it loads, and one disabled and enabled again starts with no registered skills. Do your registerSkills (and registerRoles, harmless to repeat) in cb, on the hub it is given. The listener is registered on your plugin, so it stops when your plugin unloads.

onHubReady(this.app, this, '0.4.0', (hub) => hub.registerSkills(this.manifest.id, MY_SKILLS));

Without the helper: this.registerEvent(this.app.workspace.on('model-hub:ready', (api) => …)), checking api.version with isCompatible.

Constants

BUILTIN_TIERS

declare const BUILTIN_TIERS: readonly [
    "quick",
    "balanced",
    "careful",
    "frontier",
    "research",
    "embedding",
    "decision"
];

The seven tiers every Hub has. They can't be deleted, and no custom tier may shadow one.

DEFAULT_TIER

declare const DEFAULT_TIER: BuiltinTierName;

What a role resolves to when it names neither a tier nor models, or names a tier that doesn't exist.

HUB_PLUGIN_ID

declare const HUB_PLUGIN_ID = "model-hub";

Model Hub's plugin id, as app.plugins.getPlugin knows it.

HUB_READY_EVENT

declare const HUB_READY_EVENT = "model-hub:ready";

The workspace event the Hub fires each time it loads, with its API: at startup, and when the user disables and enables it again. A new Hub starts with no registered skills, so consumers register theirs again on it.

MAX_SAME_CALL

declare const MAX_SAME_CALL = 2;

A tool called with the same input more than this many times in a run gets repeated-call instead of running. Part of the API: consumers price agent runs with it (Learning Editor's toolResultTokens), so a change is a CHANGELOG line.

MAX_STEPS

declare const MAX_STEPS = 8;

Steps a run may take unless the caller says otherwise.

MODEL_HUB_IA

declare const MODEL_HUB_IA: {
    v: 1;
    owner: string;
    terms: {
        model: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        provider: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        key: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        tier: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        role: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        skill: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        tool: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        mcp: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        chat: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        conversation: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        "selection-command": {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
        usage: {
            id: string;
            label: string;
            icon: string;
            meaning: string;
            code: string[];
            since: string;
        };
    };
    avoid: {
        transcript: string;
        llm: string;
    };
    kinds: {};
    controls: string[];
};

REASONING_BUDGETS

declare const REASONING_BUDGETS: Record<Exclude<ReasoningLevel, "off">, number>;

Thinking tokens allowed at each level, added to the reply cap when the request is sent.

REASONING_LEVELS

declare const REASONING_LEVELS: readonly ReasoningLevel[];

The four levels, in order from no thinking to the most.

TIER_NAMES

declare const TIER_NAMES: readonly [
    "quick",
    "balanced",
    "careful",
    "frontier",
    "research",
    "embedding",
    "decision"
];

The name API 0.1–0.3 gave {@link BUILTIN_TIERS}; kept so their consumers still compile.

TRANSCRIPT_TYPE

declare const TRANSCRIPT_TYPE = "model-hub-transcript";

The frontmatter type of a transcript note.

TRANSCRIPT_VERSION

declare const TRANSCRIPT_VERSION = 1;

The format's version; a reader refuses a transcript of a newer one.

Types

Open a type to see its members.

AgentEvent type

What a run reports as it goes. step counts from 0.

type AgentEvent = {
    type: "step-start";
    step: number;
    last: boolean;
} | {
    type: "text";
    delta: string;
} | {
    type: "tool-call";
    step: number;
    name: string;
    input: unknown;
} | {
    type: "tool-result";
    step: number;
    name: string;
    input: unknown;
    ok: boolean;
    empty?: boolean;
    error?: string;
} | {
    type: "step-end";
    step: number;
    result: CompleteResult;
    spentUsd: number;
} | {
    type: "end";
    result: AgentResult;
};
AgentLoopOptions interface
interface AgentLoopOptions
  • messages: HubMessage[]
  • tools?: string[]

    Registered tool names offered to the model; default none.

  • finishTools?: string[]

    Subset of tools that end the run on success.

  • budgetUsd?: number
  • run?: string

    An open run of the caller's to work in; the loop then opens and ends none.

  • maxSteps?: number
  • signal?: AbortSignal
AgentResult interface

How a run ended.

interface AgentResult
  • reason: "answered" | "finished" | "step-limit" | "budget" | "stopped" | "error"
  • text: string

    All steps' text.

  • final?: unknown

    The finish tool's output, uncut, when reason is finished.

  • lastError?: string

    The finish tool's last error, when reason is step-limit.

  • steps: CompleteResult[]
  • spentUsd: number

    This run's spend: billed where known, listed otherwise; local and subscription count 0.

  • messages: HubMessage[]

    The full history, provider turns and tool results included, for a caller that continues the conversation.

  • error?: HubError

    Why the run ended, for budget, stopped and error.

AgentRun interface

A run in progress.

interface AgentRun
  • readonly id: string

    The run id.

  • events: AsyncIterable<AgentEvent>

    One consumer may read these; a caller that doesn't still gets result.

  • result: Promise<AgentResult>

    Resolves on every end, stopped and error included; never rejects.

  • stop(): void
AgentRunOptions type

What a caller passes to start an agent run: the loop's options, plus the system prompt and how the model is chosen.

type AgentRunOptions = AgentLoopOptions & {
    system: string;
    tier?: StreamOptions["tier"];
    model?: StreamOptions["model"];
    skills?: string[];
};
Answer type

The answer to one question, in the shape of its type.

type Answer = {
    type: "noul";
    noul: number;
} | {
    type: "choice";
    choice: string;
    probabilities: Record<string, number>;
    confidence?: number;
} | {
    type: "score";
    score: number;
    legend: Record<string, string>;
    probabilities: Record<string, number>;
    confidence?: number;
};
ApplyResult interface
interface ApplyResult
  • undo: { path: string; op: number; handle: string; }[]

    One undo handle per applied op, in the order applied.

AssistantMessage interface
interface AssistantMessage
  • role: "assistant"
  • content: string | (TextPart | FilePart | ReasoningPart | ToolCallPart | ToolResultPart)[]
  • providerOptions?: ProviderOptions
Attempt interface

One candidate the call considered: skipped before sending, sent and failed, or the one that answered.

interface Attempt
  • provider: string
  • model: string
  • outcome: "skipped" | "failed" | "ok"
  • code?: HubErrorCode
  • reason?: string
  • ledgerId?: string

    The ledger row of a failed attempt, the same id as the call span's hub.ledger_id.

BatchOptions interface

Options for batch().

interface BatchOptions
  • concurrency?: number

    Calls running at once (default 4).

  • stopOnOvercharge?: boolean

    Stop the batch when a call is billed well above its listed price (default true).

  • maxSpendUsd?: number

    Stop the batch once this much has been spent, in US dollars.

  • signal?: AbortSignal

    Stops the whole batch.

  • onProgress?: (p: BatchProgress) => void

    Called after each item finishes or fails.

BatchProgress interface

How far a batch has got.

interface BatchProgress
  • done: number

    Items finished, failures included.

  • total: number
  • failed: number

    Items whose call rejected.

  • spentUsd: number

    Spent so far: each call's billed cost, or its listed cost when the provider reports none.

BatchResult interface

What batch() resolves to. It doesn't reject when calls fail: each failure is in results.

interface BatchResult<R>
  • results: (R | HubError | undefined)[]

    One entry per item, in order: the result, the error, or undefined when the batch stopped before it.

  • progress: BatchProgress

    The final count and spend.

  • stopped?: { reason: "overcharge" | "spend-limit" | "aborted"; index: number; message: string; }

    Why the batch stopped early, if it did.

BillingKind type

How a provider charges: per token, nothing (local), or a flat subscription.

type BillingKind = "metered" | "local" | "subscription";
BudgetStatus interface

One monthly budget that applies to a call, and how much of it is spent.

interface BudgetStatus
  • scope: "role" | "tier"

    Whether the budget is the role's own or its tier's.

  • spentUsd: number

    Spent this calendar month, before this call.

  • limitUsd: number
  • warn: boolean

    Spent at least 80% of the limit.

BuiltinTierName type

The id of one of the seven built-in tiers.

type BuiltinTierName = (typeof BUILTIN_TIERS)[number];
CallOptionsBase interface · not exported, part of another type

Characters of skill text a request may carry. Skills over it are left out and reported. */ export const SKILL_BUDGET_CHARS = 40_000;

/** Reply allowance when neither the call nor the candidate sets one. Thinking room is added on top (see reasoningPlan). */ export const DEFAULT_MAX_OUTPUT_TOKENS = 4096;

/** Share of a monthly budget at which results start warning. */ export const BUDGET_WARN_SHARE = 0.8;

export interface HubDeps { providers: ProviderModule[]; catalog: Catalog; roles: RoleRegistry; tiers?: TierSet; ledger: LedgerStore; skills?: SkillLibrary; tracer?: Tracer; now?: () => Date; newId?: () => string; /** Open runs: their budgets are checked before each call and their spend counted after. */ runs?: RunRegistry; }

/ Options every call takes. */ / What every call may carry, whatever its kind: what it is about, how to stop it, and the run's own tier, models, routing or reasoning.

interface CallOptionsBase
  • tier?: TierName

    Use this tier's models and limits for one run instead of the role's (roles that allow any model).

  • routing?: Record<string, unknown>

    Routing merged into every candidate's own routing for this call (OpenRouter: pin or exclude providers, a price cap…). Recorded in the ledger.

  • hosts?: HostPreference

    How OpenRouter picks a host for this call, over the role's and the tier's setting: cheapest, balanced (the fastest at or under the listed price) or fastest. Recorded in the ledger. Any role.

  • reasoning?: ReasoningSetting

    How much the model may think on this call, over whatever the role or its models say: a level, or the 0.1 booleans. The Hub leaves room for it on top of maxOutputTokens. Recorded in the ledger.

  • candidates?: Candidate[]

    This call's own ordered candidates (roles that allow any model). Limits still apply.

  • model?: { provider: string; model: string; }

    Use this model for one run instead of the role's candidates (no fallback). It must be one of the role's candidates, or the role must allow any model.

  • for?: string

    What the call is about, for the ledger.

  • run?: string

    The run this call belongs to: its cost counts against the run's budget. Recorded in the ledger. Through the public API the run must have been started by the plugin that owns the role (invalid-request otherwise).

  • signal?: AbortSignal

    Stops the call; it then rejects with aborted.

  • attributes?: Record<string, string | number | boolean>

    Your own labels for the call, recorded on its trace span as hub.attr.<key> when traces are on, so your logs can be matched to traces: { collection: 'garden', pass: 2 }. Up to 16; keys are lowercase letters, digits, _ and . (up to 40); text values up to 200 characters. They go to the user's collector, so put no note text in them. Not in the ledger.

CallReport interface

What every finished call reports: who served it, the usage, the cost, and how it got there.

interface CallReport
  • provider: string
  • billing: LedgerRow["billing"]

    How the provider charges: per token, nothing (local) or a subscription.

  • askedModel: string
  • servedModel?: string
  • servedProvider?: string
  • usage: Usage
  • listedUsd: number | null

    US dollars at listed prices (for a subscription: the API-price equivalent); null when unknown.

  • billedUsd?: number
  • overcharged: boolean
  • durationMs: number
  • ledgerId: string
  • reasoningLeak?: number

    Reasoning tokens the host spent although the call switched thinking off: a host that ignores the switch.

  • tier: TierName
  • attempts: Attempt[]

    Every candidate considered, in order.

  • fellBack: boolean

    True when the answer didn't come from the first candidate.

  • budgets: BudgetStatus[]

    Monthly budgets that apply, with this month's spend before this call.

Candidate interface

One model to try: a provider, a model, and how to call it.

interface Candidate
  • provider: string
  • model: string
  • routing?: Record<string, unknown>

    Provider-specific routing (OpenRouter: order, ignore, max_price, data policy, fallbackModels…).

  • reasoning?: ReasoningSetting

    How much the model may think: a level, or the 0.1 booleans. Unset: the role's setting, and off when the role has none.

  • maxOutputTokens?: number

    The longest reply allowed, not counting thinking; the Hub adds the thinking budget when it sends the call.

  • contextTokens?: number

    Context length to ask a local model for (Ollama's num_ctx).

CAPABILITIES const · not exported, part of another type

What a model can do. Filled by each provider module from its provider's own listing.

declare const CAPABILITIES: readonly [
    "streaming",
    "tools",
    "structuredOutput",
    "reasoning",
    "webSearch",
    "imageInput",
    "pdfInput",
    "embeddings",
    "promptCaching",
    "decisions"
];
Capability type

One thing a model can do. A call that needs a capability its model lacks is refused (capability-missing).

type Capability = (typeof CAPABILITIES)[number];
ClientId type

Who's running the tool: the panel, an MCP server, or another plugin.

type ClientId = "panel" | `mcp:${string}` | `plugin:${string}`;
CompleteOptions interface

Options for complete().

interface CompleteOptions extends CallOptionsBase
  • system?: string

    The system prompt. Skills' text goes before it.

  • maxOutputTokens?: number

    The longest reply allowed (default 4096), not counting thinking: the Hub adds the candidate's thinking budget when it sends the call. The per-call ceiling is checked against the cost of both together.

  • toolChoice?: "auto" | "none" | "required"

    Whether the model may call the tools: none keeps them defined (for a history that has tool calls) but asks for a text answer.

  • output?: OutputSchema

    A schema for the reply, which comes back in object: a Standard Schema (zod, Valibot, ArkType), which the reply is checked against, or a JSON Schema document, which the model is asked to follow but which isn't checked.

  • webSearch?: boolean

    Let the provider search the web; the model must have webSearch. Sources come back in sources.

  • needs?: Capability[]

    Capabilities the caller needs beyond what the options imply.

  • skills?: string[]

    Skills to apply, by name. When given (even empty), the always-on skills join them; their text goes before system, and what they require of the model is checked like any other capability.

CompleteResult interface

What complete() resolves to: the reply, and the call's report.

interface CompleteResult extends CallReport
  • text: string

    The reply's text.

  • object?: unknown

    The reply as data, when the call gave an output schema.

  • toolCalls: { toolCallId: string; toolName: string; input: unknown; }[]

    Tools the model asked to call. The Hub returns them; it doesn't run them. toolCallId pairs each with its result in the next request.

  • messages: HubMessage[]

    The assistant turn this call produced, as messages with reasoning and provider metadata kept: append them (then your tool results) to continue a tool loop.

  • sources: { url: string; title?: string; }[]

    Web pages the reply drew on, when the call searched the web.

  • finishReason: string

    Why the model stopped, as the provider reports it (stop, length, tool-calls…).

  • skills?: { included: string[]; dropped: string[]; }

    Skills that went into the request, and any left out for size.

CostEstimate type

What a call would cost, without making it (no request, no ledger row). low is one step with a short reply, high every step with a full reply and the thinking room, each step's input grown by the step before; usd is between. Put high in an automation plan's cost, so a budget holds in the worst case. high leaves out tool results unless toolResultTokens says how big they get, a fallback to another model after a failure, and retries; pair it with the run's budgetUsd as a hard cap.

type CostEstimate = {
    ok: true;
    usd: number;
    low: number;
    high: number;
    approximate: true;
    model: {
        provider: string;
        model: string;
    };
    billing: BillingKind;
    inputTokens: number;
    outputTokens: number;
    notCounted?: string[];
} | {
    ok: false;
    reason: HubErrorCode;
    message: string;
};
DecideOptions type

Options for decide().

type DecideOptions = CallOptionsBase;
DecideResult interface

What decide() resolves to: the answers, whether they are calibrated, and the call's report.

interface DecideResult extends CallReport, Pick<DecisionAnswers, "answers" | "calibrated">
    DecisionAnswers interface · not exported, part of another type

    Typed decisions: a state and named questions in, typed answers out. The shapes follow TypeSafe's System One API (Jev): yes/no ("noul"), pick one ("choice"), rate on levels ("score"). https://docs.typesafe.ai/api.md /

    /** Text, or structured data the question can refer to by field name in backticks. */ export type DecisionInput = string | Record<string, unknown> | unknown[];

    / One question about the state: yes/no (noul), pick one (choice) or rate on levels (score). */ export type Question = | { type: 'noul'; instructions: DecisionInput; criteria?: { true?: DecisionInput; false?: DecisionInput } } / Options (up to 255), each with an optional description. / | { type: 'choice'; instructions: DecisionInput; criteria: Record<string, DecisionInput | null> } /* Ordered levels, lowest first (2–10). */ | { type: 'score'; instructions: DecisionInput; criteria: DecisionInput[] };

    / What decide() takes: the thing being judged, and the questions to answer about it. */ export interface DecisionRequest { / What the questions are about: text, or structured data. / state: DecisionInput; /* The questions, each under an id of your choosing; the answers come back under the same ids. */ questions: Record<string, Question>; }

    / The answer to one question, in the shape of its type. */ export type Answer = / noul: 0 (no) to 1 (yes). Uncalibrated answers are exactly 0 or 1. / | { type: 'noul'; noul: number } | { type: 'choice'; choice: string; probabilities: Record<string, number>; confidence?: number } /* score: the probability-weighted level (0 = the first level); may land between levels. */ | { type: 'score'; score: number; legend: Record<string, string>; probabilities: Record<string, number>; confidence?: number };

    /** What a decision call returns.

    interface DecisionAnswers
    • answers: Record<string, Answer>

      One answer per question, under the question's id.

    • calibrated: boolean

      True when the probabilities come from a calibrated decision model (e.g. Jev), not a text model's single answer.

    • usage: { inputTokens: number; outputTokens: number; }
    • model?: string

      The model that answered, when the provider says.

    DecisionInput type

    Text, or structured data the question can refer to by field name in backticks.

    type DecisionInput = string | Record<string, unknown> | unknown[];
    DecisionRequest interface

    What decide() takes: the thing being judged, and the questions to answer about it.

    interface DecisionRequest
    • state: DecisionInput

      What the questions are about: text, or structured data.

    • questions: Record<string, Question>

      The questions, each under an id of your choosing; the answers come back under the same ids.

    EditOp interface

    One replacement in a note: oldText must match once (or every time, with replaceAll).

    interface EditOp
    • oldText: string
    • newText: string
    • replaceAll?: boolean
    EditPreview interface
    interface EditPreview
    • id: string
    • files: { path: string; before: string; after: string; ops: PreviewOp[]; }[]
    EmbeddingDescription interface

    What describeEmbedding says about a spec, without a call.

    interface EmbeddingDescription
    • spec: EmbeddingSpec
    • fingerprint: string

      The fingerprint an embed call with this spec gives (v2: and 16 hex characters).

    • fingerprintV1?: string

      The 0.9–0.11 fingerprint a call served by this model would have given; only when dimensions is set, since it hashed the vectors' length.

    • formVersion: number

      The version of the Hub's prefix table that made prefixes.

    • prefixes: EmbedForm

      What the Hub puts before query and passage texts under this spec.

    • pricePerMTokens: number | null

      US dollars per million input tokens at listed prices: 0 for a local model, null when unpriced or unknown.

    • available: { ok: true; } | { ok: false; code: "unknown-provider" | "provider-unavailable" | "unknown-model" | "capability-missing"; reason: string; }

      Whether the spec can run now, and if not, why.

    EmbeddingSpec interface

    Everything that decides an embedding vector, in readable form. Store it with an index and pass it on every embed call for that index, so the index keeps its model when the user changes the role.

    interface EmbeddingSpec
    • provider: string

      The Hub provider id, such as openrouter or ollama.

    • model: string

      The model, as that provider names it.

    • dimensions: number | null

      Cut vectors to this many dimensions (Matryoshka models); null keeps the model's own.

    • form: "bare" | "family"

      family: the Hub adds the model family's query or passage prefix for each call's kind. bare: texts go as given.

    • instruction: string | null

      The task for instruction-tuned models (Qwen3) in the family form; null is the Hub's default task.

    EmbedForm interface · not exported, part of another type

    How each embedding model family wants its inputs (spec: Model Hub brainstorm §6). Asymmetric models embed a query and a passage differently; sending them bare works but ranks worse. Models not listed take text as is.

    interface EmbedForm
    • query: string
    • passage: string
    EmbedOptions interface

    Options for embed().

    interface EmbedOptions extends CallOptionsBase
    • spec?: EmbeddingSpec

      Run exactly this model and form: no tier, no fallback. It replaces model, candidates, tier, dimensions and instruction (giving any of them too is invalid-request). A model that isn't one of the role's runs only when the role sets pinnedModels and has embedded with it before, or sets allowAnyModel.

    • expectFingerprint?: string

      The fingerprint the caller's vectors have. A v2: value is checked before anything is sent, and needs spec. Any other value is a 0.9–0.11 fingerprint, which hashed what served the call, so it is checked after the call. Either way a mismatch rejects with fingerprint-mismatch.

    • kind?: "query" | "passage"

      Whether the texts are search queries or passages to search; the Hub adds the model family's prefix. Without it, texts go as they are.

    • dimensions?: number

      Cut each vector to this many dimensions and rescale it, for Matryoshka models (Qwen3, OpenAI's text-embedding-3).

    • instruction?: string

      The task for instruction-tuned models (Qwen3), in place of the default "retrieve relevant passages". Part of the fingerprint.

    EmbedResult interface

    What embed() resolves to.

    interface EmbedResult extends CallReport
    • embeddings: number[][]

      One vector per input text, in order.

    • dimensions: number

      The vectors' length, after any dimensions cut.

    • fingerprint: string

      A hash of the Hub provider, the model, the dimensions and the input form (prefixes and instruction). A router's upstream (which host OpenRouter sent the call to) is left out, so it stays the same from call to call. Vectors with the same fingerprint can be compared; name vector files by it so a change can't mix rows.

    • fingerprintV1?: string

      With a spec: the fingerprint is v2:…, and this is the 0.9–0.11 value for the same vectors.

    • matched?: "v1" | "v2"

      With expectFingerprint: which fingerprint it matched.

    EstimateRequest interface

    What estimate() prices: the call as it would be made.

    interface EstimateRequest
    • messages?: HubMessage[]

      The conversation, or input as one user message.

    • input?: string
    • system?: string
    • skills?: string[]
    • maxOutputTokens?: number

      The reply cap per step; the role's (or its model's) when unset.

    • steps?: number

      How many model steps the work may take (an agent run's maxSteps); 1 when unset.

    • tools?: string[]

      Names of registered tools the call would offer: their descriptions and schemas count as prompt, and the model must take tools.

    • toolResultTokens?: number

      Tokens of tool results fed back after each step, at most (a tool that returns a long note: its length in tokens, times the calls a step may make). Each later step's input grows by it in high. Unset: tool results aren't counted.

    • inputTokens?: number

      Tokens of input not known yet (pages not read, digests still to come), added to the prompt.

    • longestInputTokens?: number

      With times: the unread input of the longest of those calls. The per-call limit is checked against it, since a call over the limit is refused at run time even when the average fits; the price stays the average's.

    • times?: number

      How many independent calls of this shape: chapters, books. Multiplies the price; unlike steps, inputs don't grow.

    • decision?: DecisionRequest

      Price a decide() call instead: its prompt from the request, and a 512-token reply.

    • webSearch?: boolean

      The call searches the web: the model must search. The provider's search fee isn't known, so it isn't counted.

    FileContent type

    Bytes or base64 text, or a URL to fetch them from.

    type FileContent = string | Uint8Array | ArrayBuffer | URL;
    FilePart interface
    interface FilePart
    • type: "file"
    • data: FileContent
    • filename?: string
    • mediaType: string

      Such as application/pdf.

    • providerOptions?: ProviderOptions
    GetHubResult type

    What getHub returns: the Hub, or why there isn't one, with a message to show the user.

    type GetHubResult = {
        hub: HubApi;
    } | {
        hub: null;
        reason: "missing" | "incompatible";
        message: string;
        version?: string;
    };
    HostPreference type

    How a provider that picks a host per call (OpenRouter) picks one: cheapest, the lowest price however slow; fastest, the most tokens per second; balanced, the fastest host that costs no more than the model's listed price. A per-call ceiling caps all three.

    type HostPreference = "cheapest" | "balanced" | "fastest";
    HUB_ERROR_CODES const · not exported, part of another type

    Why the Hub refused or failed a call. Every refusal names its cause; nothing falls back silently. / /* Every code a refusal or failure can carry.

    declare const HUB_ERROR_CODES: readonly [
        "invalid-role-name",
        "invalid-skill",
        "skill-taken",
        "unknown-role",
        "role-taken",
        "unknown-tier",
        "invalid-tier-name",
        "unknown-provider",
        "unknown-model",
        "model-not-allowed",
        "fingerprint-mismatch",
        "capability-missing",
        "unpriced-model",
        "over-ceiling",
        "over-budget",
        "no-models",
        "invalid-request",
        "provider-unavailable",
        "provider-error",
        "reply-cut-off",
        "aborted",
        "tool-unknown",
        "tool-name-taken",
        "tool-invalid-input",
        "tool-denied",
        "tool-failed",
        "tool-timeout",
        "run-budget",
        "mcp-unauthorized",
        "edit-no-match",
        "edit-stale"
    ];
    HubError class · not exported, part of another type

    Why the Hub refused or failed a call. Every refusal names its cause; nothing falls back silently. / /* Every code a refusal or failure can carry. */ export const HUB_ERROR_CODES = [ 'invalid-role-name', 'invalid-skill', 'skill-taken', 'unknown-role', 'role-taken', 'unknown-tier', 'invalid-tier-name', 'unknown-provider', 'unknown-model', 'model-not-allowed', 'fingerprint-mismatch', 'capability-missing', 'unpriced-model', 'over-ceiling', 'over-budget', 'no-models', 'invalid-request', 'provider-unavailable', 'provider-error', 'reply-cut-off', 'aborted', 'tool-unknown', 'tool-name-taken', 'tool-invalid-input', 'tool-denied', 'tool-failed', 'tool-timeout', 'run-budget', 'mcp-unauthorized', 'edit-no-match', 'edit-stale', ] as const;

    export type HubErrorCode = (typeof HUB_ERROR_CODES)[number];

    /** What every Hub call rejects with. message is written for the user; branch on code.

    class HubError extends Error
    • readonly code: HubErrorCode

      Why the call was refused or failed.

    • readonly details: Record<string, unknown>

      Facts behind the refusal, by code: the role, the tier, the estimate and ceiling, what the model lacks (missing)…

    HubErrorCode type
    type HubErrorCode = (typeof HUB_ERROR_CODES)[number];
    HubMessage type

    One message of a conversation: what complete, stream and agent take, and what they give back.

    type HubMessage = SystemMessage | UserMessage | AssistantMessage | ToolMessage;
    HubTool interface

    A tool the Hub can run: its name, schema, effect, a handler, and optionally an undo function. Handlers receive input after checkInput passes, so they can assume the shape is correct.

    interface HubTool
    • name: string

      The tool's name: namespace.name, e.g., books.add_to_shelf.

    • description: string

      What the tool does, for the model and the user.

    • input: JsonSchema

      The schema for the tool's input.

    • effect: ToolEffect

      Whether the tool reads or writes.

    • replay?: ToolReplay

      Whether a call may be sent again with the same input. Optional for plugins; the Hub's own tools declare it.

    • undo?: ToolUndo

      For a write: how it is taken back. { kind: 'handle' } goes with undoHandle; { kind: 'none', reason } says why there is no undo. Optional for plugins; the Hub's own write tools declare it.

    • panelOnly?: boolean

      Only the Hub's own chat panel may see and call it: left out of the MCP server and tools(), refused for other runs.

    • undoHandle?: (result: unknown) => string | null

      Optional: given a result, return an undo handle (a string) or null. The handle goes in the run log. If null, the tool is not undoable.

    • describe?: (input: Record<string, unknown>) => Promise<ToolPreview>

      Optional: what the approval dialog shows for this input, in place of the raw arguments: a diff of the edits it will make, or a few lines of text. Called before the user is asked, under the tool's timeout. An error from it whose code is a HubErrorCode refuses the call with that code (A plain Error with code set will do).

    • handler: (input: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>

      The handler to call with the input and context; rejects on failure. An error whose code is a HubErrorCode keeps that code; any other becomes tool-failed. Its error messages reach the tool-call log and the caller; don't put argument text (note contents) in them.

    ImagePart interface
    interface ImagePart
    • type: "image"
    • image: FileContent
    • mediaType?: string

      Such as image/png; found from the bytes when left out.

    • providerOptions?: ProviderOptions
    JsonObject type

    A JSON object.

    type JsonObject = {
        [key: string]: JsonValue | undefined;
    };
    JsonSchema interface

    A JSON schema for a tool's input, as tools use it: types, properties, required, items, enum, minimum. This is a subset of the full JSON Schema spec, sufficient for tool input validation.

    interface JsonSchema
    • type: "object" | "string" | "number" | "integer" | "boolean" | "array"
    • description?: string
    • properties?: Record<string, JsonSchema>
    • required?: string[]
    • items?: JsonSchema
    • enum?: string[]
    • minimum?: number
    JsonSchemaDocument type

    A JSON Schema document, such as { type: 'object', properties: { … }, required: [ … ] }.

    type JsonSchemaDocument = {
        [key: string]: unknown;
    };
    JsonValue type

    A JSON value, as tool inputs, results and provider options carry.

    type JsonValue = null | string | number | boolean | readonly JsonValue[] | {
        readonly [key: string]: JsonValue | undefined;
    };
    LedgerFilter interface

    Which ledger rows usage() returns. Every field given must match; none returns every row.

    interface LedgerFilter
    • plugin?: string

      Calls made through this plugin's roles.

    • role?: string

      Calls through this role.

    • since?: string

      Calls at or after this time (ISO 8601).

    • until?: string

      Calls before this time (ISO 8601).

    • run?: string

      Calls that belonged to this run.

    LedgerRow interface

    One call, as the ledger records it. Refusals and failures are recorded too, at cost 0.

    interface LedgerRow
    • id: string
    • time: string
    • plugin: string
    • role: string
    • tier?: string

      The id of the tier the role resolved to.

    • tierName?: string

      That tier's name at the time of the call.

    • attempt?: number

      Which try this was, when the call fell back along its candidates (1 = the first candidate sent to).

    • hosts?: HostPreference

      How the call asked OpenRouter to pick a host: cheapest, balanced or fastest.

    • routing?: Record<string, unknown>

      Routing the caller added for this call, if any.

    • reasoning?: ReasoningLevel

      The reasoning level the call itself asked for, when it did.

    • for?: string

      What the call was about, as the calling plugin describes it.

    • operation: "complete" | "stream" | "embed" | "decide"
    • provider: string
    • billing: BillingKind
    • askedModel: string
    • servedModel?: string
    • servedProvider?: string
    • usage: Usage
    • listedUsd: number | null

      US dollars at listed prices; null when the model has no listed price.

    • billedUsd?: number

      US dollars the provider says it billed, when it says.

    • overcharged: boolean
    • durationMs: number
    • run?: string

      The run this call belonged to, if any.

    • reasoningLeak?: number

      Reasoning tokens spent although the call switched thinking off.

    • pinned?: true

      An embed call that ran an index's own model (its spec) rather than one of the role's models.

    • limits?: UsageLimit[]

      Usage limits the provider reported on this call (Claude's subscription: session and week).

    • outcome: "ok" | "refused" | "failed"
    • errorCode?: HubErrorCode
    • errorMessage?: string
    MatchMode type

    How an op was found: as written, after evening out spaces and quotes, or by close likeness.

    type MatchMode = "exact" | "normalized" | "fuzzy";
    ModelInfo interface

    A model as the Hub knows it, from its provider's own listing. models() returns these for pickers.

    interface ModelInfo
    • provider: string

      The provider's id in the Hub, such as openrouter or ollama.

    • id: string

      The model's id at that provider, as a candidate names it.

    • label?: string

      A name to show; fall back to id.

    • contextLength?: number

      Context window in tokens, when the provider says.

    • pricing: Pricing
    • priceSource?: PriceSourceKind

      Where pricing came from; absent means nobody priced it.

    • variablePrice?: true

      A router that picks a model per call: no price until the call is served.

    • capabilities: Capability[]
    ModelMessage type

    The Model Hub's public API, as other plugins see it. This folder is what consumers import.

    const found = getHub(this.app, '0.4.0');
    if (!found.hub) return new Notice(found.message);
    found.hub.registerRoles('my-plugin', [{ name: 'mine.summarize', provider: 'openrouter', model: '…' }]);
    const r = await found.hub.complete('mine.summarize', [{ role: 'user', content: 'Summarize this note.' }]);

    / import type { HubMessage } from './messages'; import type { EditOp, RiskCheck } from './edits'; export type { AssistantMessage, FileContent, FilePart, HubMessage, ImagePart, JsonObject, JsonValue, ProviderOptions, ReasoningPart, SystemMessage, TextPart, ToolCallPart, ToolMessage, ToolResultOutput, ToolResultPart, UserMessage } from './messages'; export type { JsonSchemaDocument, OutputSchema, StandardSchemaV1 } from './schema'; /** @deprecated: use HubMessage. The same type, kept so code written for 0.11 compiles.

    type ModelMessage = HubMessage;
    ModelPolicy interface

    The models to try and the spending limits: what a tier holds, and what a role may carry of its own.

    interface ModelPolicy
    • candidates: Candidate[]

      Priority order: the first that can serve the call is used; a failed call moves to the next.

    • ceilingUsd?: number

      Most a single call may cost, in US dollars.

    • monthlyBudgetUsd?: number

      Most this may spend in a calendar month, in US dollars (metered calls only).

    • hosts?: HostPreference

      How OpenRouter picks a host for these models; balanced when unset. A role's own wins over its tier's.

    OutputSchema type

    What complete()'s output takes: a JSON Schema document, or a Standard Schema such as a zod schema.

    type OutputSchema = JsonSchemaDocument | StandardSchemaV1;
    PreviewedEdit interface

    One op placed in a note: where it matched, how, and what replaces it.

    interface PreviewedEdit
    • op: number

      The op's index in the request.

    • from: number
    • to: number
    • text: string

      The note's text in the range when it matched.

    • mode: MatchMode
    • confidence: number

      1 for exact and normalized; the likeness score for fuzzy.

    • newText: string
    • rangeHash: string

      A short hash of text, the range's content at preview.

    PreviewEdit interface

    One edit as the approval dialog shows it: a few words either side, what goes and what comes.

    interface PreviewEdit
    • before: string
    • removed: string
    • added: string
    • after: string
    • mode: string

      How the text was found: exact, normalized or fuzzy.

    • risk: string[]

      Risk flags in words: "a number changed".

    PreviewOp interface

    One placed op in a preview, with the risk flags its change raises.

    interface PreviewOp extends PreviewedEdit
    • path: string
    • risk: RiskFlag[]
    PriceSourceKind type

    Where a model's price came from, in the order the catalog trusts them: typed by the user, the provider's own listing, models.dev, a module's dated table.

    type PriceSourceKind = "typed" | "listing" | "models.dev" | "table";
    PriceTier interface

    A higher price that applies to the whole call once its input passes aboveInputTokens (long-context pricing). US dollars per token.

    interface PriceTier
    • aboveInputTokens: number
    • input: number
    • output: number
    • cacheRead?: number
    Pricing interface

    US dollars per token. null means the price is unknown.

    interface Pricing
    • input: number | null
    • output: number | null
    • cacheRead?: number | null
    • cacheWrite?: number | null
    • tiers?: PriceTier[]

      Prices for long prompts, by the input size they start above.

    ProviderOptions type

    Options for one provider, by provider name, passed through untouched: { anthropic: { cacheControl: … } }.

    type ProviderOptions = Record<string, JsonObject>;
    ProviderSpend interface

    A provider's spend report: its figures, and the figures it couldn't get, each with why.

    interface ProviderSpend
    • provider: string
    • label: string
    • billing: BillingKind
    • figures: SpendFigure[]
    • unavailable: string[]

      Figures that couldn't be had, each as a reason in words ("couldn't read the key's limit: OpenRouter answered 401").

    • asOf: string

      When the report was made (ISO 8601).

    Question type

    One question about the state: yes/no (noul), pick one (choice) or rate on levels (score).

    type Question = {
        type: "noul";
        instructions: DecisionInput;
        criteria?: {
            true?: DecisionInput;
            false?: DecisionInput;
        };
    } | {
        type: "choice";
        instructions: DecisionInput;
        criteria: Record<string, DecisionInput | null>;
    } | {
        type: "score";
        instructions: DecisionInput;
        criteria: DecisionInput[];
    };
    ReasoningLevel type

    How much a model may think before it answers. off sends no reasoning; the three levels set a thinking budget the Hub adds on top of the reply cap, so thinking never eats the reply.

    type ReasoningLevel = "off" | "low" | "medium" | "high";
    ReasoningPart interface

    The model's thinking, as an earlier turn returned it. Send it back unchanged to continue a tool loop.

    interface ReasoningPart
    • type: "reasoning"
    • text: string
    • providerOptions?: ProviderOptions
    ReasoningPlan interface

    What the Hub settled for one call: the level to ask the provider for (none: the model's default) and the room to leave for thinking.

    interface ReasoningPlan
    • level?: ReasoningLevel
    • budgetTokens: number
    ReasoningSetting type

    A candidate's or role's reasoning setting: a level, or the API 0.1 booleans (false is off, true is medium).

    type ReasoningSetting = boolean | ReasoningLevel;
    ResolvedRole interface

    What a call through a role uses: the candidates to try, and every limit that applies.

    interface ResolvedRole extends RoleSpec
    • effective: Candidate[]

      The candidates to try, in order (the role's own, else the tier's).

    • ceilings: number[]

      Every per-call ceiling that applies (role and tier); a call must fit all of them.

    • budgets: { scope: "role" | "tier"; limitUsd: number; }[]

      Every monthly budget that applies.

    • tierName: string

      The display name of the tier tier is the id of.

    • hostsInEffect: HostPreference

      The host preference calls use: the role's own, else the tier's, else balanced.

    • tierMissing?: true

      Set when the role names a tier that doesn't exist (a deleted or not-yet-made custom tier): it runs on balanced.

    • missingTier?: TierName

      The tier the role names (as it names it) but that doesn't exist.

    RiskCheck interface

    A check run on each change before it is applied: a reason to look twice, or undefined.

    interface RiskCheck
    • name: string
    • check: (before: string, after: string) => string | undefined
    RiskFlag interface

    A check that fired on a change, and why.

    interface RiskFlag
    • check: string
    • message: string
    RoleDefinition interface

    A role as a plugin registers it: a name, a description, and its model policy — a tier and/or its own ordered candidates, a per-call ceiling and a monthly budget. The same schema as a tier.

    The one-model form (provider + model, API 0.1) still works and becomes a one-candidate list.

    interface RoleDefinition extends Partial<ModelPolicy>
    • name: string
    • description?: string
    • tier?: TierName

      The tier this role uses, built in or the user's own, by id or by name; balanced when it names none. A tier that doesn't exist (yet) is kept as named: the role runs on balanced, flagged tierMissing, until it is made. Store the id createTier returns; a name keeps working across renames (old names stay as aliases).

    • allowAnyModel?: boolean

      Any model in the catalog may be picked for one run (interactive roles such as the chat panel's). Limits still apply.

    • pinnedModels?: boolean

      Embedding only: an embed call with a spec may run the spec's model even when it is no longer one of the role's models, as long as this role has embedded with that model before (the ledger says so). For an index that keeps its model when the user changes the role.

    • provider?: string

      API 0.1: one provider and model.

    • model?: string
    • reasoning?: ReasoningSetting

      Reasoning for every candidate of the role that sets none: a level, or the 0.1 booleans (false is off).

    • maxOutputTokens?: number
    • contextTokens?: number
    • routing?: Record<string, unknown>
    • allowedModels?: { provider: string; model: string; }[]

      API 0.1: models a caller may pick for one run.

    RoleOverride interface

    The user's setting for a role: any of a tier, own candidates and limits. null fields clear back to the plugin's default.

    interface RoleOverride
    • tier?: TierName
    • candidates?: Candidate[]
    • ceilingUsd?: number | null
    • monthlyBudgetUsd?: number | null
    • hosts?: HostPreference | null

      How OpenRouter picks hosts for this role; null goes back to the plugin's default, then the tier's.

    RoleSpec interface

    A role's settings after the plugin's default and the user's override are combined.

    interface RoleSpec
    • name: string
    • owner: string
    • description?: string
    • tier: TierName
    • candidates: Candidate[]

      The role's own candidates; empty means "the tier's".

    • ceilingUsd?: number
    • monthlyBudgetUsd?: number
    • allowAnyModel: boolean
    • pinnedModels: boolean
    • hosts?: HostPreference

      The role's own host preference (the user's, else the plugin's); unset means the tier's.

    • allowedModels: { provider: string; model: string; }[]
    • overridden: boolean

      True when the user has changed anything.

    RunInfo interface

    A run as the API shows it.

    interface RunInfo
    • id: string
    • client: ClientId
    • budgetUsd: number | null
    • spentUsd: number
    • undo: UndoEntry[]
    • label?: string

      What the run is for, as the plugin named it.

    SkillRegistration interface

    A skill a plugin registers: its folder's text files, by relative path, SKILL.md among them.

    interface SkillRegistration
    • files: Record<string, string>
    SkillSummary interface

    A skill as skills() lists it.

    interface SkillSummary
    • name: string

      The name a call puts in skills.

    • description: string

      What the skill is for, from its SKILL.md.

    • mode: "always" | "chosen"

      always skills join every call that asks for skills. Only the user's vault skills may be always.

    • requires: Capability[]

      What a model must be able to do for this skill to be used.

    • origin: "builtin" | "vault"

      builtin: shipped with the Hub or registered by a plugin (see plugin); vault: in the user's skills folder.

    • mcpPrompt: boolean

      mcp-prompt: true in its SKILL.md: the Hub's MCP server offers this skill to outside clients (Claude Code, Codex, Claude Desktop) as a prompt, with an optional focus argument, while the user's Offer skills to MCP clients as prompts switch is on. A vault copy of your skill is offered only if the user marked it.

    • plugin?: string

      The plugin that registered this skill with registerSkills.

    • extends?: string

      A skill this one layers on; its instructions are included before this one's.

    • extendsMissing?: boolean

      extends names a skill that isn't loaded (for example, the plugin that registers it is off).

    • files(): Promise<Record<string, string>>

      The skill's own text files, by path relative to its folder (reader.yml, prompts/extract.md), recursive, without SKILL.md. A vault skill's are read from the vault at each call, so edits show at once. Only this skill's files: a parent's (extends) are the parent's files(); merging them is up to you.

    SpendFigure interface

    One figure in a provider's spend report: an amount used over a window, and what is left when the provider says.

    interface SpendFigure
    • label: string

      What the figure covers, in words: "This month", "Last 5 hours", "Key limit".

    • used: number
    • unit: SpendUnit
    • limit?: number

      The most allowed in this window, when known.

    • remaining?: number

      What is left of limit, when known.

    • resetsAt?: string

      When the window starts again (ISO 8601), when known.

    • calls?: number

      Calls in the window, when the figure counts something else.

    • equivalent?: boolean

      A dollar figure that is the API-price equivalent of subscription calls, not money billed.

    • source: "ledger" | "provider"

      ledger: counted from the Hub's own record of calls; provider: reported by the provider.

    • asOf?: string

      When the provider reported it (ISO 8601), for a figure read from an earlier call.

    SpendSummary interface

    What spend() resolves to.

    interface SpendSummary
    • month: string

      The start of the month the tier figures count from (UTC, ISO 8601).

    • tiers: TierSpend[]
    • providers: ProviderSpend[]
    SpendUnit type

    What a spend figure counts; percent is a share of a limit the provider reports (0–100).

    type SpendUnit = "usd" | "tokens" | "calls" | "percent";
    StandardSchemaV1 interface

    A schema from any library that follows the Standard Schema spec.

    interface StandardSchemaV1<Input = unknown, Output = Input>
    • readonly "~standard": StandardSchemaV1.Props<Input, Output>
    StreamOptions type

    Options for stream(): those of complete(), without structured output.

    type StreamOptions = Omit<CompleteOptions, "output">;
    StreamResult interface

    What stream() returns at once: the text as it arrives, and a promise of the full result.

    interface StreamResult
    • textStream: AsyncIterable<string>

      Text as it arrives. Ends with a HubError if the call fails or is stopped.

    • result: Promise<CompleteResult>

      Resolves when the stream ends, with the full result; rejects with a HubError.

    SystemMessage interface
    interface SystemMessage
    • role: "system"
    • content: string
    • providerOptions?: ProviderOptions
    TextAnchor interface

    A passage found by its text, with a little of what comes before and after to tell repeats apart.

    interface TextAnchor
    • exact: string
    • prefix?: string
    • suffix?: string
    TextPart interface
    interface TextPart
    • type: "text"
    • text: string
    • providerOptions?: ProviderOptions
    Tier type

    A tier as listed: its policy, its stable id (a built-in tier's is its name; a custom tier's never changes, even when it is renamed), its display name, and whether the user made it.

    type Tier = ModelPolicy & {
        id: string;
        name: TierName;
        custom: boolean;
    };
    TierName type

    A tier's id (a built-in tier's name, or a custom tier's id such as topic-map-bulk); lookups also accept its name.

    type TierName = string;
    TierSpend interface

    A tier's metered spend this calendar month (UTC), as the budget check counts it, against its monthly limit.

    interface TierSpend
    • id: string
    • name: string
    • spentUsd: number
    • limitUsd?: number

      The tier's monthly budget; absent when it has none.

    • remainingUsd?: number

      limitUsd less spentUsd, never below 0; absent with no limit.

    ToolCallPart interface

    A tool call the model made in an earlier turn.

    interface ToolCallPart
    • type: "tool-call"
    • toolCallId: string
    • toolName: string
    • input: unknown
    • providerOptions?: ProviderOptions
    • providerExecuted?: boolean

      The provider ran it itself (web search), so no result is owed.

    ToolCallResult interface

    What a successful tool call returns.

    interface ToolCallResult
    • result: unknown
    • truncated: boolean
    • outcome: ToolOutcome

      empty when the result is null, an empty string or list, or an object with empty: true.

    • raw?: unknown

      The uncut result, when result was cut. For the harness's finish tools; not sent to models.

    • undo?: string
    ToolCallRow interface

    One tool call, as the tool-call log records it. The arguments are kept only as a hash: they can hold note text.

    interface ToolCallRow
    • id: string
    • kind?: "call" | "undo"

      What the row records; absent means a call.

    • time: string
    • run: string
    • client: ClientId
    • plugin: string

      The tool's owner.

    • tool: string
    • effect: ToolEffect
    • outcome: "ok" | "refused" | "failed"
    • errorCode?: HubErrorCode
    • errorMessage?: string
    • durationMs: number
    • undo?: string

      The write's undo handle, when it returned one.

    • argsSha256: string
    • truncated?: boolean
    • empty?: boolean

      The call ran and found nothing.

    ToolContext interface

    Context passed to a tool handler: the run id, who's calling, and an abort signal for cleanup.

    interface ToolContext
    • run: string

      The run id this tool is part of; used for logging and undo.

    • client: ClientId

      The client calling the tool: the panel, an MCP server, or another plugin.

    • signal: AbortSignal

      Abort signal for the tool to check during long operations.

    ToolEffect type

    Whether a tool reads or writes. Determines audit logging and policy checks.

    type ToolEffect = "read" | "write";
    ToolInfo interface

    Public information about a registered tool: its name, schema, effect, owner, and policy. This is what the Hub shares with models and for auditing.

    interface ToolInfo
    • name: string

      The tool's name: namespace.name.

    • description: string

      What the tool does.

    • input: JsonSchema

      The schema for the tool's input.

    • effect: ToolEffect

      Whether the tool reads or writes.

    • owner: string

      The plugin or MCP server that owns the tool.

    • policy: ToolPolicy

      The policy applied to this tool.

    • panelOnly: boolean

      Only the Hub's chat panel may call it.

    • cantUndo?: string

      For a write the tool declares can't be taken back (undo: { kind: 'none' }): why, in words. The approval dialog says so.

    ToolLogFilter interface

    Which tool-call rows toolCalls() returns. Every field given must match.

    interface ToolLogFilter
    • run?: string
    • plugin?: string
    • tool?: string
    • since?: string
    • until?: string
    ToolMessage interface
    interface ToolMessage
    • role: "tool"
    • content: ToolResultPart[]
    • providerOptions?: ProviderOptions
    ToolOutcome type · not exported, part of another type
    type ToolOutcome = "ok" | "empty";
    ToolPolicy type

    The policy the Hub applies to a tool: allow without asking, ask the user, or deny outright.

    type ToolPolicy = "allow" | "ask" | "deny";
    ToolPreview type

    What a tool's describe returns for the approval dialog.

    type ToolPreview = {
        kind: "edits";
        summary?: string;
        files: {
            path: string;
            edits: PreviewEdit[];
        }[];
    } | {
        kind: "text";
        text: string;
    };
    ToolReplay type

    Whether a call may be sent again with the same input: safe when a second call has no further effect (a read, a write that sets a value), unsafe when it acts again (an append, a message sent).

    type ToolReplay = "safe" | "unsafe";
    ToolResultOutput type

    What a tool returned, for the model to read.

    type ToolResultOutput = {
        type: "text";
        value: string;
        providerOptions?: ProviderOptions;
    } | {
        type: "json";
        value: JsonValue;
        providerOptions?: ProviderOptions;
    } | {
        type: "error-text";
        value: string;
        providerOptions?: ProviderOptions;
    } | {
        type: "error-json";
        value: JsonValue;
        providerOptions?: ProviderOptions;
    } | {
        type: "execution-denied";
        reason?: string;
        providerOptions?: ProviderOptions;
    };
    ToolResultPart interface

    The result of one tool call, paired with it by toolCallId.

    interface ToolResultPart
    • type: "tool-result"
    • toolCallId: string
    • toolName: string
    • output: ToolResultOutput
    • providerOptions?: ProviderOptions
    ToolUndo type

    How a write is taken back: handle when its result gives an undo handle (through undoHandle), none when it can't be, with the reason in words ("a sent message can't be recalled").

    type ToolUndo = {
        kind: "handle";
    } | {
        kind: "none";
        reason: string;
    };
    Transcript interface
    interface Transcript
    • version: number
    • source: TranscriptSource
    • title: string

      A short title, from the first question unless one was given.

    • created: string

      ISO times.

    • updated: string
    • about?: string

      The note the conversation was about, as a vault path.

    • turns: TranscriptTurn[]
    TranscriptProblem type

    Why a note couldn't be read as a transcript.

    type TranscriptProblem = "not-a-transcript" | "newer-version" | "malformed";
    TranscriptSource type

    Where a conversation came from: the chat panel, a harness run, or a chat exported from another app.

    type TranscriptSource = "panel" | "harness" | "chatgpt" | "claude";
    TranscriptTurn interface

    One turn: who spoke, what was said, and for a reply, which model and what it cost.

    interface TranscriptTurn
    • role: "user" | "assistant"
    • text: string
    • at?: string

      When the turn was said, as an ISO time.

    • provider?: string

      For a reply: the provider and model that wrote it.

    • model?: string
    • usd?: number

      For a reply: what it cost in US dollars, every step included.

    • tools?: string[]

      For a reply: the tools the model called, by name, in order.

    UndoEntry interface

    A write's undo handle, kept on its run.

    interface UndoEntry
    • tool: string
    • owner: string
    • handle: string
    Usage interface

    Tokens a call used, as the provider reported them.

    interface Usage
    • inputTokens: number

      All input tokens, cached ones included.

    • outputTokens: number

      All output tokens, reasoning included.

    • cacheReadTokens: number

      Input tokens read from the provider's prompt cache.

    • cacheWriteTokens: number

      Input tokens written to the provider's prompt cache.

    • reasoningTokens: number

      Output tokens spent on reasoning.

    UsageLimit interface

    A usage limit as the provider reported it on a call: Claude's subscription reports its 5-hour session and its week. Recorded on the ledger row of the call that saw it.

    interface UsageLimit
    • window: string

      The provider's name for the window (five_hour, seven_day).

    • utilization: number

      Share of the limit used, 0–1.

    • resetsAt?: string

      When the window starts again (ISO 8601).

    • status?: string

      The provider's word for the state (allowed, allowed_warning, rejected).

    UserMessage interface
    interface UserMessage
    • role: "user"
    • content: string | (TextPart | ImagePart | FilePart)[]
    • providerOptions?: ProviderOptions
    Package
    @insurgently/model-hub-api
    Plugin id
    model-hub
    Needs
    Obsidian 1.13.1, desktop
    License
    MIT