@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.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # claude-agent-sdk
2
+
3
+ The second Warble back-end target: the **Claude Agent SDK** `query()` loop (TypeScript/Node).
4
+
5
+ Where `claude-code-cli` (Rust) emits *static* agent files, this back-end drives the SDK's **in-loop**
6
+ `query({options})` at runtime, so it is bound to the SDK's language (TypeScript). It consumes the
7
+ **same IR** (`docs/spec/ir-schema.md`) as every other back-end — it never links the Rust core; the
8
+ `ir.json` JSON document is the only thing crossing between them.
9
+
10
+ ## What this back-end proves
11
+
12
+ - **IR JSON is a real language-neutral seam** — a Rust front-end (`warble compile`) emits `ir.json`;
13
+ this **TypeScript** back-end consumes the identical file, with no shared types and no Rust link.
14
+ (The file target can't show this — it lives in the same Rust workspace as the compiler.)
15
+ - **Thin, borrow-the-loop back-end** — the dispatcher only maps three orthogonal IR enums to
16
+ `query({options})`; the agent loop, permissions, sandbox, and tool calls are all borrowed from the
17
+ Agent SDK. Handler count ≈ `3 realization + 4 outcome + 3 trigger`, never per-component.
18
+ - **Closes three file-target wall-hits**:
19
+ - **#1 per-step tier** → realized **in-loop** via SDK `agents` (per-agent model), no static
20
+ subagent files. `llm:per_step_tier` is *native* on this target (vs *realize-via(subagents)* on
21
+ the file target).
22
+ - **#3 guardrail runtime enforcement** → the `read_only_execution` guardrail is enforced at
23
+ runtime by a `canUseTool` callback that inspects every tool call and denies escapes with a
24
+ reason fed back to the model — not the file target's static allow/deny strings.
25
+ - **#5 per-step observability/trace** → per-step usage + per-tier cost/latency are captured from
26
+ the `query()` message stream (`modelUsage`) into `trace.json`.
27
+ - **One renderer across back-ends** — the render step shells out to `warble render` (the Rust
28
+ deterministic reference renderer); HTML is not re-implemented in TS.
29
+
30
+ MVP realizes nearly the full IR surface: all three `realization_kind`s (`skill`/`tool`/
31
+ `gated-tool`), three of four `effect.outcome.kind`s (`none`/`assertion`/`mutation`), and two of
32
+ three `trigger.kind`s (`one_shot`/`scheduled`). Scoped to those three IR enums, only the `dispatch`
33
+ outcome and the `event` trigger remain documented loud-failing extension points ("wall-hits") —
34
+ handler count still stays ≈`3 realization + 4 outcome + 3 trigger`, never growing per-component; an
35
+ unrealized arm is an `options.ts` early-throw, not a missing code path.
36
+
37
+ A fourth, orthogonal axis has its own loud-fail: `llm:per_step_provider` (hybrid cloud+local
38
+ routing) loud-fails for any hybrid-staged step under a non-`none` render gate. Where it *is*
39
+ supported, there are two realizations — a staged-executor and an in-process `dispatch_step` tool —
40
+ selected by `WARBLE_HYBRID_MODE`; see `docs/spec/capability-model.md` §7.2 for the selection rule
41
+ and both realizations.
42
+
43
+ ## Target
44
+
45
+ One `engine × mode` target: **`claude-agent-sdk:local`** — the local `@anthropic-ai/claude-agent-sdk`
46
+ (subscription login, compute on your machine). Its capability profile
47
+ (`src/targets.ts`) is owned by *this* back-end in TypeScript — the shared thing across back-ends is
48
+ the IR + the capability-model semantics, not the profile data.
49
+
50
+ ## Three ways to use it
51
+
52
+ Same IR, same mapping — pick the surface that fits your integration.
53
+
54
+ ### 1. CLI
55
+
56
+ ```bash
57
+ npm install
58
+
59
+ # offline: build + inspect the assembled query() options without calling the SDK
60
+ npx tsx src/cli.ts dispatch ../../examples/render-demo/ir.golden.json "orders overview" \
61
+ --out ./run --dry-run
62
+
63
+ # live: drive the Agent SDK loop, enforce read-only at runtime, render the dashboard
64
+ npx tsx src/cli.ts dispatch ../../examples/render-demo/ir.golden.json "orders overview" \
65
+ --out ./run --render-flavor programmatic
66
+ # → ./run/result.txt, ./run/trace.json, ./run/dashboard.html, ./run/capability-report.json
67
+
68
+ # inspect the resolved plan without dispatching (agents/steps/tiers/capabilities/guardrails)
69
+ npx tsx src/cli.ts manifest ../../examples/render-demo/ir.golden.json --out ./run/manifest.json
70
+
71
+ # multi-turn chat over one component (stdin, line-by-line; Ctrl-D to end)
72
+ npx tsx src/cli.ts chat ../../examples/render-demo/ir.golden.json --component answer_query
73
+
74
+ # authenticated subscription picker data; no user prompt, tools, or MCP session is created
75
+ npx tsx src/cli.ts list-models --project /absolute/path/to/project --timeout 10000
76
+ ```
77
+
78
+ Flags: `--target` (default `claude-agent-sdk:local`), `--models-config <yaml>` or inline
79
+ `--strong/--cheap/--orchestrator`, `--render-flavor programmatic|prompt` (default programmatic),
80
+ `--project <dir>` (override the bound wren project cwd), `--warble-bin <path>`, `--out <path>`,
81
+ `--max-turns N`, `--title`, `--dry-run` (`dispatch` only), `--standalone` (`emit` only).
82
+
83
+ `manifest` runs the same preparation as `emit` — no `question`, `query()` is never called — and
84
+ serializes the resolved agents/steps/tiers/capabilities/guardrails to stdout or `--out`, structurally
85
+ identical to the vercel back-end's bundle (see [`src/manifest.ts`](./src/manifest.ts)) — a consumer
86
+ can source a display from whichever back-end actually runs, instead of always reading the vercel
87
+ bundle target's output.
88
+
89
+ `chat` opens a multi-turn session ([`src/session.ts`](./src/session.ts), G1 — single profile, many
90
+ turns) over one component (`--component`, default `answer_query`), resuming the SDK session turn
91
+ over turn. `--stream-json` streams one `WarbleChatEvent` NDJSON line per event
92
+ ([`src/events.ts`](./src/events.ts)) instead of plain final-answer text, ending each turn with a
93
+ `{"t":"answer",…}` line; every turn also emits a `{"t":"session","id":…}` line — on success **and**
94
+ on a failed turn — so a caller can resume that conversation with `--resume <session-id>`.
95
+
96
+ `list-models` emits exactly one versioned JSON object for the currently authenticated Claude
97
+ subscription. It exposes only model ID, display name, and description; unavailable authentication,
98
+ runtime, timeout, or protocol states are returned as sanitized JSON. It uses an empty SDK input and
99
+ always cleans up its idle query, so it never sends a user turn or enables tools, MCP, or settings.
100
+
101
+ ### 2. Embed the library in your own TS app
102
+
103
+ `npm run build` emits `dist/` with types. Import and drive it in-process:
104
+
105
+ ```ts
106
+ import { dispatch } from "@warble/claude-agent-sdk";
107
+
108
+ const out = await dispatch(
109
+ { ir: fs.readFileSync("ir.json", "utf8"), question: "orders overview", irPath: "ir.json" },
110
+ { outDir: "./run" },
111
+ );
112
+ console.log(out.components[0].result.htmlPath, out.components[0].result.trace);
113
+ ```
114
+
115
+ For **full control of the loop**, stop at `prepareDispatch` and hand `plan.options` to the SDK's
116
+ `query()` yourself (add your own tools / MCP servers / permission strategy):
117
+
118
+ ```ts
119
+ import { prepareDispatch, makeReadOnlyGuard } from "@warble/claude-agent-sdk";
120
+ import { query } from "@anthropic-ai/claude-agent-sdk";
121
+
122
+ const { components } = prepareDispatch({ ir, question: "orders overview", irPath: "ir.json" });
123
+ const { plan } = components[0];
124
+ const { canUseTool } = makeReadOnlyGuard({ readOnly: plan.meta.readOnly, writeScope: null, cwd: plan.options.cwd! });
125
+ for await (const m of query({ prompt: plan.prompt, options: { ...plan.options, canUseTool, /* + your tools */ } })) {
126
+ /* your own streaming/UI */
127
+ }
128
+ ```
129
+
130
+ ### 3. Generate a TS agent module to check into your codebase (`emit`)
131
+
132
+ Freeze the resolved plan into an importable `.ts` (analogue of the file target emitting `.md`):
133
+
134
+ ```bash
135
+ # thin (default): generated module imports the runtime helpers from @warble/claude-agent-sdk
136
+ npx tsx src/cli.ts emit ../../examples/render-demo/ir.golden.json --out src/agents/dashboard.ts
137
+
138
+ # standalone (eject): inlines the guard + trace + render shell — only @anthropic-ai/claude-agent-sdk
139
+ # + the `warble` binary (for render) are needed; no @warble/* dependency
140
+ npx tsx src/cli.ts emit ../../examples/render-demo/ir.golden.json --out src/agents/dashboard.ts --standalone
141
+ ```
142
+
143
+ Each component becomes an exported `async function <verb>(question, opts?)` that drives the loop with
144
+ the frozen `query({options})`. The generated module type-checks under `--strict` in both modes.
145
+ Because `canUseTool` (guardrail) and render are live code, the **thin** output imports them from the
146
+ library; **standalone** inlines them (the `warble` binary is still used for render — that is the
147
+ renderer-reuse contract, not a TS dependency).
148
+
149
+ ## Dev
150
+
151
+ ```bash
152
+ npm run check-types # tsc --strict, no emit
153
+ npm test # node:test suite (offline; render test skips if `warble` isn't built)
154
+ npm run build # tsup → dist/ (ESM .js + .d.ts) for the library + CLI bin
155
+ ```
156
+
157
+ ## Runtime prerequisite for a full data e2e
158
+
159
+ A full run that returns **real numbers** needs the `wren` CLI on PATH and a wired connection. The
160
+ committed `examples/jaffle-wren` bundles the semantic layer **and** the `jaffle_shop.duckdb` file
161
+ itself, but `wren_project.yml` has no connection block yet, so it still isn't queryable as-shipped —
162
+ and `wren` is a separate install, the same runtime prerequisite the file target has. The SDK plumbing,
163
+ runtime guardrail enforcement, per-step-tier delegation, and deterministic render are all verified
164
+ independently of that data runtime (see `SDK-NOTES.md` and the test suite).