@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 +164 -0
- package/dist/chunk-F4QDKVP7.js +2844 -0
- package/dist/chunk-F4QDKVP7.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +288 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +1143 -0
- package/dist/index.js +123 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
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).
|