import type { z } from 'zod'; import type { Logger } from '../../utils/logger.js'; import type { CodeNavigationProvider } from '../code-navigation/index.js'; import type { ToolResultGuardrailSpec } from '../guardrail/index.js'; import type { SessionId, TurnId } from '../ids/index.js'; import type { InvocationState } from '../invocation/index.js'; import type { PermissionMode } from '../permission/index.js'; import type { Sandbox } from '../sandbox/index.js'; import type { ToolPresentation } from './presentation.js'; export interface ToolRegistryRef { searchDeferred(query: string): ToolDefinition[]; /** Ranked active matches, when this registry supports active-tool discovery. */ searchActive?(query: string): ToolDefinition[]; activate(names: string[]): void; getAvailability(name: string): ToolAvailability; } /** * The slice of the skills registry a tool is given. * * Structural for the reason `ToolRegistryRef` is: this file is imported by * everything, and naming `SkillRegistry` here would drag the skill loader's * filesystem imports into every consumer's type graph. */ export interface SkillRegistryRef { /** * Enumerate the metadata the model may use to discover a skill. * * Optional so an older structural registry that only supports named loads * remains a valid host. `SkillTool` refuses its list mode when this member is * absent: `names()` cannot distinguish a model skill from an operator-only * one, so treating it as a safe catalog would disclose an authority boundary. */ catalog?(): readonly { /** The name the registry accepts, which may be namespaced by its host. */ registeredName: string; description: string; location: string; allowedTools?: string; invocation?: 'model' | 'operator' | 'both'; }[] | Promise; /** * Load a skill's full body, or `undefined` for a name nobody registered. * * Full disclosure by design: a tool call asking for a skill is asking * for its instructions, and returning metadata the model already has in * its manifest would answer a question it did not ask. */ load(name: string): Promise<{ skill: { metadata: { name: string; description: string; allowedTools?: string; /** * The literal union, not `string`. Widening it here would * let a ref satisfy this interface while carrying a value * `isInvocableBy` cannot read, and the failure would be a * silent `both` — the fail-open answer. */ invocation?: 'model' | 'operator' | 'both'; }; body?: string; }; } | undefined>; /** Every registered name, for a "did you mean" that names real options. */ names(): readonly string[]; } /** * The slice of the background job registry a tool is given. * * A structural reference rather than the class, for the reason * `ToolRegistryRef` exists: this type file is imported by everything, and * naming the implementation here would drag a `node:child_process` module * into every consumer's type graph. `owner` is not on this surface at all — * the executor binds it to the turn, so a tool cannot start a job that * outlives, or is billed to, somebody else's run. */ export interface BackgroundJobRegistryRef { start(params: { command: string; workingDirectory: string; }): { id: string; status: string; }; get(id: string): { id: string; status: string; exitCode?: number; }; read(id: string, opts?: { fromOffset?: number; }): { chunk: string; nextOffset: number; droppedBytes: number; status: string; exitCode?: number; }; kill(id: string): Promise<{ id: string; status: string; }>; list(): readonly { id: string; command: string; status: string; }[]; /** * Await this job's exit instead of reading it in a loop; resolves at * once for a job that has already stopped. Optional, and added after * the rest of this surface: a host implementing this interface directly * rather than through `bindOwner` may not have it yet, so `wait_for_job` * checks for it and says so rather than assuming every host can wait. */ waitForExit?(id: string, opts?: { signal?: AbortSignal; }): Promise<{ id: string; status: string; exitCode?: number; }>; /** * Say that the model is waiting on this job, so the turn stays open for it * when the model stops calling tools. * * Called by `wait_for_job` and nothing else. The kernel holds a finishing * run open — bounded, and for no model tokens — only for a job marked * here; a job nobody marked never delays a turn, which is what a dev server * or a watcher needs. Optional for the reason `waitForExit` is: a host * implementing this interface directly may have nowhere to record the * intent, and then there is simply no hold. */ markAwaited?(id: string): void; } /** * Tracks which files the agent has read in the current turn. * Write tool consults this to enforce the "read before overwrite" invariant * an existing file must be read first or the write fails. * Keys are the resolved path used by the tool — sandbox-relative when a sandbox * is active, absolute (`workingDirectory`-resolved) otherwise. */ export interface FileReadTracker { /** * `content` lets the tracker fingerprint what was read, which is what * makes drift detectable later. Optional so a host that only needs the * read-before-overwrite guard can keep its existing implementation. * `fullWriteCallId` links a successfully written complete body to its * execution; pass ToolContext.toolUseId only after the write succeeds. */ recordRead(key: string, content?: string, fullWriteCallId?: string): void; hasRead(key: string): boolean; /** * Optional witness of a successful full-body write, supplied by the executing * tool through recordRead's third argument. An unchanged later observation * preserves it; different or unknown content clears it. Never infer from a name. */ writeCallId?(key: string): string | undefined; /** * Optional chain of calls whose bodies compose the current content: the * full-body write that started it, then the successful edits applied on * top of it, in order. * * Defined only while at least one edit sits on a witnessed write — exactly * when `writeCallId` is not. An edited file's body is no longer the write * call's body, and a consumer that knows only `writeCallId` has to keep * seeing nothing there rather than a claim that has quietly stopped being * true. The chain names INPUTS: reconstructing the body means replaying * those calls' visible arguments and checking the result against * `fingerprint(key)`, never asserting it. */ editChain?(key: string): { rootWriteCallId: string; editCallIds: readonly string[]; } | undefined; /** * Optional record of a successful edit's resulting content, with the call * that produced it. * * Does everything `recordRead(key, content)` does, and additionally extends * the chain when this edit ran against content the ledger already had a * fingerprint for and a witnessed write underneath it — the two facts that * make the replay reproducible. Missing either, it is an ordinary * observation of a body nobody can replay: the fingerprint advances and the * chain is cleared. Pass ToolContext.toolUseId only after the edit succeeded. */ recordEdit?(key: string, content: string, callId: string): void; /** * Optional witness of a read that returned the file WHOLE, with the * fingerprint of the rendering it returned. * * Does everything `recordRead(key, content)` does, and additionally records * that the body is visible in `callId`'s receipt — not as text this ledger * holds, but as the exact rendering `callId` emitted, so a consumer can * check the receipt it can see against `renderedFingerprint` before * referencing it. A partial read never calls this: a window proves nothing * about the rest of the file. Pass `ToolContext.toolUseId` and the * fingerprint of the tool's own output string. * * The witness is recorded only where no write witness or chain survives the * observation, because a write-rooted body is one the model composed and * can be replayed through. It is cleared by exactly what clears a write * witness — different or unknown content — and additionally by any * `recordEdit`, because a read roots no chain: the body it witnesses exists * only as a rendering, and nothing may be replayed on top of it. */ recordFullRead?(key: string, content: string, callId: string, renderedFingerprint: string): void; /** * Optional witness of the full read above: the call whose receipt holds the * body, and the fingerprint of what that call emitted. * * `renderedFingerprint` is deliberately not the body's fingerprint — * `fingerprint(key)` is that. It describes the RECEIPT, so a consumer * admits the reference only while the receipt it can see is byte-for-byte * what the tool returned; an elided, spilled or cleared one is not. * * The derived work context asks a tracker for `writeCallId` and * `fingerprint` before it reads any witness at all — a name alone does not * say the ledger is the one execution wrote — so a tracker implementing * this pair and neither of those still establishes nothing. */ readWitness?(key: string): { callId: string; renderedFingerprint: string; } | undefined; /** * Optional note that a built-in mutation has just refused this path because * the body on disk differs from the fingerprint above. * * Not an observation. The refusing tool read the disk, but all it reports * here is the disagreement — recording the body it found would re-baseline * the drift check and admit the very mutation that was refused. An * implementation must therefore leave `fingerprint`, `hasRead`, * `writeCallId`, `editChain` and `readWitness` exactly as they were, and any * later content observation clears the flag. It exists so a consumer that * cannot read the filesystem — the derived work context — can stop * referencing a body it has been told is stale, without a check of its own. */ recordDriftObserved?(key: string): void; /** Whether a refused mutation has reported this path stale since the last observation. */ driftObserved?(key: string): boolean; /** * Fingerprint of the body captured at the last read, when one was. * * A file mutation is computed against what the agent READ, and between * that read and the write the file may have moved under it — a person * editing in an editor, another process, a second agent. The in-process * lock cannot see any of those. Comparing this against the body actually * on disk at mutation time is what turns a silent lost update into a * refusal the agent can act on by re-reading. */ fingerprint?(key: string): string | undefined; } export interface ToolPauseOption { readonly id: string; readonly label: string; readonly description?: string; } export interface ToolPauseRequest { /** * Names this pause within the call. * * A tool call may pause more than once — "which environment", then * "are you sure" — and the answers have to be told apart. The name is * what a resume payload is routed by, so it is the author's to choose * and should describe the decision, not the tool. */ readonly name: string; /** The question, in the words the human will read. */ readonly prompt: string; /** Short topic label, when the surface showing this has room for one. */ readonly header?: string; readonly options?: readonly ToolPauseOption[]; readonly multiSelect?: boolean; /** Defaults to true: a human who disagrees with every option can say so. */ readonly allowFreeText?: boolean; } /** * How a pause ended. * * `unanswered` is deliberately not a variant of `answered` with an empty * selection. A tool that pauses to ask "may I charge this card" and reads * silence as yes is worse than one that never asked, so the absence of an * answer has its own shape and cannot be destructured into consent by * accident. */ export type ToolPauseOutcome = { readonly status: 'answered'; readonly selectedOptionIds: readonly string[]; readonly text?: string; } | { readonly status: 'unanswered'; readonly reason: string; } | { readonly status: 'aborted'; }; export type RequestToolPause = (request: ToolPauseRequest) => Promise; /** * Where one tool execution entered the runtime from. * * The executor owns this value. A nested caller supplies only its private * operation context to {@link ToolContext.dispatchTool}; the executor derives * the parent id from the context it already issued, so a tool cannot relabel * itself as somebody else's child. */ export type ToolCallSource = { readonly kind: 'direct'; } | { readonly kind: 'nested'; readonly parentToolUseId: string; } | { readonly kind: 'code'; readonly parentToolUseId: string; /** The code runtime's id, unique within the parent program. */ readonly runtimeToolCallId: string; }; /** * Operation authority a trusted tool may narrow when it dispatches another * tool. * * `signal` is fused with the parent call's signal; it can revoke authority * earlier but can never extend the parent's lifetime. `runtimeToolCallId` * identifies one request inside a model-authored program. The executor still * chooses the child's event identity and the parent lineage; durable pause * ownership stays with the model-issued ancestor present in the checkpoint. */ export interface ToolDispatchOptions { readonly signal?: AbortSignal; readonly runtimeToolCallId?: string; } export interface ToolContext { /** * Host-owned read snapshot. Bound to this tool call's lifetime, including its * deadline; an optional signal can cancel capture earlier. Never replays * effects. Unsupported stores return undefined. */ captureSessionEvidence?: (maxReadBytes?: number, signal?: AbortSignal) => Promise; /** The session this call belongs to. */ sessionId: SessionId; /** The turn this call belongs to. */ turnId: TurnId; workingDirectory: string; /** * Directories besides the working directory the file tools may reach, * absolute. A host adds one for a session (`/add-dir`); a sandboxed turn * binds each. Relative paths still resolve against the working * directory; an absolute path inside any of these is accepted. */ additionalDirectories?: readonly string[]; /** * Absolute paths outside the working directory and the added directories * that THIS call was approved to reach. * * Set by the executor only for a call a review approved after the * kernel named these paths in it (`ToolCallSummary.escalation`), and only * when the turn asked for that (`QueryParams.outsideRootAccess: * 'review'`). The file tools treat each as one more root for this call * alone; nothing here widens the next call. Absent is the ordinary case: * a path outside the roots is refused. */ approvedPaths?: readonly string[]; /** * This call was approved to run outside the turn's sandbox. * * Set by the executor only when a reviewer CONFIRMED the escape for this * call by id (`HITLResumeDecision.confirmedEscalations`) and the turn * allows escapes at all (`QueryParams.sandboxEscape: 'review'`). A tool * that offers an escape honours it only when this is `true`, and refuses * the request otherwise. */ sandboxEscapeApproved?: boolean; abortSignal: AbortSignal; env: Record; log: (level: 'info' | 'warn' | 'error', message: string) => void; permissionContext?: { mode: PermissionMode; sessionId: string; turnId: string; workingDirectory: string; }; invocationState?: InvocationState; toolRegistry?: ToolRegistryRef; /** * The names this turn may call, if the turn was narrowed. * * Enforced at dispatch, not only used to decide which schemas the model is * shown. It was the latter alone for a while, which made the narrowing * presentational: a step could withhold a tool from the request and the * executor would still run it when the model named it anyway — from * repeated context, from a gateway with its own tool memory, or from a * replayed prefix. * * Absent means no narrowing, which is not the same as an empty list: an * empty list is a turn that may call nothing. */ allowedTools?: readonly string[]; sandbox?: Sandbox; /** * Symbol resolution for this turn, when a host wired one up. * * Absent means the `lsp` tool is not registered at all — see * `tools/builtins/lsp.ts` for why a tool that is always present and * always says "unavailable" is worse than one that is not there. */ codeNavigation?: CodeNavigationProvider; fileReadTracker?: FileReadTracker; /** * Where work that outlives this call is held. * * Absent means the host has provided nowhere to put a background job, and * a tool asked for one must REFUSE rather than fall back to `cmd &`. The * fallback is not a lesser version of this — under the local sandbox's * `linux-namespace` tier the wrapping `sh` is PID 1 of a fresh PID * namespace, so a backgrounded grandchild dies the moment that shell * exits, on the successful path. It returns in milliseconds looking like * it worked, with the work already dead. It is also absent from sandboxed * query-built contexts: the shipped registry owns host processes and * cannot preserve a sandbox boundary. */ backgroundJobs?: BackgroundJobRegistryRef; /** * Where the `skill` tool reads from. * * Absent means the turn has no skills, and the tool says so rather than * reporting an empty list — "no skills here" and "no registry" are * different answers. */ skills?: SkillRegistryRef; /** * How this turn reaches the web. * * Two independent halves, and either may be absent. This kernel ships a * guarded fetch provider and NO search backend, so `search` missing is * the ordinary case rather than a failure — the tools say which piece is * missing so an operator can tell a wiring decision from a fault. */ web?: { readonly fetch?: { fetch(request: { url: string; signal?: AbortSignal; }): Promise<{ url: string; status: number; contentType?: string; body: string; truncated: boolean; redirects: readonly string[]; }>; }; readonly search?: { search(request: { query: string; limit?: number; signal?: AbortSignal; }): Promise<{ query: string; hits: readonly { title: string; url: string; snippet?: string; }[]; }>; }; }; /** * Adopt the tool scope a skill declared. * * Called by the `skill` tool when a loaded skill names `allowed-tools`. * The scope INTERSECTS what the turn already allows and takes effect from * the next batch — a skill loaded alongside other calls must not * retroactively refuse them. */ adoptSkillScope?: (scope: { skill: string; allowedTools: readonly string[]; }) => void; /** * Effective model-visible character cap for this tool result. * * Present on executor-owned calls so a tool that can paginate does so * before the generic head+tail fallback loses its middle. `0` means the * host disabled the cap; absent means a direct host invocation did not * declare one. */ maxToolOutputChars?: number; /** * Screens the TURN asked for, applied to results this call produces. * * Worth having because a turn usually does not build its registry: a host * assembles one and hands it to `runAgent`, so a registry-construction * option alone is the host's to write and the kernel's default reaches * nobody. * * The registry's own {@link ToolRegistryConfig.resultGuardrails} WIN when * the registry was built with them — including an empty array, which means * none — because a registry that stated its policy has stated it. These * apply to a registry that declared none, which is the ordinary case: a * host assembles a registry and hands it to a turn it does not own. * * `undefined` means the turn declared none; an empty array means the turn * declared none ON PURPOSE, which is how a caller turns off a screen the * executor would otherwise install by default. */ toolResultGuardrails?: readonly ToolResultGuardrailSpec[]; /** * Run another tool through the same dispatch this call came through. * * For `run_code`, whose whole purpose is calling tools in a loop. NOT * added to {@link ToolRegistryRef}: that ref is about discovering and * activating tools, and putting `execute` on it would make "can dispatch" * a property of holding a registry reference rather than a capability a * host wired deliberately. * * Available only for this invocation. The executor closes it when the * visible call settles or is abandoned, aborts calls already started by * it, and waits for their executor-owned terminal records before reporting * the parent complete. Retaining the function does not retain authority. * * Tools are host-installed code — the model cannot add one — so the trust * boundary this protects is the MODEL's reach. `allowedTools` is enforced * again at dispatch. When the turn has an operator authorization gate, a * nested call must be explicitly allowed by that gate; a deny or an * undecided call fails closed because another durable human review cannot * be opened from inside the already-executing parent. */ dispatchTool?: (name: string, input: unknown, options?: ToolDispatchOptions) => Promise; /** * The `tool_use_id` of the assistant block that triggered this * execution. Tools that spawn background work (e.g. coordinator * `create_task`) thread this id into their tracking metadata so * a later, asynchronous completion can be replied back as a * canonical `tool_result` content block bound to the same id. * Optional because not every executor path provides it yet. */ toolUseId?: string; /** * Stable, turn-scoped identity shared by every direct tool call the model * issued in the same response batch. * * `toolUseId` answers "which call is this?"; this answers "which sibling * calls were launched together?". Hosts use it to keep a concurrent group * visible while one sibling is still running without accidentally reviving * terminal work from an older batch or another turn whose provider reused a * call id. Optional because a host may invoke a tool directly, outside the * query executor. */ toolBatchId?: string; /** * How this execution entered the tool registry. * * Present on executor-owned calls. Optional because a host may invoke a * tool directly outside a turn and construct its own minimal context. */ source?: ToolCallSource; /** * Raise a durable pause and wait for a human to resolve it. * * The pause machinery is excellent and was reachable from exactly four * kernel-owned points — the plan gate, the tool-review gate, the * iteration cadence, and one built-in question tool. A host-authored * tool had no seam to it, so the operations that most want their own * confirmation with their own wording (a spend, an outbound post, a * destructive migration) had to settle for the generic tool-review * gate or hand-thread a recorder and a resume callback into a private * builder, which nothing in this type suggested was possible. * * The park is a real checkpoint, so a host can see the pause on every * surface a tool-review park appears on, and the answer routes back by * name on resume — several tools pausing in one batch each get their * own, and one tool may pause more than once. * * Absent when whatever is driving the tool provides no route to a * human — a host calling a tool directly, outside a turn. A tool must * treat it as optional and decide what to do without one, and must * never read an unanswered pause as consent; the outcome says which it * was, in its own shape, so silence cannot be destructured into a yes. */ requestPause?: RequestToolPause; /** * Span to parent this tool's `execute_tool` span to. * * OTel's GenAI conventions define a strict hierarchy — * `invoke_agent` → `chat {model}` → `execute_tool` — and vendor * dashboards rely on it. `startActiveSpan` cannot supply the parent * here: the span-owning bodies upstream are async GENERATORS, and a * generator resumes on its consumer's async context, so the ambient * context is already gone by the time a tool runs. Passing the parent * explicitly is the only approach that actually works, and * `ToolContext` is already threaded to exactly the right place. */ parentSpan?: import('@opentelemetry/api').Span; /** * Say how far along you are, for a host rendering a live view. * * A tool may run for the full per-tool deadline — two minutes by * default — and before this it was silent for all of it: a host could * show that a build had started and then nothing until it finished or * timed out. * * Fire-and-forget and never throws, so a tool can call it freely * without wrapping it. This is latest state, not a log: while a host is * consuming one update, later calls replace the single pending update. * Each published message is capped at 8 KiB of UTF-8 with a visible * omission marker, and every accepted update settles before that call's * terminal event. Put complete output in `ToolResult`, not here. * * The model never sees these: progress answers "is it still working?", * which is a question only a human asks, and putting it in the * conversation would spend tokens telling the model something it cannot * act on. * * Absent when the executing surface has no event stream to write to. */ report?: (message: string, fraction?: number) => void; } export interface ToolResult { success: boolean; output: string; data?: unknown; error?: string; /** * Rich content for the MODEL, when a string cannot carry it — a * screenshot, a chart, a PDF. `output` stays the text the host and the * transcript see; when this is set it is what reaches the provider. * * Keeping the two separate is deliberate: a host UI wants "screenshot * (1280x800)", the model wants the pixels, and forcing one channel to * serve both is what made `computer-use` send megabytes of base64 as * text. */ content?: import('../message/index.js').ToolResultContent; /** * This failure might succeed if tried again — a network blip, a lock * contention, a rate limit — as opposed to one that never will, like a * missing file or a rejected argument. * * Nothing distinguished the two before, so a transient failure cost a * full model round trip to retry: the error went back as a * `tool_result`, the model read it, and decided (or didn't) to call * again. Only meaningful alongside {@link ToolDefinition.maxRetries}; * a tool that has not opted into retries is never retried no matter * what it sets here. */ retryable?: boolean; /** * Facts this result pins into the turn's working memory, by key. The * kernel keeps them in the working-memory slot — in front of the model * every iteration, across compaction — and a later pin under the same * key replaces the earlier one. For what a tool knows and the model * must not lose: what its controls do, where things are, what failed. */ workingState?: readonly import('../../compaction/types.js').WorkingStatePin[]; } export interface ToolDefinition extends ToolPresentation { name: string; description: string; inputSchema: z.ZodType; /** * Optional canonical JSON Schema shown to models instead of the runtime * Zod schema. Use this when runtime compatibility accepts aliases or * constraints that should not be advertised to a model. * * This is intentionally independent of TInput: the model-facing contract * may be narrower than the execution decoder. */ modelInputSchema?: Record; /** * Ask capable providers to constrain generated input to modelInputSchema. * ToolRegistry rejects this flag unless modelInputSchema is also present. */ enforceModelInput?: boolean; /** * Concise, model-readable recovery guidance appended when inputSchema * rejects a call. Use for conditional schemas whose required shapes * cannot be reconstructed from JSON Schema's top-level `required` list. */ validationErrorHint?: string; /** * The shape this tool returns, as JSON Schema, appended to the * description the model sees. * * JSON Schema rather than Zod because it is **shown, never validated**: * namzu does not check a tool's return value against it, so converting * through Zod would only cost fidelity. Native tools that want one can * render their Zod type with `renderToolSchema`. * * Optional and omitted by default — a tool whose return shape is * obvious from its description gains nothing from spending prompt on * it, and every tool schema rides in the cached prefix of every * request. */ outputSchema?: Record; /** * The argument that holds a shell command line, when one does. * * Declared on the tool because only the tool knows. An operator writing * `bash = { "git status*" = "allow" }` means the `command` argument, and * every other way of learning that is a list somewhere else that drifts: * a host compiling permissions has the tool's NAME and no reason to know * which of its arguments is the interesting one. * * What it buys is the difference between a permission about text and a * permission about commands. A rule routed through this argument reads * `git status && rm -rf ~` as the two commands it is, so an `allow` * written for `git status*` declines it instead of approving it on the * strength of the first few words. See `decomposeCommandLine`. * * Omit it on every tool that takes no command line. A tool that runs * something but takes an argument list rather than a line — no shell, no * chaining — has nothing to decompose and must not claim otherwise. */ commandArgument?: string; /** * The argument that holds a filesystem path the tool resolves against the * turn's roots (the working directory and the added directories). * * Declared so the kernel can see, BEFORE the call runs, that it names a * path outside those roots and turn it into an approval request instead * of a refusal (`QueryParams.outsideRootAccess: 'review'`). A tool that * does not declare it keeps refusing such a path at execution, which is * the safe direction to be wrong in. */ pathArgument?: string; /** * The boolean argument by which a call asks to run outside the turn's * sandbox, when the tool offers that at all (the shipped `bash` does). * * A call that sets it under a sandbox is always reviewed, is never * approved by a mode, a remembered grant or an `allow` rule, and runs * unconfined only when a reviewer confirmed it by id. See * `ToolContext.sandboxEscapeApproved`. */ sandboxEscapeArgument?: string; execute(input: TInput, context: ToolContext): Promise; tier?: string; permissions?: ToolPermission[]; category?: 'filesystem' | 'shell' | 'network' | 'analysis' | 'custom'; /** * Deadline for a single execution, overriding the turn-level default. * * On expiry the executor stops waiting and returns a model-visible * error result, so a slow dependency becomes something the agent can * route around instead of a turn that never comes back. The tool's * `context.abortSignal` fires at the same moment; a tool that honours * it also stops doing work, and one that ignores it merely becomes * detached. * * Omit to inherit the executor's default. */ timeoutMs?: number; /** * How many times a FAILED execution may be retried in-loop before the * error is handed to the model. * * **Defaults to 0, and that default is load-bearing.** Retrying is only * safe if the tool is idempotent, and the SDK cannot know that: silently * re-running a `write_file`, a `git push` or a payment call is worse * than never retrying at all. The tool author opts in, per tool. * * Even then, only failures the tool marked * {@link ToolResult.retryable} are retried — a missing file is not * going to appear on the second attempt, and burning the budget on it * just delays the error the model needs to see. */ maxRetries?: number; /** * This tool's output IS the turn's answer: settle with it instead of * asking the model to restate it. * * Every delegation path is blocking and returns the worker's final * text as the dispatching call's result, after which the loop went * round again — so a router agent, whose entire job is to pick a * specialist, paid one extra model call per request at the parent's * full context size, the most expensive call in the turn. The relay is * also LOSSY: the parent paraphrases the worker's answer through its * own (compacted) context, so what the caller receives is not what the * worker produced. * * Honoured only when the terminal call is the ONLY call in the turn * and it did not fail. A model that asked for other work in the same * turn meant to see those results, and ending the turn would discard * answers it requested; that turn takes the ordinary path and the * reason is logged. A failed terminal call is not an answer either — * the error goes back to the model, which is the point of returning * errors to it at all. * * Off by default. The generic case is `structured_output`, which the * runtime has always settled on; this is that rule made available to * any tool. */ terminal?: boolean; isReadOnly?(input: TInput): boolean; isDestructive?(input: TInput): boolean; isConcurrencySafe?(input: TInput): boolean; /** * Opt-in ordering boundary in a direct model tool-call batch. Earlier * calls settle before this call starts; later calls wait for this call * to settle (including failed results). Defaults to false. Independent * calls between barriers retain their existing concurrency-safe behavior. * Nested dispatch is owned by its enclosing call, not separately queued; * mark the enclosing tool as a barrier to isolate its nested operations. * Deadlines still abandon uncooperative tools: settlement does not prove * their external effects have stopped, and does not imply success. */ executionBarrier?: boolean; /** * Where this tool came from, when it did not come from here. * * Absent means host-defined: this process, code the operator installed, * no untrusted party in the chain. Present means a connected server * supplied both the tool and its own description of what the tool does * — including whether it is read-only, which three separate gates were * treating as a fact rather than as the hint the wire calls it. * * See {@link isTrustedReadOnly}. This field exists so a gate can tell * the two apart; `isReadOnly` keeps reporting faithfully what the * server said, because the outbound re-export and the destructive * label shown to a human both need the server's own answer. */ provenance?: ToolProvenance; } export interface ToolProvenance { /** The connected server this tool came from, named as configured. */ readonly server: string; /** * The operator marked this server's read-only claims as trustworthy. * * Per server, never global: one switch meaning "trust annotations" * hands every connected server the same reach, which is the hole it * would be closing. Default false — an unmarked server's claim raises * the requirement and never lowers it. */ readonly readOnlyHintTrusted: boolean; } export type ToolPermission = 'file_read' | 'file_write' | 'shell_execute' | 'network_access' | 'env_access'; export interface LLMToolSchema { type: 'function'; function: { name: string; description: string; parameters: Record; }; } export type ToolAvailability = 'deferred' | 'active' | 'suspended'; export type ZodToJsonSchema = (schema: z.ZodType) => Record; export interface ToolTierDefinition { id: string; label: string; priority: number; description?: string; } export interface ToolTierConfig { tiers: ToolTierDefinition[]; guidanceTemplate?: (tiers: ToolTierDefinition[]) => string; labelInDescription?: boolean; } export interface ToolRegistryConfig { logger?: Logger; tierConfig?: ToolTierConfig; /** * Screens run against every tool result before anything downstream * reads it — the output budget, compaction, and the model itself are * all past this point. * * Absent means no screening, which is what shipped before this existed: * a connected server's text reached the model unexamined. See * {@link ToolResultGuardrailSpec}. */ resultGuardrails?: readonly ToolResultGuardrailSpec[]; } export interface ToolExecutionResult extends ToolResult { permissionDenied?: boolean; permissionMessage?: string; } /** * An input decoded exactly once by its owning tool registry. * * `input` is the detached, deeply frozen JSON review projection: * authorization, approval UI, probes and audit all inspect this value. The * registry privately retains a separate detached copy that * {@link ToolRegistryContract.executePrepared} gives the tool. Caller-owned * aliases therefore cannot change either side after preparation. A * preparation is registry-owned and cannot be executed by a different * registry or after that tool registration is replaced. */ export interface PreparedToolExecution { readonly toolName: string; readonly input: unknown; } /** Result of decoding a tool input at the execution boundary. */ export type ToolPreparationResult = { readonly success: true; readonly prepared: PreparedToolExecution; } | { readonly success: false; readonly result: ToolExecutionResult; }; /** * Full tool registry contract — registration, lookup, execution, prompt generation. * Concrete implementation: `ToolRegistry` in `registry/tool/execute.ts`. */ export interface ToolRegistryContract { register(id: string, tool: ToolDefinition): void; register(tool: ToolDefinition, initialState?: ToolAvailability): void; register(tools: ToolDefinition[], initialState?: ToolAvailability): void; unregister(id: string): boolean; clear(): void; get(name: string): ToolDefinition | undefined; getOrThrow(name: string): ToolDefinition; has(name: string): boolean; getAll(): ToolDefinition[]; listIds(): string[]; listNames(): string[]; getAvailability(name: string): ToolAvailability; activate(names: string[]): void; defer(names: string[]): void; suspendAll(): void; hasSuspended(): boolean; searchDeferred(query: string): ToolDefinition[]; /** Ranked active matches, when this registry supports active-tool discovery. */ searchActive?(query: string): ToolDefinition[]; getCallableTools(toolNames?: string[]): ToolDefinition[]; /** * Decode/transform an input once, before authorization or human review. * * The returned preparation is opaque registry authority. Implementations * must not run tool code here. They must detach both the executable value * and `prepared.input` from caller/schema aliases, and make the latter a * deeply immutable JSON projection of the exact value retained for * execution. Unsupported mutable/exotic graphs fail closed. */ prepareExecution(toolName: string, rawInput: unknown): ToolPreparationResult; /** Execute the exact value retained by `prepareExecution`, without parsing again. */ executePrepared(prepared: PreparedToolExecution, context: ToolContext): Promise; execute(toolName: string, rawInput: unknown, context: ToolContext): Promise; size(): number; toLLMTools(toolNames?: string[]): LLMToolSchema[]; toPromptSection(toolNames?: string[]): string; toTierGuidance(): string | null; assignTiers(mapping: Record): void; } export * from './repair.js'; export type { ToolCallView, ToolPresentation, ToolResultView, } from './presentation.js'; //# sourceMappingURL=index.d.ts.map