import { PluginsSettings, ProviderRoutingSettings, AgentDefinition, BudgetTracker, CustomTool } from '@theokit/sdk'; import { RetryOptions } from '@theokit/sdk/retry'; import { C as CompiledAgentOptions, a as CompiledTool } from './agent-compiler-BMpuJFAI.js'; import { TheokitAgentError } from '@theokit/sdk/errors'; import { z } from 'zod'; import { b as MainLoopMeta } from './types-DdltPCj2.js'; import { WireChunk } from '@theokit/presenter/wire'; import { H as HookHandlers } from './hook-handlers-Cw2FsnE5.js'; /** * Typed discriminated union for agent SSE stream events. * * Every event has a `type` field for discrimination. * Clients narrow via `if (event.type === 'text_delta') event.content`. */ /** Partial text content from the LLM. */ interface TextDeltaEvent { type: 'text_delta'; content: string; } /** Agent started a tool call. */ interface ToolCallEvent { type: 'tool_call'; callId: string; toolName: string; input: unknown; } /** * Tool-call arguments streaming in incrementally (theokit-sdk#70). Emitted repeatedly as the * model generates a tool call's args, BEFORE they are committed. The same `callId` correlates to * the later `tool_call` (args committed) and `tool_result`. Consumers opt in to render tool-input * progressively; those that don't simply ignore this variant (it never replaces `tool_call`). */ interface PartialToolCallEvent { type: 'partial_tool_call'; callId: string; toolName: string; input: unknown; } /** Tool execution completed. */ interface ToolResultEvent { type: 'tool_result'; callId: string; toolName: string; output: string; durationMs: number; isError: boolean; } /** Extended thinking / reasoning (when model supports it). */ interface ThinkingEvent { type: 'thinking'; content: string; } /** Agent loop iteration. */ interface IterationEvent { type: 'iteration'; step: number; totalSteps: number | null; } /** Human approval required before proceeding. */ interface ApprovalRequiredEvent { type: 'approval_required'; callId: string; toolName: string; question: string; input?: unknown; callbackUrl: string; timeoutMs: number; /** M20 — JSON-schema descriptor of the custom payload the approver may attach (optional). */ payloadSchema?: Record; } /** * The run is blocked awaiting user input or approval — theokit#141. * * The SDK's low-fidelity pause signal (`SDKRequestMessage`), distinct from {@link * ApprovalRequiredEvent}. The latter is the framework's own, produced by `createHitlPlugin`, and * carries everything an approval UI needs to be actionable. This one carries only the request id, * because that is all the SDK provides — and it matters most exactly where the plugin is absent * (the ACP/serving path of theokit#139), which is where a dropped pause reads to the user as a * hang with no explanation. * * A consumer that cannot resolve the request should still SHOW that the run is waiting. Silence is * the one response that is always wrong. */ interface InputRequestedEvent { type: 'input_requested'; requestId: string; } /** Task-level milestone or summary — theokit#141. Both fields are optional at the source. */ interface TaskProgressEvent { type: 'task_progress'; status?: string; text?: string; } /** * Live output from a shell command the SDK is running — theokit#141. * * `event` is passed through opaquely because the SDK types it as `Record`. The * layer deliberately does not interpret or render it: guessing a shape here would be a second, * weaker oracle over a payload whose real contract lives upstream. */ interface ShellOutputEvent { type: 'shell_output'; event: Record; } /** Agent encountered an error. */ interface ErrorEvent { type: 'error'; code: string; message: string; retryable: boolean; } /** * Why a run stopped short of finishing on its own — theokit#379. * * ## Why it is an enum and not a `truncated: boolean` * * The two members demand OPPOSITE reactions from the caller, and a boolean cannot carry the * difference: * * - `'step_limit'` — the loop ran out of tool-calling turns while the model still wanted more. * The work is unfinished but progressing; re-sending is the sane continuation. * - `'no_progress'` — the doom-loop guard stopped the model repeating IDENTICAL tool calls. The * SDK's own wording: "a controlled stop, NOT a truncation to re-send". Re-sending repeats the * loop that was just cut. * * A caller that reads `truncated: true` and re-sends does the right thing in the first case and * feeds the doom loop in the second. * * ## Why these spellings * * Both sides of the boundary had already agreed on them before this field existed: the framework's * own `LoopFinishReason` (`../loop/loop-strategy.ts`) spells the outer-loop terminals `step_limit` * and `no_progress`, and so does the SDK's continuation driver * (`RunToCompletionResult.terminal: "done" | "step_limit" | "no_progress"`). Inventing a third * vocabulary for the same two outcomes was the only way to get this wrong. * * ## Why there is no `'finished'` member * * A clean run carries NO `stopReason` at all — absence is the finished case. Stamping every * ordinary turn with a new field would change what every existing consumer receives in order to * describe the one case that was already correct. A consumer that has never heard of `stopReason` * therefore keeps receiving byte-identical frames for every run that finishes on its own, and for a * truncated one it receives one extra optional key it ignores — the same degradation the ai-sdk * `finish` chunk already allows, since its `messageMetadata` is `unknown`. */ type AgentStopReason = 'step_limit' | 'no_progress'; /** Agent completed with a final result. */ interface DoneEvent { type: 'done'; result: string; usage: { inputTokens: number; outputTokens: number; totalTokens: number; /** V4-O: SDK reasoning/cache token buckets (0 when the provider omits them). */ reasoningTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number; }; durationMs: number; /** * Total cost in USD for this agent run, as the SDK priced it (`CostBreakdown.amountUsd`). * * Absent when the run's price is not known (the SDK has no pricing data for the model); 0 means * the SDK priced the run at zero, for example a subscription-included route. A consumer summing * runs must not read an absent cost as 0 (usetheokit/theokit#969). */ cost?: number; /** * theokit#379 — why the run stopped, when it did NOT stop because the agent was done. * * ABSENT on a clean finish. Present only when the SDK reports that the run was cut: the terminal * frame is a `done` either way (a reached ceiling is not an error — see `sdk-adapter.ts`'s * `applyStepCeiling`), so without this field a cut run and a finished one are the same event. * * Read from `RunResult.stoppedByDoomLoop` / `RunResult.stoppedAtIterationLimit` at the adapter * boundary. See {@link AgentStopReason} for why the caller needs to tell the two apart. */ stopReason?: AgentStopReason; /** * The model this turn actually ran on — the EFFECTIVE id, not the declared one. * * It is resolved at the one place that knows: `createSdkAgentStream`, where * `overrides.model ?? compiled.model ?? 'openai/gpt-4o-mini'` is decided. A consumer reading * `CompiledAgentOptions.model` instead would be wrong twice — it misses a per-run override, and * it reads `undefined` for an agent that declared no model and still ran one. * * Tokens are on this event already, and tokens alone convert to no cost: price is per model. * This is the other half of that question, and the reason it travels rather than staying inside * the adapter (usetheokit/theokit#368's fifth criterion). * * Optional so a producer that predates it — or one building a `done` from a source with no model * to report — degrades to absence rather than to a fabricated id. */ model?: string; } /** * Per-turn usage the translator attaches to the ai-sdk `finish` chunk's `messageMetadata`, so it * lands on the reconstructed assistant `UIMessage.metadata` on the client (via `readUIMessageStream`) * with NO extra header/store wiring. It is the seam that lets a surface (a TUI status bar, a web * cost meter) show real tokens/cost for the turn it just streamed. Mirrors `DoneEvent`'s usage/cost * (the authoritative totals the SDK reports at turn end) plus the wall-clock `durationMs`. */ interface AgentTurnMetadata { usage: DoneEvent['usage']; /** Total cost in USD for the turn (present iff the SDK reported it). */ cost?: number; durationMs: number; /** * theokit#379 — mirrors {@link DoneEvent.stopReason} so a surface that only ever sees the wire * (a web client reading `UIMessage.metadata`, the observability translator reading the `finish` * chunk) can tell a cut turn from a finished one without a second transport. Absent on a clean * finish, exactly as on `DoneEvent`. */ stopReason?: AgentStopReason; /** * Mirrors {@link DoneEvent.model} — the model the turn ran on, carried to whoever only ever sees * the wire. The observability translator is the caller that needs it: it reads the `finish` * chunk and records the model beside the token counts, which is what makes a cost answerable * from a trace at all. Absent when the producer reported none. */ model?: string; } /** Agent run started. */ interface RunStartedEvent { type: 'run_started'; runId: string; agentName: string; model?: string; } /** Artifact generation started (code, document, diagram). */ interface ArtifactStartEvent { type: 'artifact_start'; artifactId: string; mimeType: string; filename?: string; metadata?: Record; } /** Artifact content chunk (streamable artifacts). */ interface ArtifactChunkEvent { type: 'artifact_chunk'; artifactId: string; chunk: string; isLast: boolean; } /** Real-time state update from @Observable channels. */ interface StateUpdateEvent { type: 'state_update'; channel: string; data: unknown; } /** Checkpoint saved (resumable agents). */ interface CheckpointSavedEvent { type: 'checkpoint_saved'; checkpointId: string; step: number; resumeToken: string; } /** File edit produced by a code assistant tool. */ interface FileEditEvent { type: 'file_edit'; file: string; format: 'search-replace' | 'unified-diff' | 'full-file' | 'line-range'; search?: string; replace?: string; content?: string; diff?: string; startLine?: number; endLine?: number; } /** Discriminated union of all agent stream events. */ type AgentStreamEvent = RunStartedEvent | TextDeltaEvent | ToolCallEvent | PartialToolCallEvent | ToolResultEvent | ThinkingEvent | IterationEvent | ApprovalRequiredEvent | InputRequestedEvent | TaskProgressEvent | ShellOutputEvent | ArtifactStartEvent | ArtifactChunkEvent | StateUpdateEvent | CheckpointSavedEvent | FileEditEvent | ErrorEvent | DoneEvent; /** Type guard helpers. */ declare function isTextDelta(e: AgentStreamEvent): e is TextDeltaEvent; declare function isToolCall(e: AgentStreamEvent): e is ToolCallEvent; declare function isPartialToolCall(e: AgentStreamEvent): e is PartialToolCallEvent; declare function isToolResult(e: AgentStreamEvent): e is ToolResultEvent; declare function isDone(e: AgentStreamEvent): e is DoneEvent; declare function isError(e: AgentStreamEvent): e is ErrorEvent; declare function isApprovalRequired(e: AgentStreamEvent): e is ApprovalRequiredEvent; /** * The failure, as chunks the protocol accepts — theokit#161 (B). * * ## The measured defect * * M95 put the `code` INSIDE the error chunk (`{type:'error', errorText, errorCode}`), for a good * reason: without it a consumer that must DISTINGUISH the failure has only the message, and matching * on error text is the heuristic this ecosystem already paid for once — M93 classified failures as * transient by regex over the message and read `ECONNREFUSED ...:443` as definitive because the PORT * matched its "4xx" pattern. * * But the `error` variant of ai's `uiMessageChunkSchema` is STRICT: any extra key invalidates it. * Measured against `ai@7.0.14` — `{type:'error',errorText:'boom'}` validates; the same chunk with * `errorCode` does NOT. So since M95 this path emitted a chunk outside the protocol it claims to * speak, and a consumer that validates would reject the whole frame — losing the text along with it. * * Nobody saw it because the test written to catch exactly this stopped BEFORE validating: its * precondition (`toContainEqual({type:'error',errorText:'boom'})`) went stale when `errorCode` * appeared, `expect` threw, and the validation loop never ran. * * ## Why a data part, and not one of the easy exits * * Dropping the code returns the consumer to the text matching M95 removed. Embedding it in * `errorText` is the same thing under another name. The protocol already has the right place — a * data part — and this file already uses one for `data-checkpoint`. Measured: it validates. * * `transient: true` because an error code is turn diagnostics, not message content: the SDK does not * persist it in history, which is exactly what we want. * * The data part comes BEFORE the error chunk deliberately: a sequential consumer already holds the * code when the failure arrives. In the other order it would have to handle the error first and only * then learn which one it was. */ /** * What the browser is told a failure was. * * Masked by default (usetheokit/theokit#390): the server's raw text — a driver's message, an HTTP * client's, a filesystem call's — reached the client verbatim, and `ai@7` on the same protocol * masks by default for the reason its own comment gives, "prevent leaking server error details to * the client by default". * * The full text is NOT lost: it reaches the server's logs and the `agent.run` span, and it reaches * this hook. What stops is it reaching the browser unless a host decides otherwise. */ type MaskError = (error: { message: string; code?: string; }) => string; declare function presentUIMessageStream(events: AsyncIterable, opts: { textId: string; onError?: MaskError; }): AsyncGenerator; /** Minimal event shape matching SDK's SDKMessage discriminated union. */ interface StreamEvent { type: string; [key: string]: unknown; } /** * Create a Web Standard Response that streams the agent's turn as UIMessage wire chunks. * * The `event:` line carries the chunk's own type. It is informational — `parseWireStream` reads * only `data:` lines, per WHATWG SSE — and kept because an `EventSource` consumer can dispatch on * it. * * @param eventStream - the framework's own run events * @param opts.onError - what a consumer is told a failure was; masked by default (#390) */ declare function streamAgentResponse(eventStream: AsyncIterable, opts?: { onError?: Parameters[1]['onError']; }): Response; /** * LoopStrategy — the per-round terminal-decision contract that gives runtime to * `@MainLoop({ strategy })`. * * V4-A proved `@MainLoop`'s `strategy` field was metadata-only (declared + * compiled, never executed). This module is the foundation Phase 2's * `runReflectiveLoop` branches on. Modeled on Mastra's `agentic-loop`/`stopWhen` * (inverted) + `maxSteps` ceiling — NOT Spring's per-call Advisor (plan ADR D1). * Config is Zod-validated so an invalid `maxIterations` fails fast at resolve * time, never as a silent infinite loop at runtime (plan ADR D3). */ /** Why a single round ended (or, for the V4-D terminals, why the whole loop ended). */ type LoopFinishReason = 'tool-calls' | 'stop' | 'length' | 'error' | 'step_limit' | 'no_progress'; /** Value object describing one round's result. */ interface LoopOutcome { /** Why the round ended (the continuation signal). */ readonly finishReason: LoopFinishReason; /** 1-indexed round number that just completed. */ readonly round: number; /** Tool calls executed during the round (V4-N: `id` correlates the call to its result). */ readonly toolCalls: readonly { id: string; name: string; input: unknown; output: string; }[]; /** Accumulated assistant text for the round. */ readonly responseText: string; } /** The terminal-decision contract. `shouldContinue` is the inverted `stopWhen`. */ interface LoopStrategy { /** * The strategy name, surfaced in `finalize`/logs. M54 relaxes this from * `MainLoopMeta['strategy']` to `string` so a caller-injected `.loopStrategy(custom)` can name * itself freely — the three built-ins keep their names, but the seam is no longer union-locked. * The INTERNAL resolution (`loopStrategyConfigSchema`) still validates the three built-in names * via `z.enum`; a custom never passes through it (it enters by the seam, not by name). */ readonly name: string; /** Hard ceiling on rounds — guarantees termination (enforced by the runner since M54). */ readonly maxIterations: number; /** True ⇒ re-enter for another round; false ⇒ terminate. The runner caps it at `maxIterations`. */ shouldContinue(outcome: LoopOutcome): boolean; } /** Default round ceiling when `@MainLoop` declares no `maxIterations` (EC-3: finite, never Infinity). */ declare const DEFAULT_MAX_ITERATIONS = 8; /** Serializable config for a LoopStrategy. SSoT per type-safety.md (ADR D3). */ declare const loopStrategyConfigSchema: z.ZodObject<{ name: z.ZodEnum<{ "simple-chat": "simple-chat"; "plan-act-reflect": "plan-act-reflect"; react: "react"; }>; maxIterations: z.ZodNumber; }, z.core.$strip>; type LoopStrategyConfig = z.infer; /** * Map a `@MainLoop` strategy + ceiling to a concrete {@link LoopStrategy}. * * - `simple-chat` ⇒ exactly one round (`shouldContinue` always false). * - `react` ⇒ continue while the round ended on `tool-calls` AND the ceiling has not been reached * (`round < maxIterations`) — the reflection is `noop` (no feedback), so the strategy IS the gate. * - `plan-act-reflect` ⇒ DEFER continuation to the reflection (V4-S): `shouldContinue` is * `round < maxIterations`, so the loop continues iff the (custom) `ReflectionStrategy` returns * `continue: true` within the ceiling — letting a reflection extend even a terminal (`stop`) * round (e.g. "you answered without editing — make the edit now"). Backward-compatible with the * default `ladderReflectionStrategy`, which itself returns `continue: true` only on `tool-calls`, * so the observable behavior with the shipped ladder is unchanged. * * Throws (Zod) when `maxIterations < 1` — fail fast, never a silent infinite loop. */ declare function resolveLoopStrategy(strategy: string, maxIterations?: number): LoopStrategy; interface DelegationResult { response: string; toolCalls: { id: string; name: string; input: unknown; output: string; }[]; /** * USD the run's priced rounds cost. A round the SDK could not price adds nothing here, so read it * with {@link costUnknown}: when that is set, this is the known spend and not the run's cost. */ cost: number; /** * Present, and `true`, when at least one round reported no finite cost (B-409): the run cost * {@link cost} plus an amount nobody knows. Absent on a run whose every round was priced. A * consumer summing runs must not read such a `cost` as the run's price (#969). */ costUnknown?: true; tokens: number; /** V4-N: split token usage accumulated across rounds (`tokens` stays as the total). Absent for the single-shot path. */ tokensInput?: number; tokensOutput?: number; /** V4-O: reasoning/cache token buckets accumulated across rounds (0 on any loop-driven run; the loop seeds them). Optional for type compat. */ reasoningTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number; /** Rounds the reflective loop ran (set by `runReflectiveLoop`; absent for the single-shot path). */ rounds?: number; /** * The loop's terminal reason (set by `runReflectiveLoop`): `'stop'`/`'length'` natural end, * `'step_limit'` (hit maxIterations), `'no_progress'` (stuck). Absent for the single-shot path. (V4-D) */ finishReason?: LoopFinishReason; } /** * Why a run stopped BEFORE a round rather than after one. Without it the error reports an * overspend (`$actual > $limit`), which is only true once the spend passed the limit; a refusal * stops while the spend is still under it, so it needs wording that does not claim otherwise. */ type BudgetRefusal = /** The next round's projected cost (the last completed round's), which would pass the limit. */ { readonly projectedRoundCost: number; } /** The last round's cost is not known, so the limit cannot be enforced for the next one. */ | { readonly costUnknown: true; } /** The limit is 0 or below, so no round fits: the run stops before its first one. */ | { readonly nothingToSpend: true; }; /** * DELEGATION budget exceeded — the dollar cost of a delegated agent. * * ## Why the name changed in M91 * * It was called `BudgetExceededError` and **shadowed** the SDK class of the same name, which belongs * to another domain: context-WINDOW budget (`budgetName`/`window`/`mode`) against DELEGATION budget * (`agentName`/`actualCost`). Since the consumer holds an unbreakable rule never to import * `@theokit/sdk` directly, it **could never reach the SDK's** — and an `instanceof` against this * barrel silently matched the wrong domain. * * It is the failure mode M73 documented in `auth-parity.test.ts`: when two classes compete for the * same name, no behavioural test goes red — only an identity `toBe` catches it. * * `subpath-coverage.test.ts` recorded the collision as a `gap` on `./errors`, with the reason written * down and the acknowledgement that renaming was breaking and out of M78's scope. M91 paid the bill. */ /** * M80 — extends {@link TheokitAgentError}, not plain `Error`. * * `isTransientError` is defined over `TheokitAgentError`, so a class outside that hierarchy is * INVISIBLE to it and the only recourse left to a consumer is matching on message text. `code` is * stable across a rename; `isRetryable` is DECLARED, because a default would be a retry policy * nobody chose. */ declare class DelegationBudgetExceededError extends TheokitAgentError { readonly agentName: string; readonly actualCost: number; readonly budgetLimit: number; readonly name = "DelegationBudgetExceededError"; /** The cost the refused round was projected at; absent when the spend already passed the limit. */ readonly projectedRoundCost: number | undefined; constructor(agentName: string, actualCost: number, budgetLimit: number, refusal?: BudgetRefusal); } /** * A run with a USD ceiling stopped because the cost of its last round is not known (B-409). * * A model with no price reports no cost, and reading that as $0 would let the run keep spending * against a limit it can no longer check, so the run stops instead (fail closed). `actualCost` is * the spend that IS known. A subclass so it can be caught on its own while every existing * `instanceof DelegationBudgetExceededError` and the `DELEGATION_BUDGET_EXCEEDED` code still match; * `name` is inherited because the parent declares it as a string literal, which a subclass cannot * override with another. */ declare class DelegationBudgetCostUnknownError extends DelegationBudgetExceededError { constructor(agentName: string, knownSpend: number, budgetLimit: number); } /** * @deprecated Use {@link DelegationBudgetExceededError}. The alias is kept for one major so anyone * catching by the old name is not broken; it is the **same** class, not a copy — `instanceof` still * holds in both directions, and a referential-identity test (`toBe`) pins that. * * knip reports the value/type pair as a duplicate export, which is exactly what a deprecation * alias is; `rules.duplicates` is "warn" for that reason. Removing it is the breaking change it * exists to avoid. */ declare const BudgetExceededError: typeof DelegationBudgetExceededError; /** @deprecated Use {@link DelegationBudgetExceededError}. */ type BudgetExceededError = DelegationBudgetExceededError; /** * M80 — extends {@link TheokitAgentError}, not plain `Error`. * * `isTransientError` is defined over `TheokitAgentError`, so a class outside that hierarchy is * INVISIBLE to it and the only recourse left to a consumer is matching on message text. `code` is * stable across a rename; `isRetryable` is DECLARED, because a default would be a retry policy * nobody chose. */ declare class DelegationError extends TheokitAgentError { readonly agentName: string; readonly cause: unknown; readonly name = "DelegationError"; constructor(agentName: string, cause: unknown); } /** * ReflectionStrategy — composes INTO the loop between rounds (the loop calls * `reflect()` then `LoopStrategy.shouldContinue()`). * * `'plan-act-reflect'` resolves to the `'ladder'` default shipped here; a custom * strategy may be supplied (OCP — plan Drawback #2). `reflect()` is pure (no * I/O, no LLM) — it inspects the round's outcome and returns feedback to inject * into the next round's prompt plus a `continue` hint. The hard round ceiling * lives in `LoopStrategy.shouldContinue` (maxIterations), NOT here. */ /** Result of reflecting on a completed round. */ interface ReflectionResult { /** Optional text prepended to the next round's prompt. */ readonly feedback?: string; /** Hint: should the loop reflect-and-continue? (the ceiling still bounds it) */ readonly continue: boolean; } /** * V4-K: a per-run mutable scratch bag threaded to every `reflect()` call within one * `runReflectiveLoopStream` run, so a STATEFUL strategy can accumulate cumulative state * (counters, one-shot flags) across rounds. The framework owns the lifecycle (creates * one per run, passes the SAME ref each round) but writes NOTHING into it — the strategy * owns the contents. Generic by design: the framework cannot know app semantics (e.g. * what an "edit" is). A consumer narrows it locally (`ctx as MyState`). */ type ReflectionContext = Record; /** Pluggable between-round reflection. Pure — never performs I/O. */ interface ReflectionStrategy { /** Strategy identifier (e.g. `'ladder'`). */ readonly name: string; /** * Inspect the round outcome; return feedback + continue hint. `ctx` (V4-K) is the * per-run mutable scratch — optional for backward compatibility (shipped strategies * ignore it; stateful custom strategies accumulate in it across rounds). */ reflect(outcome: LoopOutcome, ctx?: ReflectionContext): ReflectionResult; } /** Serializable config for a ReflectionStrategy. SSoT per type-safety.md (ADR D3). */ declare const reflectionStrategyConfigSchema: z.ZodObject<{ name: z.ZodString; }, z.core.$strip>; type ReflectionStrategyConfig = z.infer; /** * The default `'ladder'` reflection (what `'plan-act-reflect'` resolves to). * * Continues with bounded templated feedback while the round ended on * `tool-calls`; terminates on any terminal `finishReason` (`stop`/`error`/ * `length`). The feedback is a short fixed template (bounded — EC-5; context * growth is the SDK's responsibility, not the loop's). */ declare const ladderReflectionStrategy: ReflectionStrategy; /** * No-op reflection for `'react'` (multi-round WITHOUT reflection feedback). * * Returns `{ continue: true }` (no feedback) so the round-continuation decision * is delegated entirely to `LoopStrategy.shouldContinue` (i.e. `react` loops * while `finishReason === 'tool-calls'` under the ceiling). NOTE: the plan's * pseudo-code (Files-to-edit) sketched `{ continue: false }`, which would make * `react` single-shot — that contradicts the plan's own Deep Dives ("`react` * still multi-rounds while finishReason==='tool-calls'"); `continue: true` is * the behavior the resolved react LoopStrategy requires. */ declare const noopReflectionStrategy: ReflectionStrategy; /** * runReflectiveLoop — the multi-round reflective driver that gives `@MainLoop` * its runtime (closes the metadata-only gap, V4-A / plan ADR D2). * * Per round it consumes ONE SDK stream turn (the model call stays in the SDK — * the factory is `createSdkAgentStream(...)`, injected for testability), derives * a {@link LoopOutcome}, asks the {@link ReflectionStrategy} for feedback, then * the {@link LoopStrategy} whether to continue. Bounded by `maxIterations` * (forced terminal at the ceiling — never an infinite loop). The bridge owns the * loop; the SDK owns the model call (sdk-runtime.md / ADR 0031 — no second runtime). * * `runReflectiveLoop` is INTERNAL (not re-exported from the package barrel, * Drawback #4) — consumed by `delegate()` (T2.2) and `AgentRunner` (T3.1). */ /** One SDK stream turn: `createSdkAgentStream(...)` returns this shape. */ /** * Opens one round's SDK stream. `opts.disableTools` (step-cap force-close) asks the factory to * gate tools OFF for THIS round (the SDK adapter maps it to `tool_choice:"none"` at send-time, so a * cached agent — whose tools can't be un-registered — is still forced to a text summary). Optional + * ignored by injected test factories ⇒ backward-compatible. */ type RoundStreamFactory = (message: string, sessionId: string, opts?: { disableTools?: boolean; }) => AsyncIterable; /** * Multi-agent orchestration runtime. * * Provides `delegate()` — a function that lets a parent agent invoke a * sub-agent and receive its result. Handles budget clamping (D4), tool sharing, * toolbox auto-instantiation (EC-1), and routes the resolved `@MainLoop` strategy * through `runReflectiveLoop` — the SAME driver `AgentRunner.run()` uses, so both * on-ramps are one runtime (ADR D4). `simple-chat` ⇒ `shouldContinue:()=>false` * ⇒ exactly one round; `react`/`plan-act-reflect` ⇒ multi-round reflective loop. */ interface DelegateOptions { /** * M81 — wall-clock cap for the delegation, in milliseconds. Absent ⇒ no clock cap. * * A DIFFERENT guard from `budget`: money is spent by work that progresses, and a delegation that * HANGS burns clock without spending a cent. A consumer wrote its own timeout race with its own * typed error because this was missing. Exceeding it raises `DelegationTimeoutError`, which — * unlike the budget errors — is marked retryable, because a hang is often transient. */ readonly timeoutMs?: number; /** * Max USD for this sub-agent call. `NaN` is refused with `DelegationError`; 0 or below stops the * call before any round with `DelegationBudgetExceededError`; `Infinity` is no ceiling. */ budget?: number; /** * Parent's remaining budget (for clamping), read on the same terms as `budget`: a parent that * has spent its ceiling (0 or below) ends the call as budget exceeded before any round. */ parentBudgetRemaining?: number; /** Parent's tools (for sharing — sub-agent inherits these). */ parentTools?: CompiledTool[]; /** * Parent's hooks. The member inherits them, and inheritance may only TIGHTEN: both * `pre_tool_call` gates run and the first refusal wins, so a member cannot widen what the parent * refused. * * Symmetric with `parentTools` on purpose — `delegate` already carried the parent's tools and its * budget into the member's run and left its AUTHORITY behind, which is how a squad member could * call a tool the supervisor had vetoed. The failure was silent: nothing threw, and the member's * own suite passed because the member never declared the gate. * * To scope a member differently, pass its handlers on `spec.compiled.hooks` — they compose with * these rather than replacing them, because a widening that a call site cannot see is the state * this option exists to remove. */ parentHooks?: HookHandlers; /** * LLM API key (inherited from parent). * * `null` says the provider takes NO credential — a model on the developer's own machine * (usetheokit/theokit#423). It is a distinct value from `''` on purpose: an empty string is what * an unset environment variable produces, and treating the two alike is how a typo becomes an * unauthenticated run. `undefined` still means the caller supplied nothing and is still refused. */ apiKey?: string | null; /** Session ID override (default: crypto.randomUUID for isolation). */ sessionId?: string; /** Cancellation — aborts stop the reflective loop from re-entering. */ signal?: AbortSignal; /** Per-run model override (`?? SubAgent @Agent model`). */ model?: string; /** * Per-run working directory → `Agent.create({ local: { cwd } })`. * * B-073 — if you point this at a git worktree you created, the sub-agent runs in a checkout with * none of the repository's gitignored files: no `.env`, no local config, no credentials. This * layer does not copy them, and it has no `.worktreeinclude` — the reference's one-file answer to * the same problem — because it never creates a worktree in the first place (measured with * controls: zero `git worktree` calls, zero `isolation` options; held falsifiable by * `tests/unit/this-layer-creates-no-worktree.test.ts`, which goes red the day that changes). * * Stated here rather than left to be discovered, because the failure does not look like a missing * file — it looks like a broken agent. */ cwd?: string; /** Per-run plugins (e.g. a read-only permission gate for an explore sub-agent). */ plugins?: PluginsSettings; /** Per-run provider routing (e.g. OpenRouter). */ providers?: ProviderRoutingSettings; /** Per-run sub-agent definitions. */ agents?: Record; /** Per-run SDK budget tracker (inner tool-loop cap). */ budgetTracker?: BudgetTracker; /** Per-run pre-built SDK tools forwarded raw (V4-Q). */ sdkTools?: readonly CustomTool[]; /** Per-round transient retry (V4-P). */ retry?: RetryOptions; /** Custom between-round reflection (default: ladder for `plan-act-reflect`, else noop). */ reflection?: ReflectionStrategy; /** Per-run loop-ceiling override (`?? SubAgent @MainLoop maxIterations`). */ maxIterations?: number; /** * Called BEFORE the sub-agent runs. Returns the input the sub-agent will receive — return * `ctx.input` unchanged, or a rewritten string (e.g. inject a persona). A transform, not a veto. */ onDelegationStart?: (ctx: { subAgent: string; input: string; }) => string | Promise; /** * Called AFTER the sub-agent completes. Returns the result the supervisor sees — return * `ctx.result` unchanged, or a transformed one (e.g. redact, score, re-wrap). */ onDelegationComplete?: (ctx: { subAgent: string; result: DelegationResult; }) => DelegationResult | Promise; /** * Injected stream factory (parity with `AgentRunnerRunOptions.streamFactory`) — drives the loop * directly instead of the SDK adapter (tests / custom transport). Absent ⇒ `createSdkAgentStream`. */ streamFactory?: RoundStreamFactory; } /** * Delegate a task to a sub-agent and collect its result. * * - Budget clamping: `min(parentBudgetRemaining, budget)` (D4) * - Tool sharing: parent tools merged with sub-agent tools (sub wins on collision) * - Toolbox auto-instantiation: sub-agent toolboxes instantiated without DI (EC-1) * - Session isolation: each delegation gets a unique session ID (EC-4) * - `@MainLoop` strategy runtime: routes through `runReflectiveLoop` (the same loop * `AgentRunner.run` uses — one runtime for both on-ramps, ADR D4). The runtime * metric (`THEO_AGENT_MAINLOOP_RUNTIME_APPLIED`) + typed-error + cumulative budget * all live in the shared driver, so they fire identically on both paths. */ /** * What `delegate` needs from a sub-agent: a name and already-compiled options (built by * `applyCapabilities`). Compatible with {@link AgentRunnerSpec} — one spec drives both on-ramps. */ interface SubAgentSpec { readonly name: string; readonly compiled: CompiledAgentOptions; /** Loop strategy (`@MainLoop({ strategy })`); absent ⇒ the same `'simple-chat'` default. */ readonly strategy?: MainLoopMeta['strategy']; /** Loop ceiling (`@MainLoop({ maxIterations })`); a per-run override still wins. */ readonly maxIterations?: number; } declare function delegate(spec: SubAgentSpec, message: string, opts?: DelegateOptions): Promise; /** * M25 (theokit-ai-first) — background delegation + task-completion scoring. * * Two THIN wrappers over the M12 `delegate` (ADR 0038/0040): no new orchestration engine, no second * loop, no new store. `delegateBackground` kicks off a sub-agent without blocking the supervisor and * hands back a handle to await later. `delegateWithScoring` runs `delegate`, scores the result with * an injected scorer, and re-delegates with the scorer's feedback until it passes or `maxRounds` is * hit. The `delegate` implementation is injectable (`delegateFn`) so this is testable without a * SubAgent class or an LLM — and so it NEVER re-implements the delegation runtime. */ /** * M81 — anything that can run a delegation. * * The reach gap this closes: both wrappers took a `SubAgentSpec` produced by the capability * compiler, so a consumer holding an SDK `SubAgent` or `Squad` could not feed them. That is why the * scoring loop — the layer's highest-value piece — had ZERO adoption in a product that runs an * explicit review pass: it was unreachable from the primitives that product actually holds. * * An SDK `SubAgent` or `Squad` satisfies this by having `run`. So does a test double, which is why * the loop is now testable without a compiler, a class or an LLM. */ interface DelegationPort { run(message: string): Promise; } /** What both wrappers accept: the compiled spec (unchanged) or the port (M81, additive). */ type DelegationTarget = SubAgentSpec | DelegationPort; /** The delegation primitive both wrappers drive. Defaults to the M12 {@link delegate}. */ type DelegateFn = (subAgent: SubAgentSpec, message: string, opts?: DelegateOptions) => Promise; /** A running background delegation the supervisor can await/poll later. */ interface BackgroundDelegation { /** Await the sub-agent's result (or rejection). Idempotent — returns the same settled promise. */ wait(): Promise; /** Whether the underlying delegation has resolved or rejected. */ settled(): boolean; } /** * Start a sub-agent WITHOUT blocking the supervisor. Returns immediately with a handle; the * supervisor keeps working and calls `wait()` when it needs the result. A thin async wrapper over * `delegate` — not a scheduler (Top-risk 1). Rejections are still observable via `wait()`. */ declare function delegateBackground(subAgent: DelegationTarget, message: string, opts?: DelegateOptions & { delegateFn?: DelegateFn; }): BackgroundDelegation; /** A scorer's verdict on a sub-agent result. `feedback` is fed back into the next round on failure. */ interface ScoreVerdict { pass: boolean; /** Optional numeric score (0..1) for logging/telemetry. */ score?: number; /** Guidance appended to the next delegation when `pass` is false. */ feedback?: string; } /** Scores a sub-agent result. Opt-in (Top-risk 2) — the caller supplies it, and pays its cost. */ type Scorer = (result: DelegationResult) => ScoreVerdict | Promise; /** The outcome of a scored delegation: the final result plus the per-round verdict trail. */ interface ScoredDelegation { result: DelegationResult; /** Rounds actually run (1..maxRounds). */ rounds: number; /** Whether the final round passed the scorer. */ passed: boolean; /** The verdict from each round, in order. */ verdicts: ScoreVerdict[]; } /** * Run `delegate`, score the result, and re-delegate with the scorer's feedback until it passes or * `maxRounds` is reached. Each round is ONE `delegate` call — no second loop, no new store. Returns * the final result (passing, or the last attempt) with the per-round verdict trail. */ declare function delegateWithScoring(subAgent: DelegationTarget, message: string, opts: DelegateOptions & { scorer: Scorer; maxRounds?: number; delegateFn?: DelegateFn; feedbackTemplate?: (message: string, feedback: string) => string; }): Promise; export { loopStrategyConfigSchema as $, type AgentStopReason as A, type BackgroundDelegation as B, type CheckpointSavedEvent as C, type DelegationTarget as D, type ErrorEvent as E, type FileEditEvent as F, type StateUpdateEvent as G, type ThinkingEvent as H, type IterationEvent as I, type ToolCallEvent as J, type ToolResultEvent as K, type LoopStrategy as L, delegate as M, delegateBackground as N, delegateWithScoring as O, type PartialToolCallEvent as P, isApprovalRequired as Q, type ReflectionStrategy as R, type StreamEvent as S, type TextDeltaEvent as T, isDone as U, isError as V, isPartialToolCall as W, isTextDelta as X, isToolCall as Y, isToolResult as Z, ladderReflectionStrategy as _, type DelegateOptions as a, noopReflectionStrategy as a0, presentUIMessageStream as a1, reflectionStrategyConfigSchema as a2, resolveLoopStrategy as a3, streamAgentResponse as a4, type MaskError as a5, type RoundStreamFactory as b, type DelegationResult as c, type AgentStreamEvent as d, type AgentTurnMetadata as e, type ApprovalRequiredEvent as f, type ArtifactChunkEvent as g, type ArtifactStartEvent as h, BudgetExceededError as i, DEFAULT_MAX_ITERATIONS as j, type DelegateFn as k, DelegationBudgetCostUnknownError as l, DelegationBudgetExceededError as m, DelegationError as n, type DelegationPort as o, type DoneEvent as p, type LoopFinishReason as q, type LoopOutcome as r, type LoopStrategyConfig as s, type ReflectionContext as t, type ReflectionResult as u, type ReflectionStrategyConfig as v, type RunStartedEvent as w, type ScoreVerdict as x, type ScoredDelegation as y, type Scorer as z };