import { type ApprovalId, type Guard, type RiskLabel, type RunContext, type ToolCall, type ToolDescriptor, type ToolOutcome, type ToolRegistry, type VendoApprovalPart } from "@vendoai/core"; import type { UIMessage, UIMessageStreamWriter } from "ai"; import type { TurnTimings } from "./runtime.js"; /** An observer of one guarded call: invoked before the gate, returning the * finisher that receives the model-visible outcome. */ export type ToolCallHook = (call: ToolCall) => (outcome: ToolOutcome) => void; /** 03-agent §2 */ export interface ToolBridgeOptions { registry: ToolRegistry; ctx: RunContext; guard?: Guard; writer?: UIMessageStreamWriter; toolOutputCap?: number; gate?: (call: ToolCall) => ToolOutcome | undefined; /** Observes every guarded call. THREE independent call sites want this one slot * — composition's turn shape (harness-turn.ts), the away run record * (agents/away.ts), and the runtime's capability-miss detector — and a plain * assignment silently drops whoever wrote it first, which is how every composed * turn came to report zero tool calls. Fill it through * {@link mergeToolCallHooks}; never assign over a value that may already be * there. A hook that throws is logged and skipped: watching a call may never * fail, delay or reorder it. */ onCall?: ToolCallHook; /** Discovery-discipline 2026-07-25: a pre-guard short-circuit (the connect * gate). A non-undefined outcome means the call cannot run — needsApproval * skips the guard entirely (no approval minted; the ask would be answered * by a card for a call that can never execute) and the registry's execute, * wrapped by the same gate, returns the outcome on the tool channel. */ preflight?: (call: ToolCall, ctx: RunContext) => Promise; /** Per-TURN set of `:` keys that already rendered a * connect card, so one unconnected service costs the user one card no * matter how many of its tools the model called. Omit to disable deduping * (the away runner has no card surface). */ connectCards?: Set; /** This turn's collector, for the two phases only this file stands in: the * guard's evaluation (`previewApproval`) and the tool's own run * (`guardedCall`). They are disjoint — the preview decides, the dispatch runs * on that verdict — so neither is counted into the other and `modelMs`'s * subtraction stays honest. Unset, nothing is measured. */ timings?: TurnTimings; } /** * The ONE hook that runs all of them, in argument order, on both halves of a * call: every hook is opened before the gate, and every finisher it handed back * runs on the outcome. `undefined` arguments drop out, so a caller passes an * optional hook straight through. * * This exists so a caller that wants to observe calls ADDS itself to the * `onCall` slot instead of assigning over it. Assignment is how the turn's tool * counting disappeared: `{ ...bridge, onCall: mine }` type-checks, reads as * additive, and silently throws away the hook the spread just copied in. * * A hook that throws — on the way in or on the outcome — is logged and skipped: * it neither fails the tool call nor stops any other hook. */ export declare function mergeToolCallHooks(...hooks: Array): ToolCallHook; /** The flat §16 approval part shared by every consent surface (native * needsApproval, pending-approval outcomes). */ export declare function approvalPart(toolCallId: string, risk: RiskLabel, approvalId: ApprovalId, invalidatedGrant?: VendoApprovalPart["invalidatedGrant"], /** The ask's OWN descriptor, when the tool parked an ask about something * other than the call the model made. It both GRADES the card and gives it * its authored words — `risk` above belongs to the calling tool, which for a * build is `vendo_make`'s "read". */ asked?: ToolDescriptor): VendoApprovalPart; /** * Execute ONE guarded call with every shipped rail attached: the view channel * (a `vendo_apps_*` tree OpenSurface, plus the VENDO_VIEW_STREAM bridge that * streams partial views mid-build), the inline connect card with its per-turn * dedupe, the build-failed banner, the knowledge citations part, and * `toolOutputCap`. * * Exported so the harness runtime's `turn.tools.call` runs THIS path rather than * a second one. A harness that generates an app has to render, and the * commit-gated workspace render seam is additive for file-truth harnesses — not a * replacement for this channel. Both coexist. */ export declare function guardedCall(descriptor: ToolDescriptor, options: ToolBridgeOptions, input: unknown, { toolCallId }: { toolCallId: string; }): Promise; /** * The approval PREVIEW: the preflight short-circuit plus `previewCheck`, raising * today's `data-vendo-approval` part — `invalidatedGrant` and all — when the guard * wants a human. * * Exported for the same reason as {@link guardedCall}, and it is exactly why * `previewCheck` exists: this is the ONE guard evaluation of a call the caller * is about to dispatch. The verdict computed here is what the dispatching call * runs on (guard.ts, `#decideForExecution`), so the write-budget/call-rate * breakers are charged once and the judge is asked once. The harness runtime * previews here, awaits the tap when the answer is "ask", then executes ONCE. */ export declare function previewApproval(descriptor: ToolDescriptor, options: ToolBridgeOptions, input: unknown, { toolCallId }: { toolCallId: string; }, /** Receives the guard's approval id when the verdict is "ask" (undefined when * the guard itself threw and we fail closed) — a second output beside the * boolean verdict, which is the only thing a caller can wait on. */ onAsk?: (approvalId: ApprovalId | undefined) => void): Promise; //# sourceMappingURL=tool-bridge.d.ts.map