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>;
}): voidRegisters 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;
}): RunInfoOpens 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
toolsthat end the run on success.budgetUsd?: numberrun?: stringAn open run of the caller's to work in; the loop then opens and ends none.
maxSteps?: numbersignal?: AbortSignal
AgentResult interface
How a run ended.
interface AgentResult
reason: "answered" | "finished" | "step-limit" | "budget" | "stopped" | "error"text: stringAll steps' text.
final?: unknownThe finish tool's output, uncut, when reason is
finished.lastError?: stringThe finish tool's last error, when reason is
step-limit.steps: CompleteResult[]spentUsd: numberThis 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?: HubErrorWhy the run ended, for
budget,stoppedanderror.
AgentRun interface
A run in progress.
interface AgentRun
readonly id: stringThe 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,
stoppedanderrorincluded; 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: stringmodel: stringoutcome: "skipped" | "failed" | "ok"code?: HubErrorCodereason?: stringledgerId?: stringThe 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?: numberCalls running at once (default 4).
stopOnOvercharge?: booleanStop the batch when a call is billed well above its listed price (default true).
maxSpendUsd?: numberStop the batch once this much has been spent, in US dollars.
signal?: AbortSignalStops the whole batch.
onProgress?: (p: BatchProgress) => voidCalled after each item finishes or fails.
BatchProgress interface
How far a batch has got.
interface BatchProgress
done: numberItems finished, failures included.
total: numberfailed: numberItems whose call rejected.
spentUsd: numberSpent 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: BatchProgressThe 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: numberSpent this calendar month, before this call.
limitUsd: numberwarn: booleanSpent 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?: TierNameUse 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?: HostPreferenceHow 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) orfastest. Recorded in the ledger. Any role.reasoning?: ReasoningSettingHow 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?: stringWhat the call is about, for the ledger.
run?: stringThe 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-requestotherwise).signal?: AbortSignalStops 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: stringbilling: LedgerRow["billing"]How the provider charges: per token, nothing (local) or a subscription.
askedModel: stringservedModel?: stringservedProvider?: stringusage: UsagelistedUsd: number | nullUS dollars at listed prices (for a subscription: the API-price equivalent); null when unknown.
billedUsd?: numberovercharged: booleandurationMs: numberledgerId: stringreasoningLeak?: numberReasoning tokens the host spent although the call switched thinking off: a host that ignores the switch.
tier: TierNameattempts: Attempt[]Every candidate considered, in order.
fellBack: booleanTrue 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: stringmodel: stringrouting?: Record<string, unknown>Provider-specific routing (OpenRouter: order, ignore, max_price, data policy, fallbackModels…).
reasoning?: ReasoningSettingHow 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?: numberThe longest reply allowed, not counting thinking; the Hub adds the thinking budget when it sends the call.
contextTokens?: numberContext 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?: stringThe system prompt. Skills' text goes before it.
maxOutputTokens?: numberThe 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:
nonekeeps them defined (for a history that has tool calls) but asks for a text answer.output?: OutputSchemaA 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?: booleanLet the provider search the web; the model must have
webSearch. Sources come back insources.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: stringThe reply's text.
object?: unknownThe reply as data, when the call gave an
outputschema.toolCalls: { toolCallId: string; toolName: string; input: unknown; }[]Tools the model asked to call. The Hub returns them; it doesn't run them.
toolCallIdpairs 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: stringWhy 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: booleanTrue 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?: stringThe 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: DecisionInputWhat 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: stringnewText: stringreplaceAll?: boolean
EditPreview interface
interface EditPreview
id: stringfiles: { path: string; before: string; after: string; ops: PreviewOp[]; }[]
EmbeddingDescription interface
What describeEmbedding says about a spec, without a call.
interface EmbeddingDescription
spec: EmbeddingSpecfingerprint: stringThe fingerprint an
embedcall with this spec gives (v2:and 16 hex characters).fingerprintV1?: stringThe 0.9–0.11 fingerprint a call served by this model would have given; only when
dimensionsis set, since it hashed the vectors' length.formVersion: numberThe version of the Hub's prefix table that made
prefixes.prefixes: EmbedFormWhat the Hub puts before query and passage texts under this spec.
pricePerMTokens: number | nullUS dollars per million input tokens at listed prices: 0 for a local model,
nullwhen 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: stringThe Hub provider id, such as
openrouterorollama.model: stringThe model, as that provider names it.
dimensions: number | nullCut vectors to this many dimensions (Matryoshka models);
nullkeeps the model's own.form: "bare" | "family"family: the Hub adds the model family's query or passage prefix for each call'skind.bare: texts go as given.instruction: string | nullThe task for instruction-tuned models (Qwen3) in the
familyform;nullis 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: stringpassage: string
EmbedOptions interface
Options for embed().
interface EmbedOptions extends CallOptionsBase
spec?: EmbeddingSpecRun exactly this model and form: no tier, no fallback. It replaces
model,candidates,tier,dimensionsandinstruction(giving any of them too isinvalid-request). A model that isn't one of the role's runs only when the role setspinnedModelsand has embedded with it before, or setsallowAnyModel.expectFingerprint?: stringThe fingerprint the caller's vectors have. A
v2:value is checked before anything is sent, and needsspec. 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 withfingerprint-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?: numberCut each vector to this many dimensions and rescale it, for Matryoshka models (Qwen3, OpenAI's text-embedding-3).
instruction?: stringThe 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: numberThe vectors' length, after any
dimensionscut.fingerprint: stringA 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?: stringWith a
spec: thefingerprintisv2:…, 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
inputas one user message.input?: stringsystem?: stringskills?: string[]maxOutputTokens?: numberThe reply cap per step; the role's (or its model's) when unset.
steps?: numberHow 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?: numberTokens 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?: numberTokens of input not known yet (pages not read, digests still to come), added to the prompt.
longestInputTokens?: numberWith
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?: numberHow many independent calls of this shape: chapters, books. Multiplies the price; unlike
steps, inputs don't grow.decision?: DecisionRequestPrice a
decide()call instead: its prompt from the request, and a 512-token reply.webSearch?: booleanThe 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: FileContentfilename?: stringmediaType: stringSuch 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: HubErrorCodeWhy 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: stringThe tool's name: namespace.name, e.g.,
books.add_to_shelf.description: stringWhat the tool does, for the model and the user.
input: JsonSchemaThe schema for the tool's input.
effect: ToolEffectWhether the tool reads or writes.
replay?: ToolReplayWhether a call may be sent again with the same input. Optional for plugins; the Hub's own tools declare it.
undo?: ToolUndoFor a write: how it is taken back.
{ kind: 'handle' }goes withundoHandle;{ kind: 'none', reason }says why there is no undo. Optional for plugins; the Hub's own write tools declare it.panelOnly?: booleanOnly 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 | nullOptional: 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
codeis a HubErrorCode refuses the call with that code (A plainErrorwithcodeset 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
codeis a HubErrorCode keeps that code; any other becomestool-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: FileContentmediaType?: stringSuch 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?: stringproperties?: Record<string, JsonSchema>required?: string[]items?: JsonSchemaenum?: 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?: stringCalls made through this plugin's roles.
role?: stringCalls through this role.
since?: stringCalls at or after this time (ISO 8601).
until?: stringCalls before this time (ISO 8601).
run?: stringCalls 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: stringtime: stringplugin: stringrole: stringtier?: stringThe id of the tier the role resolved to.
tierName?: stringThat tier's name at the time of the call.
attempt?: numberWhich try this was, when the call fell back along its candidates (1 = the first candidate sent to).
hosts?: HostPreferenceHow 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?: ReasoningLevelThe reasoning level the call itself asked for, when it did.
for?: stringWhat the call was about, as the calling plugin describes it.
operation: "complete" | "stream" | "embed" | "decide"provider: stringbilling: BillingKindaskedModel: stringservedModel?: stringservedProvider?: stringusage: UsagelistedUsd: number | nullUS dollars at listed prices; null when the model has no listed price.
billedUsd?: numberUS dollars the provider says it billed, when it says.
overcharged: booleandurationMs: numberrun?: stringThe run this call belonged to, if any.
reasoningLeak?: numberReasoning tokens spent although the call switched thinking off.
pinned?: trueAn 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?: HubErrorCodeerrorMessage?: 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: stringThe provider's id in the Hub, such as
openrouterorollama.id: stringThe model's id at that provider, as a candidate names it.
label?: stringA name to show; fall back to
id.contextLength?: numberContext window in tokens, when the provider says.
pricing: PricingpriceSource?: PriceSourceKindWhere
pricingcame from; absent means nobody priced it.variablePrice?: trueA 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?: numberMost a single call may cost, in US dollars.
monthlyBudgetUsd?: numberMost this may spend in a calendar month, in US dollars (metered calls only).
hosts?: HostPreferenceHow OpenRouter picks a host for these models;
balancedwhen 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: numberThe op's index in the request.
from: numberto: numbertext: stringThe note's text in the range when it matched.
mode: MatchModeconfidence: number1 for exact and normalized; the likeness score for fuzzy.
newText: stringrangeHash: stringA 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: stringremoved: stringadded: stringafter: stringmode: stringHow the text was found:
exact,normalizedorfuzzy.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: stringrisk: 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: numberinput: numberoutput: numbercacheRead?: number
Pricing interface
US dollars per token. null means the price is unknown.
interface Pricing
input: number | nulloutput: number | nullcacheRead?: number | nullcacheWrite?: number | nulltiers?: 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: stringlabel: stringbilling: BillingKindfigures: 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: stringWhen 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: stringproviderOptions?: 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?: ReasoningLevelbudgetTokens: 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: stringThe display name of the tier
tieris the id of.hostsInEffect: HostPreferenceThe host preference calls use: the role's own, else the tier's, else
balanced.tierMissing?: trueSet when the role names a tier that doesn't exist (a deleted or not-yet-made custom tier): it runs on
balanced.missingTier?: TierNameThe 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: stringcheck: (before: string, after: string) => string | undefined
RiskFlag interface
A check that fired on a change, and why.
interface RiskFlag
check: stringmessage: 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: stringdescription?: stringtier?: TierNameThe tier this role uses, built in or the user's own, by id or by name;
balancedwhen it names none. A tier that doesn't exist (yet) is kept as named: the role runs onbalanced, flaggedtierMissing, until it is made. Store theidcreateTierreturns; a name keeps working across renames (old names stay as aliases).allowAnyModel?: booleanAny model in the catalog may be picked for one run (interactive roles such as the chat panel's). Limits still apply.
pinnedModels?: booleanEmbedding only: an
embedcall with aspecmay 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?: stringAPI 0.1: one provider and model.
model?: stringreasoning?: ReasoningSettingReasoning for every candidate of the role that sets none: a level, or the 0.1 booleans (
falseis off).maxOutputTokens?: numbercontextTokens?: numberrouting?: 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?: TierNamecandidates?: Candidate[]ceilingUsd?: number | nullmonthlyBudgetUsd?: number | nullhosts?: HostPreference | nullHow OpenRouter picks hosts for this role;
nullgoes 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: stringowner: stringdescription?: stringtier: TierNamecandidates: Candidate[]The role's own candidates; empty means "the tier's".
ceilingUsd?: numbermonthlyBudgetUsd?: numberallowAnyModel: booleanpinnedModels: booleanhosts?: HostPreferenceThe role's own host preference (the user's, else the plugin's); unset means the tier's.
allowedModels: { provider: string; model: string; }[]overridden: booleanTrue when the user has changed anything.
RunInfo interface
A run as the API shows it.
interface RunInfo
id: stringclient: ClientIdbudgetUsd: number | nullspentUsd: numberundo: UndoEntry[]label?: stringWhat 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: stringThe name a call puts in
skills.description: stringWhat the skill is for, from its
SKILL.md.mode: "always" | "chosen"alwaysskills join every call that asks for skills. Only the user's vault skills may bealways.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 (seeplugin);vault: in the user's skills folder.mcpPrompt: booleanmcp-prompt: truein itsSKILL.md: the Hub's MCP server offers this skill to outside clients (Claude Code, Codex, Claude Desktop) as a prompt, with an optionalfocusargument, 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?: stringThe plugin that registered this skill with
registerSkills.extends?: stringA skill this one layers on; its instructions are included before this one's.
extendsMissing?: booleanextendsnames 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, withoutSKILL.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'sfiles(); 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: stringWhat the figure covers, in words: "This month", "Last 5 hours", "Key limit".
used: numberunit: SpendUnitlimit?: numberThe most allowed in this window, when known.
remaining?: numberWhat is left of
limit, when known.resetsAt?: stringWhen the window starts again (ISO 8601), when known.
calls?: numberCalls in the window, when the figure counts something else.
equivalent?: booleanA 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?: stringWhen the provider reported it (ISO 8601), for a figure read from an earlier call.
SpendSummary interface
What spend() resolves to.
interface SpendSummary
month: stringThe 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: stringproviderOptions?: 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: stringprefix?: stringsuffix?: string
TextPart interface
interface TextPart
type: "text"text: stringproviderOptions?: 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: stringname: stringspentUsd: numberlimitUsd?: numberThe tier's monthly budget; absent when it has none.
remainingUsd?: numberlimitUsdlessspentUsd, 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: stringtoolName: stringinput: unknownproviderOptions?: ProviderOptionsproviderExecuted?: booleanThe provider ran it itself (web search), so no result is owed.
ToolCallResult interface
What a successful tool call returns.
interface ToolCallResult
result: unknowntruncated: booleanoutcome: ToolOutcomeemptywhen the result is null, an empty string or list, or an object withempty: true.raw?: unknownThe uncut result, when
resultwas 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: stringkind?: "call" | "undo"What the row records; absent means a call.
time: stringrun: stringclient: ClientIdplugin: stringThe tool's owner.
tool: stringeffect: ToolEffectoutcome: "ok" | "refused" | "failed"errorCode?: HubErrorCodeerrorMessage?: stringdurationMs: numberundo?: stringThe write's undo handle, when it returned one.
argsSha256: stringtruncated?: booleanempty?: booleanThe 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: stringThe run id this tool is part of; used for logging and undo.
client: ClientIdThe client calling the tool: the panel, an MCP server, or another plugin.
signal: AbortSignalAbort 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: stringThe tool's name: namespace.name.
description: stringWhat the tool does.
input: JsonSchemaThe schema for the tool's input.
effect: ToolEffectWhether the tool reads or writes.
owner: stringThe plugin or MCP server that owns the tool.
policy: ToolPolicyThe policy applied to this tool.
panelOnly: booleanOnly the Hub's chat panel may call it.
cantUndo?: stringFor 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?: stringplugin?: stringtool?: stringsince?: stringuntil?: 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: stringtoolName: stringoutput: ToolResultOutputproviderOptions?: 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: numbersource: TranscriptSourcetitle: stringA short title, from the first question unless one was given.
created: stringISO times.
updated: stringabout?: stringThe 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: stringat?: stringWhen the turn was said, as an ISO time.
provider?: stringFor a reply: the provider and model that wrote it.
model?: stringusd?: numberFor 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: stringowner: stringhandle: string
Usage interface
Tokens a call used, as the provider reported them.
interface Usage
inputTokens: numberAll input tokens, cached ones included.
outputTokens: numberAll output tokens, reasoning included.
cacheReadTokens: numberInput tokens read from the provider's prompt cache.
cacheWriteTokens: numberInput tokens written to the provider's prompt cache.
reasoningTokens: numberOutput 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: stringThe provider's name for the window (
five_hour,seven_day).utilization: numberShare of the limit used, 0–1.
resetsAt?: stringWhen the window starts again (ISO 8601).
status?: stringThe provider's word for the state (
allowed,allowed_warning,rejected).
UserMessage interface
interface UserMessage
role: "user"content: string | (TextPart | ImagePart | FilePart)[]providerOptions?: ProviderOptions