@warble/claude-agent-sdk 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1143 @@
1
+ import { Query, Options, CanUseTool, HookCallbackMatcher, NonNullableUsage, ModelUsage, SDKMessage } from '@anthropic-ai/claude-agent-sdk';
2
+
3
+ /**
4
+ * Dispatch-time error — the TS analogue of the Rust back-end's `DispatchError`.
5
+ *
6
+ * Every loud-fail in this back-end (unknown IR version, unsupported enum "wall-hit", capability
7
+ * `fail`, undefined tier) throws a `DispatchError`; the CLI turns it into a non-zero exit + message.
8
+ */
9
+ declare class DispatchError extends Error {
10
+ constructor(message: string);
11
+ }
12
+
13
+ /** The deliberately small host-facing contract for provider-owned model discovery. */
14
+ declare const MODEL_CATALOG_VERSION: 1;
15
+ interface ModelCatalogModel {
16
+ model: string;
17
+ displayName: string;
18
+ description?: string;
19
+ isDefault?: boolean;
20
+ reasoningEfforts?: Array<{
21
+ value: string;
22
+ displayName: string;
23
+ description?: string;
24
+ }>;
25
+ }
26
+ type ModelCatalogUnavailableCode = "not_authenticated" | "runtime_unavailable" | "timeout" | "protocol_error";
27
+ type ModelCatalogResult = {
28
+ version: typeof MODEL_CATALOG_VERSION;
29
+ status: "ready";
30
+ provider: "claude";
31
+ models: ModelCatalogModel[];
32
+ } | {
33
+ version: typeof MODEL_CATALOG_VERSION;
34
+ status: "unavailable";
35
+ provider: "claude";
36
+ code: ModelCatalogUnavailableCode;
37
+ retryable: boolean;
38
+ };
39
+ type QueryFactory = (params: {
40
+ prompt: AsyncIterable<unknown>;
41
+ options: {
42
+ cwd: string;
43
+ tools: string[];
44
+ mcpServers: [];
45
+ settingSources: [];
46
+ abortController: AbortController;
47
+ };
48
+ }) => Query;
49
+ interface DiscoverClaudeModelsOptions {
50
+ cwd?: string;
51
+ timeoutMs?: number;
52
+ /** Test seam; production always uses the installed Agent SDK query factory. */
53
+ queryFactory?: QueryFactory;
54
+ }
55
+ /**
56
+ * Ask the authenticated Agent SDK for its model picker data without yielding a user message.
57
+ * The Query object still owns a subprocess/session, so every exit path interrupts, aborts, and
58
+ * returns its iterator before exposing the narrow catalog result.
59
+ */
60
+ declare function discoverClaudeModels(options?: DiscoverClaudeModelsOptions): Promise<ModelCatalogResult>;
61
+
62
+ /** `realization_kind` — how a component is realized. */
63
+ type RealizationKind = "skill" | "tool" | "gated-tool";
64
+ /** Component family. */
65
+ type ComponentType = "analytical" | "assertive" | "mutating" | "constitutive" | "orchestrating";
66
+ /** `trigger.kind`. */
67
+ type TriggerKind = "one_shot" | "scheduled" | "event";
68
+ /** `effect.outcome.kind`. */
69
+ type OutcomeKind = "none" | "assertion" | "mutation" | "dispatch";
70
+ declare const REALIZATION_KINDS: readonly RealizationKind[];
71
+ declare const COMPONENT_TYPES: readonly ComponentType[];
72
+ declare const TRIGGER_KINDS: readonly TriggerKind[];
73
+ declare const OUTCOME_KINDS: readonly OutcomeKind[];
74
+ interface ContextBinding {
75
+ project: string;
76
+ binding_mode: string;
77
+ /**
78
+ * Fine-grained resolved binding (IR v0.3): metrics/dimensions/grains + lineage summary the
79
+ * front-end learned from the bound semantic layer. Carried through and tolerated; this back-end
80
+ * does not yet consume it (it drives off the coarse project path).
81
+ */
82
+ resolved?: unknown;
83
+ }
84
+ /**
85
+ * The IR's profile-level `config` block. Empty since IR `0.6` removed `tier_policy` (an inert
86
+ * field no back-end read); kept as a type so future profile-level config is an additive change.
87
+ */
88
+ type IrConfig = Record<string, never>;
89
+ /**
90
+ * A per-step LLM call. `tier` is an **open string** (standard core: `strong`/`cheap`; custom names
91
+ * allowed) resolved to a concrete model at dispatch by the model config. The v0.2 named I/O contract
92
+ * (`consumes`/`produces`) + per-step `prompt` make a step realizable in isolation; an in-loop runtime
93
+ * like this one carries context itself and does not need them for single-session delegation.
94
+ */
95
+ interface LlmCall {
96
+ name: string;
97
+ tier: string;
98
+ consumes: string[];
99
+ produces: string | null;
100
+ prompt: string;
101
+ conditional: boolean;
102
+ /**
103
+ * The closed-vocabulary guard deciding whether a `conditional` step runs (IR v0.3+; see
104
+ * `docs/spec/ir-schema.md`). Realized by the hybrid-staged executor (`run.ts`, `conditional.ts`):
105
+ * an `on_failure` guard whose target is the adjacent producing step folds that step into a bounded
106
+ * repair turn; every other guard shape is a deterministic run/skip decision. The single/sdk-split
107
+ * paths ride the SDK's own in-loop `query()`, where Claude judges the condition emergently from the
108
+ * prompt text instead.
109
+ */
110
+ when: WhenGuard | null;
111
+ }
112
+ /**
113
+ * A closed-vocabulary guard on a conditional `llm_call`: `guard` is one of `on_failure` /
114
+ * `on_flag` / `on_missing`, `target` is the guard-specific argument. See `docs/spec/ir-schema.md`.
115
+ */
116
+ interface WhenGuard {
117
+ guard: string;
118
+ target: string;
119
+ }
120
+ interface Guardrail {
121
+ name: string;
122
+ locked: boolean;
123
+ scope: string | null;
124
+ threshold?: unknown;
125
+ }
126
+ /** A context precondition a component requires to hold before it runs (e.g. `has_metric`). */
127
+ interface Precondition {
128
+ predicate: string;
129
+ args?: Record<string, unknown>;
130
+ }
131
+ /**
132
+ * A component parameter, either bound at dispatch time (`bind`, with an optional `default`) or
133
+ * sourced from context (`source`). Exactly one of `bind`/`source` is expected per the schema.
134
+ */
135
+ interface ParamSpec {
136
+ name: string;
137
+ bind?: string;
138
+ source?: string;
139
+ default?: unknown;
140
+ }
141
+ /** An authored evaluation spec: which eval template to run and which metrics it scores. */
142
+ interface EvalSpec {
143
+ template_ref: string;
144
+ metrics: string[];
145
+ }
146
+ interface Trigger {
147
+ kind: TriggerKind;
148
+ }
149
+ /** A typed render block: a type plus its field-name → field-type schema (echoed verbatim). */
150
+ interface RenderBlock {
151
+ type: string;
152
+ fields: Record<string, string>;
153
+ }
154
+ interface Outcome {
155
+ kind: OutcomeKind;
156
+ verdict_type?: string;
157
+ emits?: string[];
158
+ target?: string;
159
+ change_type?: string;
160
+ routable_scope?: unknown;
161
+ }
162
+ interface Effect {
163
+ render_blocks: RenderBlock[];
164
+ outcome: Outcome;
165
+ }
166
+ /** One evaluated `context_precondition` and its outcome (IR v0.3: structured, was a string list). */
167
+ interface PreconditionCheck {
168
+ predicate: string;
169
+ outcome: string;
170
+ }
171
+ interface PreconditionResult {
172
+ status: string;
173
+ checks: PreconditionCheck[];
174
+ }
175
+ interface ComponentNode {
176
+ id: string;
177
+ verb: string;
178
+ type: ComponentType;
179
+ realization_kind: RealizationKind;
180
+ context_binding: ContextBinding;
181
+ precondition_result: PreconditionResult;
182
+ prompt_fragment: string;
183
+ llm_calls: LlmCall[];
184
+ guardrails: Guardrail[];
185
+ trigger: Trigger;
186
+ required_capabilities: string[];
187
+ borrowed_actions: string[];
188
+ eval_ref: string;
189
+ effect: Effect;
190
+ context_requirements: string[];
191
+ context_precondition: Precondition[];
192
+ params: ParamSpec[];
193
+ eval: EvalSpec | null;
194
+ /** Optional free-form framing shared by every step of this component (see `docs/spec/ir-schema.md`). */
195
+ brief?: string;
196
+ }
197
+ interface WarbleIr {
198
+ warble_ir_version: string;
199
+ profile: string;
200
+ context_binding: ContextBinding;
201
+ config: IrConfig;
202
+ components: ComponentNode[];
203
+ }
204
+ /**
205
+ * IR versions this back-end understands. Older versions (0.1, 0.2) are no longer accepted now that
206
+ * the front-end only emits 0.3 — see the compatibility matrix in `docs/spec/ir-schema.md`. An
207
+ * unrecognized version is a loud-fail rather than a silent best-effort read.
208
+ */
209
+ declare const SUPPORTED_IR_VERSIONS: readonly string[];
210
+ /**
211
+ * Parse + validate a Warble IR JSON document. Throws a {@link DispatchError} (loud-fail) on an
212
+ * unsupported version, a missing/mistyped load-bearing field, or an out-of-vocabulary enum value.
213
+ */
214
+ declare function parseIr(json: string): WarbleIr;
215
+ /** Distinct tier names across a node's `llm_calls`, order-preserving. */
216
+ declare function distinctTiers(calls: readonly LlmCall[]): string[];
217
+
218
+ /**
219
+ * Target capability profiles — the declarative side of the capability model
220
+ * (`docs/spec/capability-model.md`), owned by THIS back-end in TypeScript.
221
+ *
222
+ * A runtime target is `engine × mode`. This back-end declares one target, `claude-agent-sdk:local`:
223
+ * the local `@anthropic-ai/claude-agent-sdk` `query()` loop (subscription login, compute on the
224
+ * user's machine). The shared thing across back-ends is the IR + the capability-model
225
+ * *semantics* (native / realize-via / degrade / fail, criticality, provided_by); the profile *data*
226
+ * is target-specific and each back-end writes its own. So this file is the TS sibling of
227
+ * the Rust file target's `targets.rs`, not a shared table.
228
+ *
229
+ * How `local` differs from the Rust file target's `claude-code:headless` (the point of the second
230
+ * back-end):
231
+ * - `llm:per_step_tier` native (in-loop per-call model) ← headless: realize-via(subagents)
232
+ * - `structured_output_capture` native (message stream) ← headless: native(stream-json)
233
+ * - `render_contract` realize-via(warble-render) ← headless: realize-via(html-file)
234
+ * The rest match headless, including the safety-critical loud-fails (human_approval, blast_radius).
235
+ */
236
+ /** One of the four resolution outcomes a capability can take on a target. */
237
+ type CapabilityOutcome = "native" | "realize-via" | "degrade" | "fail";
238
+ /** Who supplies a resolved capability. */
239
+ type ProvidedBy = "runtime" | "warble" | "none";
240
+ /**
241
+ * safety-critical capabilities must never silently degrade — unsupported means the resolution pass
242
+ * aborts. required/best-effort may degrade with a warning recorded in the report.
243
+ */
244
+ type Criticality = "safety-critical" | "required" | "best-effort";
245
+ interface CapabilityEntry {
246
+ outcome: CapabilityOutcome;
247
+ via: string | null;
248
+ provided_by: ProvidedBy;
249
+ criticality: Criticality;
250
+ note: string | null;
251
+ }
252
+ type CapabilityProfile = Record<string, CapabilityEntry>;
253
+ /** The one target this back-end declares (engine × mode). */
254
+ type TargetId = "claude-agent-sdk:local";
255
+ declare const DEFAULT_TARGET: TargetId;
256
+ declare function isKnownTarget(value: string): value is TargetId;
257
+ declare function knownTargetNames(): readonly string[];
258
+ /** Capability profile for `claude-agent-sdk:local`. */
259
+ declare function localProfile(): CapabilityProfile;
260
+ /** Resolve a target id to its capability profile, or `null` if unknown. */
261
+ declare function profileFor(targetId: string): CapabilityProfile | null;
262
+
263
+ interface ResolvedCapability {
264
+ capability: string;
265
+ outcome: CapabilityOutcome;
266
+ provided_by: ProvidedBy;
267
+ criticality: Criticality;
268
+ note?: string;
269
+ }
270
+ type ResolutionReport = ResolvedCapability[];
271
+ /** Union of declared + implied required capabilities, de-duplicated, order-preserving. */
272
+ declare function collectRequiredCapabilities(node: ComponentNode): string[];
273
+ declare function resolveCapabilities(node: ComponentNode, targetId: string, profile: CapabilityProfile): ResolutionReport;
274
+ /**
275
+ * Resolve one node's required capabilities against `targetId`, erroring on any `fail` outcome
276
+ * (no silent degradation). Callers must not dispatch when this throws.
277
+ */
278
+ declare function resolveNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
279
+ /**
280
+ * Read-only inspection counterpart to `resolveNodeCapabilities`. It returns
281
+ * the same report, including a failed entry, but does not legalize execution.
282
+ * Only display-only callers may use this; dispatch/emit/chat retain the loud
283
+ * failure above.
284
+ */
285
+ declare function inspectNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
286
+
287
+ /**
288
+ * Which provider serves a tier's model — an **open string**, opaque to warble (mirrors how the IR
289
+ * treats `tier`; see `docs/spec/binding-spec.md`). Two well-known values get behavior baked into
290
+ * `TierBinding` parsing below (`ANTHROPIC_PROVIDER`, the default; `OPENAI_COMPAT_PROVIDER`, which
291
+ * requires `endpoint`), but warble does **not** validate this field against a fixed provider list —
292
+ * any other string is a valid, warble-unrecognized provider that passes through unchanged.
293
+ * Rejecting a genuinely unsupported provider is the consuming harness/back-end's job (its
294
+ * per-provider adapter registry), never warble's — warble stays opaque pass-through.
295
+ */
296
+ type Provider = string;
297
+ /**
298
+ * A tier's full runtime binding: which `provider` serves it, at what `endpoint` (OpenAI-compat only),
299
+ * running which `model`. The shorthand YAML form `tier: <model>` is `{ provider: 'anthropic',
300
+ * endpoint: null, model }` — so existing configs (and every all-cloud path) are byte-for-byte
301
+ * unchanged. Per-step provider routing reads `provider`/`endpoint`; `require()` reads `model`.
302
+ */
303
+ interface TierBinding {
304
+ provider: Provider;
305
+ endpoint: string | null;
306
+ model: string;
307
+ }
308
+ /**
309
+ * An ordered tier→binding map. Declaration order is priority: earlier tiers are "stronger" — used to
310
+ * pick the single model when a multi-tier component collapses to one call.
311
+ */
312
+ declare class ModelConfig {
313
+ /** `[tier name, binding]` in declaration order (earliest = strongest). */
314
+ private readonly tiers;
315
+ private constructor();
316
+ /** The Agent SDK defaults, matching the file target: strong→opus, cheap→haiku, orchestrator→sonnet. */
317
+ static default(): ModelConfig;
318
+ /**
319
+ * Build from the inline `--strong/--cheap/--orchestrator` flags. Inline flags are always
320
+ * Anthropic-provider aliases — provider/endpoint routing is `--models-config` only, so a non-alias
321
+ * inline flag still loud-fails on the SDK split path (unchanged behavior).
322
+ */
323
+ static fromFlags(strong: string, cheap: string, orchestrator: string): ModelConfig;
324
+ /**
325
+ * Parse a `--models-config` YAML document — the same shape the file target accepts. A tier value is
326
+ * EITHER a bare model-alias string (Anthropic shorthand) OR a `{ provider, endpoint?, model }` map:
327
+ *
328
+ * ```yaml
329
+ * tiers:
330
+ * strong: opus # shorthand ⇒ provider: anthropic
331
+ * cheap: # structured binding (docs/spec/capability-model.md §7.2)
332
+ * provider: openai_compat
333
+ * endpoint: http://localhost:11434/v1
334
+ * model: qwen2.5
335
+ * orchestrator: sonnet # reserved: the per-step-tier driver
336
+ * ```
337
+ */
338
+ static fromYaml(text: string): ModelConfig;
339
+ private bindingFor;
340
+ /** Priority rank of a tier (declaration order); unknown tiers rank last. */
341
+ private rank;
342
+ private tierNames;
343
+ /** The model a tier maps to, or a loud-fail naming the undefined tier. */
344
+ require(tier: string): string;
345
+ /**
346
+ * The full `{provider, endpoint, model}` binding a tier maps to (see docs/spec/capability-model.md
347
+ * §7.2), or a loud-fail.
348
+ * The per-step provider router (route.ts) reads this to send a step cloud-vs-local.
349
+ */
350
+ binding(tier: string): TierBinding;
351
+ /** The model for the reserved `orchestrator` tier, or a loud-fail if a config omitted it. */
352
+ orchestrator(): string;
353
+ /** The model for a single collapsed call: the strongest (lowest-rank) tier among the calls. */
354
+ collapsedModel(calls: readonly LlmCall[]): string;
355
+ /** Validate every step tier in the IR maps to a model (front-loaded so dispatch is infallible). */
356
+ validate(ir: WarbleIr): void;
357
+ }
358
+
359
+ /**
360
+ * Per-step provider routing — the hybrid-LLM core (see docs/spec/capability-model.md §7.2).
361
+ *
362
+ * The IR only knows *tiers*; the `--models-config` binding (models.ts) resolves each tier to a
363
+ * `{ provider, endpoint, model }`. When every step's provider is `anthropic`, the existing single
364
+ * `query()` loop (options.ts: single / sdk-split path) realizes the component and this module changes
365
+ * nothing. When ANY step binds to a non-Anthropic provider (e.g. ollama over OpenAI-compat), that step
366
+ * CANNOT ride the SDK `agents[].model` mechanism — that field is a restricted `sonnet|opus|haiku|inherit`
367
+ * alias union and loud-fails on anything else (SDK-NOTES.md #1). So the back-end must drive the steps
368
+ * itself: run each step as an isolated invocation on its own provider and marshal state between them via
369
+ * the IR's `consumes`/`produces` contract. That staged executor is the "hybrid-staged" mode.
370
+ *
371
+ * This module is PURE (no SDK, no network): it resolves the per-step bindings, decides the routing
372
+ * mode, and builds the marshaling messages. The actual per-step execution (a `query()` for cloud
373
+ * steps, an OpenAI-compat call for local steps) lives in run.ts. Keeping the decision pure is what
374
+ * lets the whole hybrid contract be unit-tested offline, with no ollama and no Claude subscription.
375
+ *
376
+ * Invariant (spike D2): none of this is in the IR, the components, or the profile — hybrid is entirely
377
+ * a layer-3 binding + back-end realization concern. The same compiled IR runs all-cloud or hybrid; only
378
+ * the injected `--models-config` differs.
379
+ */
380
+
381
+ /** A step with its tier resolved to a concrete `{provider, endpoint, model}` binding + its IO contract. */
382
+ interface StagedStep {
383
+ name: string;
384
+ tier: string;
385
+ provider: Provider;
386
+ endpoint: string | null;
387
+ model: string;
388
+ consumes: string[];
389
+ produces: string | null;
390
+ prompt: string;
391
+ conditional: boolean;
392
+ /** The closed-vocabulary guard deciding run/skip/repair for a `conditional` step; `null` when
393
+ * `conditional` is false. Realized by run.ts's staged executor (see conditional.ts). */
394
+ when: WhenGuard | null;
395
+ }
396
+ /**
397
+ * How a component's steps are realized:
398
+ * - `single` — one tier (or a tier collapse), one Anthropic `query()`. Existing path.
399
+ * - `sdk-split` — >1 Anthropic tier, per-step subagents in one `query()` via `agents`. Existing path.
400
+ * - `hybrid-staged` — ≥1 non-Anthropic provider; the back-end drives steps itself, one isolated
401
+ * invocation per step, marshaling `produces`→`consumes`.
402
+ */
403
+ type RoutingMode = "single" | "sdk-split" | "hybrid-staged";
404
+ interface RoutingPlan {
405
+ mode: RoutingMode;
406
+ /** Per-step resolved bindings (order = IR order). */
407
+ steps: StagedStep[];
408
+ /** Distinct providers across the steps, order-preserving. */
409
+ providers: Provider[];
410
+ }
411
+ /** Resolve every `llm_call`'s tier to a concrete binding, preserving IR order. Pure. */
412
+ declare function resolveStagedSteps(node: ComponentNode, models: ModelConfig): StagedStep[];
413
+ /** Distinct providers across resolved steps, order-preserving. */
414
+ declare function distinctProviders(steps: readonly StagedStep[]): Provider[];
415
+ /** True when any step binds to a non-Anthropic provider — the trigger for the hybrid-staged path. */
416
+ declare function usesLocalProvider(steps: readonly StagedStep[]): boolean;
417
+ /**
418
+ * Decide the routing mode for a component under a binding. `anthropicSplit` is the existing
419
+ * per-step-tier split decision (owned by options.ts, passed in to keep a single source of truth):
420
+ * it only applies when every provider is Anthropic.
421
+ */
422
+ declare function planProviderRouting(node: ComponentNode, models: ModelConfig, anthropicSplit: boolean): RoutingPlan;
423
+ /** A chat message for an isolated per-step invocation (both providers speak this shape). */
424
+ interface StepMessage {
425
+ role: "system" | "user";
426
+ content: string;
427
+ }
428
+ /**
429
+ * Build the messages for one staged step: the step's own prompt as the system message, and the user
430
+ * message = the question plus each consumed slot's value marshaled in by name (the `produces`→`consumes`
431
+ * hand-off). This is the same isolated-invocation contract the IR already carries for the file target's
432
+ * subagents (ir.ts `consumes`/`produces`), generalized here across providers.
433
+ */
434
+ declare function buildStepMessages(step: StagedStep, question: string, slots: Readonly<Record<string, string>>): StepMessage[];
435
+
436
+ /**
437
+ * Minimal OpenAI-compatible chat client for the hybrid-staged path (see docs/spec/capability-model.md §7.2).
438
+ *
439
+ * A local step (provider `openai_compat`, e.g. ollama's `http://localhost:11434/v1`) is executed by
440
+ * calling `POST {endpoint}/chat/completions` directly — NOT through the Claude SDK, whose `agents[].model`
441
+ * is a restricted alias union that loud-fails on a local model id (SDK-NOTES.md #1). ollama speaks the
442
+ * OpenAI Chat Completions shape, not the Anthropic Messages shape, so this is a distinct, deliberately
443
+ * tiny client — no streaming, no tools, no retries. It is the "third provider-aware back-end" embryo:
444
+ * enough to prove a per-step local model can be marshaled into a cloud run, not a production LLM client.
445
+ *
446
+ * Live-gated: exercised only when an ollama (or other OpenAI-compat) endpoint is reachable. The request
447
+ * SHAPING is unit-tested via {@link buildChatRequest} with no network.
448
+ */
449
+
450
+ interface ChatRequest {
451
+ model: string;
452
+ messages: StepMessage[];
453
+ stream: false;
454
+ /** Deterministic-leaning default; a local step is a bounded transform, not open-ended generation. */
455
+ temperature: number;
456
+ }
457
+ /** Build the JSON body for an OpenAI-compatible `/chat/completions` call. Pure (no network). */
458
+ declare function buildChatRequest(model: string, messages: StepMessage[]): ChatRequest;
459
+ /** Extract the assistant text from an OpenAI-compatible completion response. Pure. */
460
+ declare function extractCompletionText(body: unknown): string;
461
+ interface CallLocalOptions {
462
+ endpoint: string;
463
+ model: string;
464
+ messages: StepMessage[];
465
+ /** Optional bearer token (ollama ignores it; other OpenAI-compat servers may require it). */
466
+ apiKey?: string;
467
+ /** Injectable for tests; defaults to global fetch. */
468
+ fetchImpl?: typeof fetch;
469
+ }
470
+ /**
471
+ * Call an OpenAI-compatible chat endpoint and return the assistant text. Live-gated (needs a reachable
472
+ * endpoint); the request/response shaping is covered by {@link buildChatRequest} /
473
+ * {@link extractCompletionText} tests, and a stubbed `fetchImpl` can drive this end-to-end offline.
474
+ */
475
+ declare function callOpenAiCompat(opts: CallLocalOptions): Promise<string>;
476
+
477
+ /**
478
+ * IR enum → `query({options})` mapping — the core of this back-end, the TS analogue of the
479
+ * file target's `emit.rs`. Keyed on the **three orthogonal IR enums** (`realization_kind`,
480
+ * `effect.outcome.kind`, `trigger.kind`), never on a component's id/verb: adding another component
481
+ * of an existing type changes 0 lines here. Enum values this target does not yet
482
+ * realize fail loudly ("wall-hit"), mirroring `emit.rs::unsupported`.
483
+ *
484
+ * This module is pure/data: it builds the serializable `query()` options + a metadata report. The
485
+ * live callbacks — `canUseTool` runtime enforcement — are attached by `run.ts` from `guardrails.ts`,
486
+ * so the mapping stays testable offline.
487
+ */
488
+
489
+ type RenderFlavor = "programmatic" | "prompt";
490
+ declare const DEFAULT_RENDER_FLAVOR: RenderFlavor;
491
+ declare function parseRenderFlavor(value: string): RenderFlavor;
492
+ /** Bash rule patterns denied outright (defense in depth; canUseTool is the semantic gate). */
493
+ declare const DESTRUCTIVE_BASH_DENY: string[];
494
+ /**
495
+ * Per-step-tier split: a component whose steps span >1 tier. Realized in-loop via SDK `agents`.
496
+ * Realization-independent (see `resolve.ts::impliedCapabilities`) — driven purely by IR shape
497
+ * (>1 distinct step tier), never by `realization_kind` nor by whether the component happens to
498
+ * also *declare* `llm:per_step_tier` itself: that declaration is shape-implied, not authored, so
499
+ * requiring it redundantly would reintroduce the same silent-collapse failure for a tool/gated-tool
500
+ * component that never bothered to self-declare a capability the compiler already derives for it.
501
+ */
502
+ declare function shouldSplitPerStepTier(node: ComponentNode): boolean;
503
+ type GateKind = "realize" | "degrade" | "none";
504
+ /**
505
+ * How a RUNTIME render failure (`warble render` exiting non-zero, after the gate already resolved to
506
+ * `realize`) should be handled — distinct from `GateKind`'s `"degrade"`, which is a DESIGN-TIME
507
+ * capability degrade (no artifact-write surface at all, so no render is even attempted). Derived from
508
+ * the resolved `render_contract` capability's criticality: `best-effort` → `"degrade"` (fall back to
509
+ * the agent's own text, per the capability model's "best-effort may degrade" rule); `required` /
510
+ * `safety-critical` → `"fail"` (never silently degrade).
511
+ */
512
+ type GateFailureMode = "degrade" | "fail";
513
+ interface RenderGate {
514
+ kind: GateKind;
515
+ scope: string | null;
516
+ flavor: RenderFlavor | null;
517
+ /** Only meaningful when `kind === "realize"` (a runtime render call actually happens). Optional so
518
+ * the facet stays additive: a consumer that doesn't know about it sees `undefined` and must default
519
+ * to today's hard-fail behavior, never assume degrade. */
520
+ onFailure?: GateFailureMode;
521
+ }
522
+ interface ToolPlan {
523
+ /** Base built-in tool set made available to the agent (`tools` option). */
524
+ tools: string[];
525
+ /** Auto-allowed without a permission check. */
526
+ allowedTools: string[];
527
+ /** Hard-removed (defense in depth). */
528
+ disallowedTools: string[];
529
+ }
530
+ interface DispatchMeta {
531
+ verb: string;
532
+ target: string;
533
+ readOnly: boolean;
534
+ split: boolean;
535
+ render: RenderGate;
536
+ /** True when the outcome is an `assertion`: the final message is a verdict envelope (status block). */
537
+ assertion: boolean;
538
+ /** True when the outcome is a `mutation`: the final message is a diff/apply envelope (gated). */
539
+ mutation: boolean;
540
+ model: string;
541
+ /** Subagent tier→model, present only on the split path. */
542
+ subagentModels: Record<string, string>;
543
+ tierCollapseNote: string | null;
544
+ /** How the steps are realized (hybrid-LLM spike): single | sdk-split | hybrid-staged. */
545
+ mode: RoutingMode;
546
+ /** Distinct providers across the steps (order-preserving). `["anthropic"]` on the existing paths. */
547
+ providers: Provider[];
548
+ /** Per-step resolved bindings — populated on the `hybrid-staged` path (empty otherwise), so run.ts
549
+ * can drive each step on its own provider and marshal `produces`→`consumes`. */
550
+ stagedSteps: StagedStep[];
551
+ /** The project root a `setup_execution` component may write into (genbi-setup's onboarding
552
+ * flavor), or `null` for every other component. Threaded to `makeReadOnlyGuard` so Bash broadens
553
+ * beyond `wren` and Write/Edit are scoped to this root, instead of denied outright. `null` on the
554
+ * hybrid-staged path (out of scope — see buildHybridStagedPlan). */
555
+ setupScope: string | null;
556
+ }
557
+ interface DispatchPlan {
558
+ /** The user question (assembled prompt for `query()`). */
559
+ prompt: string;
560
+ /** Serializable `query()` options (canUseTool is attached later by run.ts). */
561
+ options: Options;
562
+ meta: DispatchMeta;
563
+ }
564
+ interface BuildConfig {
565
+ target: string;
566
+ flavor: RenderFlavor;
567
+ models: ModelConfig;
568
+ question: string;
569
+ /** Absolute path to the bound wren project (resolved by the CLI). */
570
+ cwd: string;
571
+ maxTurns?: number;
572
+ }
573
+ /**
574
+ * Build the `query({options})` for one resolved IR node. Loud-fails on any unsupported enum value
575
+ * before producing anything (wall-hit), mirroring `emit.rs`.
576
+ */
577
+ declare function buildDispatchPlan(node: ComponentNode, report: ResolutionReport, cfg: BuildConfig): DispatchPlan;
578
+
579
+ /**
580
+ * Warble's own, consumer-agnostic chat-event vocabulary — the NDJSON records `chat --stream-json`
581
+ * (cli.ts) emits to stdout, one JSON object per line, as a turn runs. This module owns the mapping
582
+ * FROM the Agent SDK's `SDKMessage` stream TO this vocabulary; it knows nothing about any particular
583
+ * consumer's own event types — a consumer maps `WarbleChatEvent` to whatever shape it needs on its own
584
+ * side (out of scope here).
585
+ *
586
+ * Step bracketing: rather than trying to derive nested Task-subagent step boundaries from
587
+ * `parent_tool_use_id` transitions (fragile — a subagent's tool calls interleave with the driver's,
588
+ * and the SDK gives no explicit "subagent started/finished" message), `ChatEventMapper` emits a
589
+ * SINGLE enclosing `step_start` (id = the dispatched verb) on the first message that produces a tool
590
+ * call, and a matching `step_finish` when the caller reports the turn is done (`finish()`). Every
591
+ * `tool_call`/`tool_result` the turn produces is grouped under that one step. Simple and correct beats
592
+ * clever-but-fragile here — a consumer that wants finer-grained nesting can still use each event's
593
+ * `parent`/`depth` fields (populated from `parent_tool_use_id`) to distinguish driver-turn tool calls
594
+ * from subagent-turn tool calls within the single step.
595
+ *
596
+ * The mapper does NOT emit `answer` — that line is assembled by the CLI from the turn's final text
597
+ * once the whole message stream has been consumed (see cli.ts's `runChatCmd`).
598
+ */
599
+ type WarbleChatEvent = {
600
+ readonly t: "step_start";
601
+ readonly id: string;
602
+ readonly name: string;
603
+ readonly parent: string | null;
604
+ readonly depth: number;
605
+ } | {
606
+ readonly t: "step_finish";
607
+ readonly id: string;
608
+ readonly ok: boolean;
609
+ readonly detail?: string;
610
+ } | {
611
+ readonly t: "tool_call";
612
+ readonly id: string;
613
+ readonly name: string;
614
+ readonly input?: unknown;
615
+ readonly parent: string | null;
616
+ readonly depth: number;
617
+ } | {
618
+ readonly t: "tool_result";
619
+ readonly id: string;
620
+ readonly ok: boolean;
621
+ readonly summary?: string;
622
+ readonly error?: string;
623
+ } | {
624
+ readonly t: "answer";
625
+ readonly text: string;
626
+ } | {
627
+ /**
628
+ * The turn's SDK session id (multi-turn resume anchor, `run.ts`'s `RunResult.sessionId`),
629
+ * emitted once per turn by `chat --stream-json` (cli.ts) — on success AND on a failed turn
630
+ * (e.g. `error_max_turns`), since a caller resuming after a failure needs the session id of
631
+ * the conversation that failed, not just of a successfully completed one. `id` is null only
632
+ * if the SDK's result message never carried a session id at all.
633
+ */
634
+ readonly t: "session";
635
+ readonly id: string | null;
636
+ };
637
+
638
+ /** A blocked tool call, recorded so the trace/report can prove enforcement actually fired. */
639
+ interface Denial {
640
+ tool: string;
641
+ reason: string;
642
+ command?: string;
643
+ }
644
+ interface GuardConfig {
645
+ readOnly: boolean;
646
+ /** Absolute artifact-write scope dir (prompt flavor); null keeps the agent fully read-only. */
647
+ writeScope: string | null;
648
+ /** The session cwd (bound wren project), used to resolve relative write paths. */
649
+ cwd: string;
650
+ /**
651
+ * +Mutating: when set, Write/Edit calls are the gated apply of a mutating component's diff, not a
652
+ * plain artifact write. The actual approval decision is borrowed from the SDK embedder's own
653
+ * `canUseTool` wrapper / approval channel — this guard cannot grant an apply on its own, so it
654
+ * always denies fail-closed and records why (a target with no approval channel is the honest edge,
655
+ * not a bug to route around).
656
+ */
657
+ mutation?: {
658
+ mustDryRun: boolean;
659
+ approvalRequired: boolean;
660
+ /**
661
+ * +Constitutive: the THIRD enforcement point, `context_write_authz` — a path-scoped gate distinct
662
+ * from `writeScope` (render artifact writes) and the plain mutation approval gate (data writes).
663
+ * When set, a Write/Edit outside this scope is denied with a SCOPE-VIOLATION reason (never even
664
+ * reaches the approval question); a write inside the scope still denies fail-closed, but with an
665
+ * APPROVAL reason — same fail-closed philosophy as the unscoped mutation branch below. The two
666
+ * reasons are distinguishable so callers/tests can tell which gate fired. Unset keeps the existing
667
+ * unscoped mutation behavior unchanged.
668
+ */
669
+ contextScope?: string;
670
+ };
671
+ /**
672
+ * +Setup (genbi-setup, the 5th enforcement point: `setup_execution`): the onboarding flavor. When
673
+ * set, Bash is broadened beyond `wren` (connector CLIs like `dlt` are permitted too — still subject
674
+ * to the DESTRUCTIVE/REDIRECTION/dotenv-read denylist, checked first and never relaxed), and
675
+ * Write/Edit are scoped to this project root rather than denied outright, and Read is denied for a
676
+ * dotenv-shaped path (see DOTENV_READER_COMMANDS/DOTENV_PATH below). Distinct from `writeScope`
677
+ * (render artifacts) and the `mutation` gates (a pre-existing MDL's diff/apply lifecycle): setup has
678
+ * no pre-bound context to gate reads against and no diff to approve — it is scaffolding a NEW
679
+ * project. `undefined`/`null` leaves every other component's behavior unchanged.
680
+ */
681
+ setupScope?: string | null;
682
+ }
683
+ /**
684
+ * Build the `canUseTool` gate for a component, plus the `PreToolUse` hooks needed to actually enforce
685
+ * the +Setup dotenv-read gap's Read side (see `makeSetupReadDenyHook`'s comment — `canUseTool` alone
686
+ * does not reach in-cwd Read in the real SDK). Both share the same `denials` array so the trace sees
687
+ * every enforcement point that fired, however it fired. Callers MUST wire `hooks` into the `query()`
688
+ * `Options.hooks.PreToolUse` for every invocation this guard's `canUseTool` is passed to — passing one
689
+ * without the other leaves the Read side unenforced for +Setup. `hooks` is `[]` for every non-setup
690
+ * component (readOnly/writeScope/mutation/context_write_authz), so wiring it unconditionally is safe
691
+ * and does not change behavior for those paths.
692
+ *
693
+ * Fail-closed: anything not explicitly permitted by `canUseTool` is denied with guidance.
694
+ */
695
+ declare function makeReadOnlyGuard(cfg: GuardConfig): {
696
+ canUseTool: CanUseTool;
697
+ denials: Denial[];
698
+ hooks: HookCallbackMatcher[];
699
+ };
700
+
701
+ /** One assistant turn's usage. `parent_tool_use_id` distinguishes driver turns from subagent turns. */
702
+ interface StepUsage {
703
+ model: string;
704
+ parent_tool_use_id: string | null;
705
+ usage: unknown;
706
+ }
707
+ interface Trace {
708
+ target: string;
709
+ verb: string;
710
+ model: string;
711
+ split: boolean;
712
+ run: {
713
+ total_cost_usd: number;
714
+ duration_ms: number;
715
+ duration_api_ms: number;
716
+ num_turns: number;
717
+ } | null;
718
+ usage: NonNullableUsage | null;
719
+ /** Per-model usage — and since each tier maps to a distinct model, this is per-tier cost. */
720
+ modelUsage: Record<string, ModelUsage>;
721
+ /** Per assistant turn (per-step granularity the headless file target can't produce). */
722
+ steps: StepUsage[];
723
+ denials: Denial[];
724
+ }
725
+ /** Pure: fold the captured message stream + guardrail denials into a trace. */
726
+ declare function aggregateTrace(messages: readonly SDKMessage[], meta: {
727
+ target: string;
728
+ verb: string;
729
+ model: string;
730
+ split: boolean;
731
+ }, denials: Denial[]): Trace;
732
+ interface RunResult {
733
+ finalText: string;
734
+ trace: Trace;
735
+ htmlPath: string | null;
736
+ denials: Denial[];
737
+ /** The SDK's session id for this run, if the result carried one (multi-turn resume anchor). */
738
+ sessionId: string | null;
739
+ /** Set when a best-effort `render_contract` failed at runtime (`warble render` exited non-zero)
740
+ * and the turn degraded instead of hard-failing (`render.onFailure === "degrade"`): `htmlPath`
741
+ * stays `null`, `finalText` is still the agent's answer, and this carries why the render was
742
+ * skipped. `null` on every run that never hit a render failure — a required/safety-critical
743
+ * render failure still throws (`DispatchError`/`DispatchSessionError`) and never reaches here. */
744
+ renderDegraded: {
745
+ reason: string;
746
+ } | null;
747
+ }
748
+ interface RunConfig {
749
+ outDir: string;
750
+ warbleBin: string;
751
+ /** Optional dashboard title passed through to `warble render`. */
752
+ title?: string;
753
+ /** Resume a prior turn's session (multi-turn continuity, session.ts). Mutually exclusive in practice
754
+ * with a fresh turn — omit for turn 1. */
755
+ resume?: string;
756
+ /** Opt-in streaming sink (`chat --stream-json`, cli.ts): called once per `WarbleChatEvent` as the
757
+ * message stream is consumed, not batched after the fact. Only wired on the main (single/split) SDK
758
+ * loop below — the hybrid-staged executor passes through with no events. */
759
+ onEvent?: (event: WarbleChatEvent) => void;
760
+ }
761
+ /**
762
+ * A dispatch failure that still carries the SDK's session id, when the result message had one —
763
+ * e.g. `error_max_turns`: the run failed, but the conversation itself is still resumable. A caller
764
+ * that wants to continue the SAME session with more turns (rather than re-dispatching a fresh
765
+ * prompt) needs this id; a plain `DispatchError` would discard it. `sessionId` is null only when
766
+ * the SDK never produced a result message at all (no session to resume).
767
+ */
768
+ declare class DispatchSessionError extends DispatchError {
769
+ readonly sessionId: string | null;
770
+ constructor(message: string, sessionId: string | null);
771
+ }
772
+ /**
773
+ * Run a dispatch plan against the live Agent SDK, then render + trace. Writes `result.txt`,
774
+ * `trace.json`, and (programmatic realize flavor) `dashboard.html` into `outDir`.
775
+ */
776
+ declare function runDispatch(plan: DispatchPlan, cfg: RunConfig): Promise<RunResult>;
777
+
778
+ /**
779
+ * The orchestrator's system prompt — PROVIDER-AGNOSTIC. Lists the steps in order by name and the
780
+ * produces→consumes marshaling; it never says which step is local vs cloud (that is the handler's job,
781
+ * from the binding). So the same prompt shape is emitted whether the binding is all-cloud or hybrid.
782
+ */
783
+ declare function buildToolDriverPrompt(steps: readonly StagedStep[]): string;
784
+ /**
785
+ * Run the hybrid-tool path: one orchestrator query() + a `dispatch_step` tool that routes each step to
786
+ * its bound provider. Mirrors {@link runHybridStaged}'s outputs (result.txt / trace.json / RunResult).
787
+ */
788
+ declare function runHybridTool(plan: DispatchPlan, cfg: RunConfig): Promise<RunResult>;
789
+
790
+ interface RenderResult {
791
+ outPath: string;
792
+ /** stderr from `warble render` (it logs "wrote … (N block(s))"). */
793
+ log: string;
794
+ }
795
+ /**
796
+ * Write the captured agent output to a temp file and shell out to `warble render`. `warble render`
797
+ * tolerates the model fencing/prose-wrapping the envelope and unwraps a `--output-format json`
798
+ * result object (see the Rust `parseEnvelope`), so we pass the raw final text through unchanged.
799
+ */
800
+ declare function renderEnvelope(finalText: string, outPath: string, opts: {
801
+ warbleBin: string;
802
+ title?: string;
803
+ }): RenderResult;
804
+
805
+ /**
806
+ * A minimal structured snapshot of what a turn resolved — filters/dimensions/measures/grain — used
807
+ * only to thread context into the NEXT turn's prompt. Nothing here is inferred by this module: a
808
+ * caller who has parsed it out of the agent's own answer (or a render envelope) supplies it via
809
+ * `ChatSession.ask(question, { intent })`. Sessions with no supplied intent simply skip distillation.
810
+ */
811
+ interface ResolvedIntent {
812
+ filters: string[];
813
+ dimensions: string[];
814
+ measures: string[];
815
+ grain?: string;
816
+ }
817
+ /**
818
+ * Merge the prior turn's resolved intent with a new follow-up question into a distilled context
819
+ * string, PREPENDED to the question as guidance for the agent — this never decides routing or
820
+ * overrides the agent's own resolution, it only reduces the odds it drops context a human speaker
821
+ * would have kept implicit ("break it down by region" after "completed orders" should still mean
822
+ * completed orders, broken down by region).
823
+ *
824
+ * Merge policy (heuristic, intentionally simple for G1):
825
+ * - filters: carried forward unless the new question signals its own filter override (`where`,
826
+ * `only`, `excluding`, `filtered to`, `instead of`).
827
+ * - dimensions: swapped to whatever the new question names after "by"/"group by"; otherwise carried.
828
+ * - measures / grain: always carried (no override heuristic yet — every question in G1 keeps the
829
+ * same metric family; multi-metric follow-ups are out of scope).
830
+ */
831
+ declare function distillFollowup(prevIntent: ResolvedIntent, newQuestion: string): string;
832
+ type ClarifyOutcome = {
833
+ kind: "clarify";
834
+ question: string;
835
+ } | {
836
+ kind: "answer";
837
+ };
838
+ /** Below this confidence, clarify rather than guess (a clarifying question is cheaper than a
839
+ * wasted expensive call). Confidence itself is supplied by the caller — parsed from whatever signal
840
+ * the agent/router gave (an eval score, a router's own stated confidence, etc.); this function only
841
+ * encodes the threshold policy, it doesn't compute confidence. */
842
+ declare const DEFAULT_CLARIFY_THRESHOLD = 0.55;
843
+ declare function decideClarify(question: string, confidence: number, threshold?: number): ClarifyOutcome;
844
+ interface Turn {
845
+ question: string;
846
+ /** The prompt actually sent to `query()` for this turn (post-distillation). */
847
+ prompt: string;
848
+ /** The turn's resolved intent, if the caller supplied one for carry-forward; null if not tracked. */
849
+ intent: ResolvedIntent | null;
850
+ /** The SDK's session id for this turn's run (resume anchor for the next turn), if any. */
851
+ sessionId: string | null;
852
+ finalText: string;
853
+ }
854
+ interface SessionState {
855
+ turns: readonly Turn[];
856
+ }
857
+ declare function createSessionState(): SessionState;
858
+ /** The resume anchor for the NEXT turn: the most recent turn's `session_id`, or null on turn 1. */
859
+ declare function lastSessionId(state: SessionState): string | null;
860
+ /** The most recently resolved intent to carry forward, or null if none was ever supplied. */
861
+ declare function lastResolvedIntent(state: SessionState): ResolvedIntent | null;
862
+ /** Pure append — returns a new state, does not mutate. */
863
+ declare function appendTurn(state: SessionState, turn: Turn): SessionState;
864
+ /** Build the next turn's prompt: turn 1 = the raw question; turn N = distilled context + question. */
865
+ declare function buildTurnPrompt(state: SessionState, question: string): string;
866
+ interface TurnResult {
867
+ finalText: string;
868
+ sessionId: string | null;
869
+ trace: Trace;
870
+ /** The actual (post-distillation) prompt sent for this turn. */
871
+ prompt: string;
872
+ }
873
+ interface AskOptions {
874
+ /** Supply this turn's resolved intent so it can be carried forward into the NEXT turn's prompt. */
875
+ intent?: ResolvedIntent;
876
+ /** Opt-in streaming sink for this turn, forwarded straight to `runDispatch` (`chat --stream-json`). */
877
+ onEvent?: (event: WarbleChatEvent) => void;
878
+ }
879
+ /**
880
+ * A multi-turn chat session over ONE prepared component's `DispatchPlan`. Each `ask()` resumes the
881
+ * prior turn's SDK session (`resume: session_id`) so the agent keeps the real conversation history;
882
+ * `distillFollowup` layers a structured hint on top for callers tracking resolved intent explicitly.
883
+ * All branching policy (distillation, clarify) lives in the pure functions above — this class is just
884
+ * plumbing over `runDispatch`.
885
+ */
886
+ declare class ChatSession {
887
+ private readonly plan;
888
+ private readonly runCfg;
889
+ /**
890
+ * Seeds the FIRST `ask()` call's resume anchor with a session id captured by an earlier
891
+ * process (e.g. `warble-agent-sdk chat --resume <id>`, cli.ts) — lets a NEW `ChatSession`
892
+ * instance resume a conversation it did not itself start. Ignored once any real turn has been
893
+ * asked in THIS instance: `lastSessionId(this.state)` then takes over, exactly as before.
894
+ */
895
+ private readonly initialResumeSessionId?;
896
+ private state;
897
+ constructor(plan: DispatchPlan, runCfg: RunConfig,
898
+ /**
899
+ * Seeds the FIRST `ask()` call's resume anchor with a session id captured by an earlier
900
+ * process (e.g. `warble-agent-sdk chat --resume <id>`, cli.ts) — lets a NEW `ChatSession`
901
+ * instance resume a conversation it did not itself start. Ignored once any real turn has been
902
+ * asked in THIS instance: `lastSessionId(this.state)` then takes over, exactly as before.
903
+ */
904
+ initialResumeSessionId?: string | undefined);
905
+ getState(): SessionState;
906
+ ask(question: string, opts?: AskOptions): Promise<TurnResult>;
907
+ }
908
+ declare function createChatSession(plan: DispatchPlan, runCfg: RunConfig, initialResumeSessionId?: string): ChatSession;
909
+
910
+ interface DispatchInput {
911
+ /** A parsed IR or a raw JSON string. */
912
+ ir: WarbleIr | string;
913
+ /** The data question to answer (the `query()` prompt). Optional for prepare-only (dry-run/emit). */
914
+ question?: string;
915
+ target?: string;
916
+ flavor?: RenderFlavor;
917
+ models?: ModelConfig;
918
+ maxTurns?: number;
919
+ /** Explicit bound-project cwd (absolute or cwd-relative). Overrides `irPath`-based resolution. */
920
+ project?: string;
921
+ /** Resolve each node's relative `context_binding.project` against this IR file's directory. */
922
+ irPath?: string;
923
+ /**
924
+ * Scope preparation to exactly this component id: only its capabilities are resolved and only
925
+ * its plan is built — every *other* component in the IR is left untouched, so its
926
+ * `required_capabilities` never enter this dispatch's preflight. Use this for `chat`, which
927
+ * only ever runs one component per process.
928
+ *
929
+ * Omit (the default) to prepare every component in the IR — the shape `manifest`, `emit`, and
930
+ * the whole-profile `dispatch` subcommand need, since each of those actually reads or runs
931
+ * every component and must know every component's resolution, not just one's.
932
+ *
933
+ * This narrows *which* component's requirements gate a given `prepareDispatch` call — it does
934
+ * not change what happens when a gated capability is unmet (still a loud throw, same message,
935
+ * same named capability; see `resolveNodeCapabilities`). A component that can itself invoke
936
+ * another IR component at runtime would need that callee's requirements folded in here too, but
937
+ * no such reachability exists yet: `borrowed_actions` names external runtime actions (notify,
938
+ * ticket, …), never another component, and the one mechanism shaped for it — `orchestrating` /
939
+ * `effect.outcome.kind: "dispatch"` with `routable_scope` — is parsed but not consumed by any
940
+ * back-end (`docs/spec/authoring.md` marks `orchestrating` "scaffolded", not realized). If that
941
+ * ever lands, this scoping must fold in the callee's requirements too.
942
+ */
943
+ componentId?: string;
944
+ }
945
+ interface PreparedComponent {
946
+ id: string;
947
+ node: ComponentNode;
948
+ report: ResolutionReport;
949
+ plan: DispatchPlan;
950
+ }
951
+ interface PreparedDispatch {
952
+ target: string;
953
+ components: PreparedComponent[];
954
+ }
955
+ /** Stable redacted status for a component the configured target cannot run. */
956
+ declare const UNAVAILABLE_COMPONENT_REASON = "component is unavailable on the configured runtime";
957
+ interface UnavailableDisplayComponent {
958
+ id: string;
959
+ node: ComponentNode;
960
+ availability: {
961
+ status: "unavailable";
962
+ reason: typeof UNAVAILABLE_COMPONENT_REASON;
963
+ };
964
+ }
965
+ type DisplayComponent = PreparedComponent | UnavailableDisplayComponent;
966
+ interface PreparedDisplayManifest {
967
+ target: string;
968
+ components: DisplayComponent[];
969
+ }
970
+ /**
971
+ * Resolve a node's bound wren project to an absolute cwd. Relative `context_binding.project` paths
972
+ * resolve against the IR file's directory (`irPath`) when given, else the current working directory;
973
+ * an explicit `project` always wins.
974
+ */
975
+ declare function resolveProjectCwd(node: ComponentNode, opts: {
976
+ project?: string;
977
+ irPath?: string;
978
+ }): string;
979
+ /**
980
+ * Parse + resolve + build every requested component's `query({options})`, without calling the SDK.
981
+ *
982
+ * By default this prepares every component in the IR. Pass `input.componentId` to scope
983
+ * preparation — and therefore capability resolution — to exactly that one component; every other
984
+ * component's `required_capabilities` are never consulted, so a component that isn't being
985
+ * dispatched can't wall-hit a dispatch it has nothing to do with. See {@link DispatchInput.componentId}.
986
+ */
987
+ declare function prepareDispatch(input: DispatchInput): PreparedDispatch;
988
+ /**
989
+ * Prepare a display-only whole-profile manifest. Unsupported components are
990
+ * represented by a closed unavailable marker; no executable plan is built
991
+ * for them. This must never be used by emit, dispatch, or chat.
992
+ */
993
+ declare function prepareDisplayManifest(input: Omit<DispatchInput, "componentId" | "question">): PreparedDisplayManifest;
994
+ interface DispatchRunConfig {
995
+ outDir: string;
996
+ warbleBin?: string;
997
+ title?: string;
998
+ }
999
+ interface ComponentOutcome {
1000
+ id: string;
1001
+ report: ResolutionReport;
1002
+ plan: DispatchPlan;
1003
+ result: RunResult;
1004
+ }
1005
+ interface DispatchOutcome {
1006
+ target: string;
1007
+ components: ComponentOutcome[];
1008
+ }
1009
+ /**
1010
+ * Prepare then RUN each component against the live Agent SDK loop. Requires `input.question`.
1011
+ * Writes each run's artifacts under `runCfg.outDir` (see {@link runDispatch}).
1012
+ */
1013
+ declare function dispatch(input: DispatchInput, runCfg: DispatchRunConfig): Promise<DispatchOutcome>;
1014
+
1015
+ /**
1016
+ * Codegen (`emit`) — freeze a prepared dispatch into an importable TS agent module.
1017
+ *
1018
+ * The IR→options mapping is resolved at emit time and the resulting `query({options})` is written as
1019
+ * source, plus a thin `run()` per component. Analogue of the file target emitting `.md` — here it is
1020
+ * `.ts` a user drops into their own codebase. Two modes:
1021
+ * - **thin (default)** — imports the runtime helpers (guardrail / trace / render) from
1022
+ * `@warble/claude-agent-sdk`; small and always in sync with the library.
1023
+ * - **standalone** — inlines a minimal read-only guard + trace + render shell, so the only imports
1024
+ * are `@anthropic-ai/claude-agent-sdk` and Node built-ins (the `warble` *binary* is still used
1025
+ * for render — that is the renderer-reuse contract, not a TS dependency).
1026
+ */
1027
+
1028
+ interface EmitOptions {
1029
+ standalone?: boolean;
1030
+ }
1031
+ /**
1032
+ * Emit a TS agent module (as source text) for a prepared dispatch. Each component becomes an exported
1033
+ * async `run()` function that drives the SDK loop with the resolved, frozen options.
1034
+ */
1035
+ declare function emitAgentModule(prepared: PreparedDispatch, opts?: EmitOptions): string;
1036
+
1037
+ /**
1038
+ * The `claude-agent-sdk:local` display manifest — a stable, structural snapshot of a resolved
1039
+ * profile (agents / steps / tiers / capabilities / guardrails) for THIS target, so a consumer can
1040
+ * source a "what will run" display from whichever back-end actually runs, instead of always
1041
+ * reading the vercel bundle target's output even when this back-end is the one dispatching.
1042
+ *
1043
+ * Field-for-field port of the vercel bundle target's assembly (`dispatcher/vercel/src/{bundle,
1044
+ * guardrails,schema,classify,emit}.rs`), driven off the same parsed IR + `ResolutionReport` this
1045
+ * back-end already produces via `prepareDispatch` — no shelling out to the Rust binary, and no
1046
+ * shared code between the two ports (each is a small, pure derivation from the same IR seam, kept
1047
+ * independently portable per language).
1048
+ */
1049
+
1050
+ interface CompatibilityPolicy {
1051
+ min_ir_version: string;
1052
+ max_ir_version: string;
1053
+ }
1054
+ interface WhenGuardOut {
1055
+ guard: string;
1056
+ target: string;
1057
+ }
1058
+ /**
1059
+ * How a conditional step is realized — mirrors the vercel bundle's `StepRealization` (serde
1060
+ * `tag = "kind"`, `snake_case`). `fallback` is omitted (never emitted): the IR has no field
1061
+ * declaring one today, matching the Rust port's `skip_serializing_if` behavior.
1062
+ */
1063
+ type StepRealization = {
1064
+ kind: "independent";
1065
+ } | {
1066
+ kind: "repair_fold";
1067
+ fold_into: string;
1068
+ max_attempts: number;
1069
+ fallback?: string;
1070
+ } | {
1071
+ kind: "guarded_skip";
1072
+ };
1073
+ interface StepManifest {
1074
+ name: string;
1075
+ tier: string;
1076
+ consumes: string[];
1077
+ produces?: string;
1078
+ prompt: string;
1079
+ when?: WhenGuardOut;
1080
+ realization: StepRealization;
1081
+ }
1082
+ interface ToolRef {
1083
+ name: string;
1084
+ source: string;
1085
+ }
1086
+ interface GuardrailManifest {
1087
+ enforcement: string;
1088
+ locked: boolean;
1089
+ scope?: string;
1090
+ threshold?: unknown;
1091
+ }
1092
+ interface AvailableAgentManifest {
1093
+ id: string;
1094
+ verb: string;
1095
+ component_type: ComponentNode["type"];
1096
+ realization_kind: ComponentNode["realization_kind"];
1097
+ trigger: ComponentNode["trigger"]["kind"];
1098
+ outcome: ComponentNode["effect"]["outcome"]["kind"];
1099
+ steps: StepManifest[];
1100
+ guardrails: Record<string, GuardrailManifest>;
1101
+ tools: ToolRef[];
1102
+ output_schema: unknown;
1103
+ capabilities: ResolutionReport;
1104
+ brief?: string;
1105
+ }
1106
+ /** A display-only declaration of a component that remains unavailable to this target. */
1107
+ interface UnavailableAgentManifest {
1108
+ id: string;
1109
+ verb: string;
1110
+ component_type: ComponentNode["type"];
1111
+ realization_kind: ComponentNode["realization_kind"];
1112
+ trigger: ComponentNode["trigger"]["kind"];
1113
+ outcome: ComponentNode["effect"]["outcome"]["kind"];
1114
+ /** Fixed empty surfaces: this record can never be treated as an execution plan. */
1115
+ steps: [];
1116
+ guardrails: Record<string, never>;
1117
+ tools: [];
1118
+ output_schema: Record<string, never>;
1119
+ capabilities: [];
1120
+ availability: {
1121
+ status: "unavailable";
1122
+ reason: string;
1123
+ };
1124
+ }
1125
+ type AgentManifest = AvailableAgentManifest | UnavailableAgentManifest;
1126
+ interface Manifest {
1127
+ manifest_version: string;
1128
+ compat: CompatibilityPolicy;
1129
+ profile: string;
1130
+ target: string;
1131
+ agents: AgentManifest[];
1132
+ }
1133
+ /** Port of `emit.rs::build_agent_bundle`, minus the tool-map parameter (this back-end's is fixed,
1134
+ * see `LOCAL_TOOL_MAP`). */
1135
+ declare function buildAgentManifest(component: PreparedComponent): AvailableAgentManifest;
1136
+ /** Never derives a plan, tool, or capability grant for an unavailable component. */
1137
+ declare function buildUnavailableAgentManifest(component: UnavailableDisplayComponent): UnavailableAgentManifest;
1138
+ /** Build the full display manifest for a `prepareDispatch` result. `raw` is the same IR the
1139
+ * dispatch was prepared from — re-parsed here (a second, cheap, pure parse) just to read
1140
+ * `profile`, which `PreparedDispatch` does not itself carry. */
1141
+ declare function buildManifest(prepared: PreparedDispatch | PreparedDisplayManifest, raw: string): Manifest;
1142
+
1143
+ export { type AgentManifest, type AskOptions, type AvailableAgentManifest, type BuildConfig, COMPONENT_TYPES, type CallLocalOptions, type CapabilityEntry, type CapabilityOutcome, type CapabilityProfile, type ChatRequest, ChatSession, type ClarifyOutcome, type CompatibilityPolicy, type ComponentNode, type ComponentOutcome, type ComponentType, type ContextBinding, type Criticality, DEFAULT_CLARIFY_THRESHOLD, DEFAULT_RENDER_FLAVOR, DEFAULT_TARGET, DESTRUCTIVE_BASH_DENY, type Denial, type DiscoverClaudeModelsOptions, DispatchError, type DispatchInput, type DispatchMeta, type DispatchOutcome, type DispatchPlan, type DispatchRunConfig, DispatchSessionError, type DisplayComponent, type Effect, type EmitOptions, type GateFailureMode, type GateKind, type GuardConfig, type Guardrail, type GuardrailManifest, type IrConfig, type LlmCall, MODEL_CATALOG_VERSION, type Manifest, type ModelCatalogModel, type ModelCatalogResult, type ModelCatalogUnavailableCode, ModelConfig, OUTCOME_KINDS, type Outcome, type OutcomeKind, type PreconditionResult, type PreparedComponent, type PreparedDispatch, type PreparedDisplayManifest, type ProvidedBy, type Provider, REALIZATION_KINDS, type RealizationKind, type RenderBlock, type RenderFlavor, type RenderGate, type RenderResult, type ResolutionReport, type ResolvedCapability, type ResolvedIntent, type RoutingMode, type RoutingPlan, type RunConfig, type RunResult, SUPPORTED_IR_VERSIONS, type SessionState, type StagedStep, type StepManifest, type StepMessage, type StepRealization, type StepUsage, TRIGGER_KINDS, type TargetId, type TierBinding, type ToolPlan, type ToolRef, type Trace, type Trigger, type TriggerKind, type Turn, type TurnResult, UNAVAILABLE_COMPONENT_REASON, type UnavailableAgentManifest, type UnavailableDisplayComponent, type WarbleIr, type WhenGuardOut, aggregateTrace, appendTurn, buildAgentManifest, buildChatRequest, buildDispatchPlan, buildManifest, buildStepMessages, buildToolDriverPrompt, buildTurnPrompt, buildUnavailableAgentManifest, callOpenAiCompat, collectRequiredCapabilities, createChatSession, createSessionState, decideClarify, discoverClaudeModels, dispatch, distillFollowup, distinctProviders, distinctTiers, emitAgentModule, extractCompletionText, inspectNodeCapabilities, isKnownTarget, knownTargetNames, lastResolvedIntent, lastSessionId, localProfile, makeReadOnlyGuard, parseIr, parseRenderFlavor, planProviderRouting, prepareDispatch, prepareDisplayManifest, profileFor, renderEnvelope, resolveCapabilities, resolveNodeCapabilities, resolveProjectCwd, resolveStagedSteps, runDispatch, runHybridTool, shouldSplitPerStepTier, usesLocalProvider };