@1kbirds/chidori 0.1.26 → 3.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 CHANGED
@@ -1,10 +1,142 @@
1
- # Chidori
1
+ # chidori TypeScript SDK
2
2
 
3
- ## Generating Python Client
4
- - maturin develop --features python
3
+ Zero-dependency TypeScript client for a running `chidori serve` instance.
4
+ Uses the global `fetch` (Node 18+, browsers). Mirrors the Python SDK.
5
5
 
6
- ## Generating Nodejs Client
7
- - npm run build-release
6
+ > **This package is not the runtime.** It's an optional HTTP client for driving
7
+ > the Chidori **runtime** — the `chidori` binary — from a TypeScript app. You
8
+ > don't need it to write or run agents (those are plain `.ts` files the runtime
9
+ > executes directly). Install the runtime separately, no Rust toolchain needed:
10
+ > `curl -fsSL https://raw.githubusercontent.com/ThousandBirdsInc/chidori/main/scripts/install.sh | sh`
11
+ > — see the [project README](https://github.com/ThousandBirdsInc/chidori#%EF%B8%8F-quick-start).
8
12
 
13
+ ## Install
9
14
 
10
- Python patterns were implemented while referring to https://github.com/pydantic/pydantic-core/tree/main/python/pydantic_core
15
+ The package is published to npm as
16
+ [`@1kbirds/chidori`](https://www.npmjs.com/package/@1kbirds/chidori). The
17
+ unscoped `chidori` npm name belongs to an unrelated project, so **always import
18
+ the scoped name** — never `npm install chidori`:
19
+
20
+ ```bash
21
+ npm install @1kbirds/chidori
22
+ ```
23
+
24
+ ```ts
25
+ import { AgentClient, Checkpoint } from "@1kbirds/chidori";
26
+ ```
27
+
28
+ To build from source instead:
29
+
30
+ ```bash
31
+ cd sdk/typescript
32
+ npm install
33
+ npm run build
34
+ ```
35
+
36
+ ### Authoring agents and tools
37
+
38
+ Agent and tool files run *inside* the Chidori runtime, not as a normal Node
39
+ program. They get their authoring types — and the `chidori`/`run` globals — from
40
+ the **virtual** module `chidori:agent`:
41
+
42
+ ```ts
43
+ /// <reference types="@1kbirds/chidori/agent-env" />
44
+ import type { Chidori, ToolDefinition } from "chidori:agent";
45
+ ```
46
+
47
+ There is no installable package behind `chidori:agent`; it is a URL-style scheme
48
+ (like `node:fs`) that the runtime strips and injects at execution time, so the
49
+ unrelated `chidori` npm package can never be pulled in by mistake. The
50
+ `/// <reference …>` line (or a `compilerOptions.types: ["@1kbirds/chidori/agent-env"]`
51
+ entry in `tsconfig.json`) gives editors and `tsc` the types while you author;
52
+ the runtime itself needs nothing installed.
53
+
54
+ ## Usage
55
+
56
+ ```ts
57
+ import { AgentClient, Checkpoint, isSignalQueued } from "@1kbirds/chidori";
58
+
59
+ const client = new AgentClient("http://localhost:8080");
60
+
61
+ // Run an agent
62
+ const session = await client.run({ document: "Rust is a systems language." });
63
+ console.log(session.output);
64
+
65
+ // Save and replay a checkpoint — zero LLM calls on replay
66
+ const checkpoint = await session.checkpoint();
67
+ const replayed = await client.replay(checkpoint);
68
+
69
+ // Durable TypeScript runs may include snapshot metadata in the checkpoint.
70
+ // The manifest is safe to inspect; raw VM snapshot bytes remain server-side.
71
+ if (checkpoint.snapshotManifest) {
72
+ console.log(checkpoint.snapshotManifest.pending?.kind);
73
+ console.log(checkpoint.snapshotManifest.abi.engine_fork);
74
+ }
75
+
76
+ const manifest = await client.getSnapshotManifest(session.id);
77
+ console.log(manifest.policy.typescript_imports);
78
+
79
+ // Live streaming: host calls, prompt stream deltas, then `done`
80
+ for await (const evt of client.stream({ document: "hi" })) {
81
+ if (evt.type === "call") console.log(evt.record.function);
82
+ if (evt.type === "prompt_delta" && evt.prompt_type === "progress") {
83
+ process.stdout.write(evt.delta);
84
+ }
85
+ if (evt.type === "done") console.log(evt.status, evt.output);
86
+ }
87
+
88
+ // Paused sessions (from input())
89
+ if (session.status === "paused") {
90
+ const resumed = await client.resume(session.id, "yes");
91
+ console.log(resumed.output);
92
+ }
93
+
94
+ // Multiplayer signals (from chidori.signal / pollSignal): deliver
95
+ // { name, payload?, from? } to a run.
96
+ const result = await client.signal(session.id, {
97
+ name: "review",
98
+ payload: { decision: "approve", notes: "LGTM" },
99
+ from: { kind: "human", id: "mara" },
100
+ });
101
+ if (isSignalQueued(result)) {
102
+ // run wasn't paused-waiting on this name → enqueued in the durable mailbox (202)
103
+ console.log("queued at delivery_seq", result.delivery_seq);
104
+ } else {
105
+ // run was paused-waiting on this name → resolved + resumed (200)
106
+ console.log(result.status, result.output);
107
+ }
108
+ ```
109
+
110
+ See the top-level `sdk/python/chidori` for the Python equivalent.
111
+
112
+ ## Snapshot-aware checkpoints
113
+
114
+ `Checkpoint` contains the replay call log plus optional `snapshotManifest`
115
+ metadata. The manifest records the runtime ABI, deterministic policy, source
116
+ hashes, pending host operation, and snapshot file name. Clients can use it to
117
+ display durable-resume state or diagnose why resume is blocked without handling
118
+ the raw `runtime.snapshot` VM bytes.
119
+
120
+ `client.replay(checkpoint)` uses the call log for deterministic replay. Durable
121
+ resume is exposed through `client.resume(sessionId, response)` for paused
122
+ sessions, recovering through persisted host-promise metadata and the replay
123
+ journal. Replay **is** the resume mechanism by design — the QuickJS live-VM
124
+ snapshot path was removed in #39, not merely deferred, so the manifest carries
125
+ journal/scaffold metadata rather than serialized VM bytes.
126
+
127
+ Use `client.getSnapshotManifest(sessionId)` when a UI needs only snapshot
128
+ metadata. The endpoint never returns the binary VM snapshot.
129
+
130
+ ## Tests
131
+
132
+ The SDK ships a dependency-free test suite (Node's built-in `node:test`
133
+ runner) that drives `AgentClient` against a stdlib `node:http` mock server,
134
+ covering run/replay/resume/signal, SSE stream parsing, checkpoint
135
+ serialization, and error handling:
136
+
137
+ ```bash
138
+ npm test # builds, then runs node --test test/*.test.mjs
139
+ ```
140
+
141
+ End-to-end coverage against a real `chidori serve` binary lives in the Python
142
+ SDK integration tests (`sdk/python/tests/`).
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Ambient types for the virtual `chidori:agent` module.
3
+ *
4
+ * Agent and tool files written for the Chidori runtime import their authoring
5
+ * types — and, in the global style, the `chidori`/`run` values — from
6
+ * `chidori:agent`:
7
+ *
8
+ * ```ts
9
+ * import type { Chidori, ToolDefinition } from "chidori:agent";
10
+ * // …or the global style:
11
+ * import { chidori, run } from "chidori:agent";
12
+ * ```
13
+ *
14
+ * There is no installable package behind that specifier. It is a virtual module
15
+ * the runtime injects, much like `node:fs` or `bun:test`: the runtime strips the
16
+ * import and supplies the real values when it executes the file, so nothing is
17
+ * resolved from npm. Crucially, the unrelated `chidori` package on npm can never
18
+ * be pulled in by mistake, because `chidori:agent` is not a registry name at all.
19
+ *
20
+ * This file exists only so editors and `tsc` can type agent files. Pull it into
21
+ * a project with a triple-slash reference at the top of an agent or tool file:
22
+ *
23
+ * ```ts
24
+ * /// <reference types="@1kbirds/chidori/agent-env" />
25
+ * import type { Chidori } from "chidori:agent";
26
+ * ```
27
+ *
28
+ * or once, project-wide, via tsconfig `compilerOptions.types`:
29
+ * `["@1kbirds/chidori/agent-env"]`.
30
+ *
31
+ * NOTE: this must stay a script file (no top-level `import`/`export`). A bare
32
+ * `declare module "chidori:agent"` is a *global* ambient declaration; adding a
33
+ * top-level `export` would turn it into a module augmentation and fail to
34
+ * resolve, since there is no real `chidori:agent` module to augment.
35
+ */
36
+ declare module "chidori:agent" {
37
+ export * from "@1kbirds/chidori/agent";
38
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,487 @@
1
+ /**
2
+ * Type declarations for TypeScript agents executed inside the Chidori runtime.
3
+ *
4
+ * These are authoring-time types only. The runtime injects the concrete
5
+ * `chidori` host object when it evaluates an agent or tool module.
6
+ */
7
+ export type AgentJson = null | boolean | number | string | AgentJson[] | {
8
+ [key: string]: AgentJson;
9
+ };
10
+ export type JsonObject = {
11
+ [key: string]: AgentJson;
12
+ };
13
+ export interface JsonSchema {
14
+ type?: "object" | "array" | "string" | "number" | "integer" | "boolean" | "null";
15
+ description?: string;
16
+ properties?: Record<string, JsonSchema>;
17
+ items?: JsonSchema;
18
+ required?: string[];
19
+ enum?: AgentJson[];
20
+ default?: AgentJson;
21
+ additionalProperties?: boolean | JsonSchema;
22
+ [keyword: string]: unknown;
23
+ }
24
+ export interface ToolDefinition {
25
+ name: string;
26
+ description: string;
27
+ parameters: JsonSchema & {
28
+ type: "object";
29
+ };
30
+ }
31
+ export type PromptStreamType = "progress" | "draft" | "subagent" | "final" | (string & {});
32
+ /** Provider prompt-cache lifetime for a cached prefix. */
33
+ export type CacheTtl = "5m" | "1h";
34
+ export interface PromptOptions {
35
+ type?: PromptStreamType;
36
+ system?: string;
37
+ model?: string;
38
+ maxTokens?: number;
39
+ max_tokens?: number;
40
+ maxTurns?: number;
41
+ max_turns?: number;
42
+ temperature?: number;
43
+ tools?: string[];
44
+ format?: "json" | (string & {});
45
+ stream?: boolean;
46
+ /**
47
+ * Prompt-cache posture. Defaults to on (`"5m"`): the runtime marks the
48
+ * stable request head (system, tools, conversation prefix) so providers
49
+ * bill repeated prefixes at the cached rate. `false` disables marking for
50
+ * this call; `"1h"` (or `{ ttl: "1h" }`) requests the extended TTL.
51
+ * Caching never changes a response — only how it is billed.
52
+ */
53
+ cache?: boolean | CacheTtl | {
54
+ ttl?: CacheTtl;
55
+ };
56
+ }
57
+ /** Structured response from `Context.respond()` — mirrors the provider turn. */
58
+ export interface LlmResponseJson {
59
+ content: string;
60
+ blocks: AgentJson[];
61
+ tool_calls: {
62
+ id: string;
63
+ name: string;
64
+ input: AgentJson;
65
+ }[];
66
+ stop_reason: string;
67
+ input_tokens: number;
68
+ output_tokens: number;
69
+ cache_creation_tokens: number;
70
+ cache_read_tokens: number;
71
+ }
72
+ /** Options for `Context.compact()` — explicit, opt-in window compaction. */
73
+ export interface CompactOptions {
74
+ /** How many of the newest conversation turns to keep verbatim (default 2). */
75
+ keepTurns?: number;
76
+ /**
77
+ * Skip compaction (pure no-op, no host call) while `estimateTokens()` is at
78
+ * or under this budget — lets a loop call `compact()` unconditionally.
79
+ */
80
+ budgetTokens?: number;
81
+ /** Model for the summarization prompt (defaults like `prompt()`). */
82
+ model?: string;
83
+ /** System instructions for the summarizer (a faithful-brief default). */
84
+ instructions?: string;
85
+ /** `maxTokens` for the summarization prompt. */
86
+ maxTokens?: number;
87
+ /** Cache posture for the summarization prompt (see `PromptOptions.cache`). */
88
+ cache?: boolean | CacheTtl | {
89
+ ttl?: CacheTtl;
90
+ };
91
+ /** TTL of the fresh cache breakpoint placed on the summary (default "5m"). */
92
+ ttl?: CacheTtl;
93
+ }
94
+ /**
95
+ * An immutable, content-addressed, turn-structured prompt context.
96
+ *
97
+ * Builder methods return a NEW context that structurally shares this one's
98
+ * segments — `base.user("a")` and `base.user("b")` are independent and share
99
+ * `base`'s prefix — which keeps cache prefixes stable and makes forks cheap.
100
+ * Only `prompt()` / `respond()` perform a durable host call.
101
+ *
102
+ * ```ts
103
+ * const base = chidori.context()
104
+ * .system("You are a policy analyst.")
105
+ * .doc("corpus", corpusText)
106
+ * .cacheBreakpoint("1h");
107
+ * let ctx = base;
108
+ * for (const q of questions) {
109
+ * ctx = ctx.user(q);
110
+ * const { text, context } = await ctx.prompt();
111
+ * ctx = context; // assistant turn appended, prefix still shared
112
+ * }
113
+ * ```
114
+ */
115
+ export interface Context {
116
+ system(text: string): Context;
117
+ /** Expose registered tools (by name, resolved like `prompt({ tools })`). */
118
+ tools(names: string[]): Context;
119
+ /** A large stable reference block, labelled for the trace. */
120
+ doc(label: string, text: string): Context;
121
+ user(text: string): Context;
122
+ assistant(text: string): Context;
123
+ toolResult(id: string, content: string, isError?: boolean): Context;
124
+ /**
125
+ * Freeze everything appended so far as a cacheable prefix (one provider
126
+ * cache breakpoint). Providers cap breakpoints, so marks are coalesced —
127
+ * latest wins. Most authors never need this: stable heads are auto-marked.
128
+ */
129
+ cacheBreakpoint(ttl?: CacheTtl): Context;
130
+ /** Send this context; returns the text and the context extended with the
131
+ * assistant turn (including any internal tool-use exchange). */
132
+ prompt(options?: PromptOptions): Promise<{
133
+ text: string;
134
+ context: Context;
135
+ }>;
136
+ /** Single structured turn for author-driven tool loops. */
137
+ respond(options?: PromptOptions): Promise<{
138
+ response: LlmResponseJson;
139
+ context: Context;
140
+ }>;
141
+ /**
142
+ * Summarize the older conversation turns into one durable summary segment
143
+ * (via a recorded `prompt` host call, so it replays deterministically) and
144
+ * return a new context: stable head + summary + fresh cache breakpoint +
145
+ * the kept newest turns. Never automatic — compaction changes what the
146
+ * model sees, so it is always an explicit author decision. Returns this
147
+ * context unchanged (without a host call) when there is nothing to compact
148
+ * or the context is within `budgetTokens`.
149
+ */
150
+ compact(options?: CompactOptions): Promise<Context>;
151
+ /** Stable content hash of the request this context would assemble. */
152
+ digest(options?: PromptOptions): string;
153
+ /** Rough local token estimate for window budgeting. */
154
+ estimateTokens(): number;
155
+ }
156
+ /** One recorded exchange in a {@link Conversation} transcript. */
157
+ export interface ConversationTurn {
158
+ role: "user" | "assistant";
159
+ text: string;
160
+ }
161
+ /** Options for `chidori.conversation()`. */
162
+ export interface ConversationOptions {
163
+ /** System prompt — frozen once as the conversation's cacheable prefix. */
164
+ system?: string;
165
+ /** Tool names available on every turn (resolved like `prompt({ tools })`). */
166
+ tools?: string[];
167
+ /** Default stream label for each turn's prompt (default `"final"`). */
168
+ type?: PromptStreamType;
169
+ /** Default model for each turn (a per-turn override still wins). */
170
+ model?: string;
171
+ /** Default output token cap for each turn. */
172
+ maxTokens?: number;
173
+ /** Default sampling temperature for each turn. */
174
+ temperature?: number;
175
+ /** Default cache posture for each turn (see {@link PromptOptions.cache}). */
176
+ cache?: boolean | CacheTtl | {
177
+ ttl?: CacheTtl;
178
+ };
179
+ /** TTL of the cache breakpoint frozen over the system/tools head. */
180
+ cacheTtl?: CacheTtl;
181
+ /**
182
+ * Opt-in window management: when set, each turn first runs the same budgeted
183
+ * `Context.compact()` — a pure no-op until the running tail exceeds budget,
184
+ * then the older turns fold into one recorded summary segment.
185
+ */
186
+ compact?: CompactOptions;
187
+ }
188
+ /** Options for `Conversation.loop()` — an interactive `input()`-driven loop. */
189
+ export interface ConversationLoopOptions {
190
+ /** Prompt shown to the human each turn (or a function of the turn index). */
191
+ prompt?: string | ((turn: number) => string);
192
+ /** Extra options forwarded to `chidori.input()` (defaults to `{ type: "message" }`). */
193
+ inputOptions?: InputOptions;
194
+ /** Words that end the loop, case-insensitive (default `["exit", "quit"]`). */
195
+ exit?: string | string[];
196
+ /** Hard cap on the number of exchanges before returning. */
197
+ maxTurns?: number;
198
+ /** Skip blank input lines instead of sending them (default `true`). */
199
+ skipEmpty?: boolean;
200
+ /** Per-turn prompt options applied to every `say()` in the loop. */
201
+ turn?: PromptOptions;
202
+ /** Called with the assistant reply (and the user message) after each turn. */
203
+ onReply?: (reply: string, message: string) => void | Promise<void>;
204
+ /** Return `true` after a turn to end the loop (checked after `onReply`). */
205
+ until?: (message: string, reply: string) => boolean;
206
+ }
207
+ /**
208
+ * A small stateful wrapper over {@link Context} for the most common shape — a
209
+ * multi-turn chat assistant. It owns the running context (system + tools frozen
210
+ * as a cacheable prefix) and threads each turn through it, so you write
211
+ * `chat.say(message)` instead of re-plumbing `ctx = (await
212
+ * ctx.user(message).prompt()).context` by hand. Every turn is still one durable
213
+ * `prompt`/`respond` host call that replays for free.
214
+ *
215
+ * ```ts
216
+ * const chat = chidori.conversation({ system: "You are concise." });
217
+ * const a = await chat.say("Hi, who are you?");
218
+ * const b = await chat.say("What can you help with?");
219
+ * // or drive it interactively:
220
+ * const transcript = await chat.loop({ prompt: "you>" });
221
+ * ```
222
+ */
223
+ export interface Conversation {
224
+ /** The underlying immutable context, for dropping to the lower-level API. */
225
+ readonly context: Context;
226
+ /** Number of completed exchanges (user+assistant pairs) so far. */
227
+ readonly length: number;
228
+ /** The transcript so far as plain `{ role, text }` entries. */
229
+ history(): ConversationTurn[];
230
+ /** Send one user message; resolves to the assistant's reply text. */
231
+ say(message: string, options?: PromptOptions): Promise<string>;
232
+ /**
233
+ * Like `say()`, but resolves to the structured response (`tool_calls`,
234
+ * `blocks`) for author-driven tool loops. Append tool results with
235
+ * `chat.context.toolResult(...)`, then call `say()` again.
236
+ */
237
+ respond(message: string, options?: PromptOptions): Promise<LlmResponseJson>;
238
+ /**
239
+ * Drive an interactive loop: read a human message via `chidori.input()`
240
+ * (terminal stdin under `chidori run`, a paused session resume under `chidori
241
+ * serve`), reply with `say()`, and repeat until the user types an exit word
242
+ * or `until` returns true. Resolves to the full transcript.
243
+ */
244
+ loop(options?: ConversationLoopOptions): Promise<ConversationTurn[]>;
245
+ }
246
+ export interface InputOptions {
247
+ type?: string;
248
+ default?: string;
249
+ choices?: string[];
250
+ }
251
+ /**
252
+ * Who delivered a signal. `kind` distinguishes a human participant from a peer
253
+ * agent; `id` is the participant identity; `runId` is set when an agent sends
254
+ * (its own run id), so agent-to-agent coordination is attributable in the trace.
255
+ */
256
+ export interface SignalSender {
257
+ kind: "human" | "agent";
258
+ id: string;
259
+ runId?: string;
260
+ }
261
+ /**
262
+ * A named message delivered into a run mid-flight (`docs/signals.md` §6.1). The
263
+ * inverse of `input()`: an outside party (human or agent) pushes
264
+ * `{ name, payload, from }` at an agent-declared listen point. Every signal is
265
+ * recorded in the call log, so the multiplayer session replays deterministically.
266
+ */
267
+ export interface Signal<T = AgentJson> {
268
+ name: string;
269
+ payload: T;
270
+ from: SignalSender;
271
+ }
272
+ export interface SignalOptions {
273
+ /**
274
+ * Resolve to a {@link SignalTimeout} sentinel after this many milliseconds
275
+ * instead of waiting forever. The deadline is enforced by the supervising
276
+ * server while the run idles; the recorded result (signal or sentinel)
277
+ * replays deterministically. Discriminate with `"timedOut" in result`.
278
+ */
279
+ timeoutMs?: number;
280
+ }
281
+ /**
282
+ * The sentinel a `timeoutMs` listen point resolves to when the deadline passes
283
+ * with no matching delivery (`docs/signals.md` §16, pinned:
284
+ * resolve-to-sentinel rather than reject). `name` is the single awaited name,
285
+ * or `null` for a multi-name `signalAny`.
286
+ */
287
+ export interface SignalTimeout {
288
+ name: string | null;
289
+ payload: null;
290
+ from: null;
291
+ timedOut: true;
292
+ }
293
+ export interface ParallelOptions {
294
+ concurrency?: number;
295
+ }
296
+ /**
297
+ * One `chidori.branch` variant (`docs/branching-execution.md` §6.1). A branch
298
+ * runs its own continuation source module from the parent's anchored state —
299
+ * not a re-run of the parent agent — so `source` is required.
300
+ */
301
+ export interface BranchVariant {
302
+ /** Branch label, shown in outcomes and the trace. Defaults to `branch-<k>`. */
303
+ label?: string;
304
+ /** Branch source module path, resolved like `callAgent` paths. */
305
+ source: string;
306
+ /** State handed to the branch as its run input. Defaults to `{}`. */
307
+ input?: AgentJson;
308
+ }
309
+ export type BranchStatus = "completed" | "paused" | "failed";
310
+ /** The result of one branch sub-run, returned for comparison (not merged). */
311
+ export interface BranchOutcome<T extends AgentJson = AgentJson> {
312
+ label: string;
313
+ /**
314
+ * `<parent run id>-op<branch seq>-branch-<k>` — identifies the branch
315
+ * sub-run, including for out-of-band `chidori branch-resume` /
316
+ * `branch-rerun` against its persisted store.
317
+ */
318
+ branchId: string;
319
+ status: BranchStatus;
320
+ /** The branch's output, when `status` is `"completed"`. */
321
+ output?: T;
322
+ /** What the branch is waiting on, when `status` is `"paused"`. */
323
+ pendingPrompt?: string;
324
+ /** The failure message, when `status` is `"failed"`. */
325
+ error?: string;
326
+ }
327
+ export interface BranchOptions {
328
+ /**
329
+ * Maximum branches running live at once (cost cap). Defaults to 1 —
330
+ * sequential. Higher values run variants in concurrent waves; outcome
331
+ * order always follows variant order.
332
+ */
333
+ concurrency?: number;
334
+ }
335
+ export interface RetryOptions {
336
+ attempts?: number;
337
+ delayMs?: number;
338
+ backoff?: "fixed" | "exponential";
339
+ }
340
+ export interface TryCallResult<T> {
341
+ ok: boolean;
342
+ value?: T;
343
+ error?: string;
344
+ }
345
+ export interface TemplateOptions {
346
+ source?: "file" | "inline";
347
+ }
348
+ export type MemoryAction = "get" | "set" | "delete" | "list" | "clear";
349
+ export type WorkspaceFileStatus = "complete" | "writing" | "failed";
350
+ export interface WorkspaceEntry {
351
+ path: string;
352
+ status: WorkspaceFileStatus;
353
+ sha256: string;
354
+ bytes: number;
355
+ language?: string | null;
356
+ attempt?: number | null;
357
+ updatedAt?: string | null;
358
+ }
359
+ export interface WorkspaceListOptions {
360
+ completeOnly?: boolean;
361
+ }
362
+ export interface WorkspaceWriteOptions {
363
+ language?: string;
364
+ }
365
+ export interface WorkspaceHost {
366
+ list(options?: WorkspaceListOptions): Promise<WorkspaceEntry[]>;
367
+ read(path: string): Promise<string>;
368
+ write(path: string, content: string, options?: WorkspaceWriteOptions): Promise<WorkspaceEntry>;
369
+ delete(path: string, reason?: string): Promise<void>;
370
+ remove(path: string, reason?: string): Promise<void>;
371
+ manifest(): Promise<AgentJson>;
372
+ }
373
+ export type TypeScriptImportPolicy = "none" | "relative" | "project";
374
+ export type DatePolicy = "disabled" | "fixed" | "host";
375
+ export type RandomPolicy = "disabled" | "seeded" | "host";
376
+ export type MapSetSnapshotPolicy = "reject" | "serialize";
377
+ export interface RuntimePolicyConfig {
378
+ typescript?: {
379
+ imports?: TypeScriptImportPolicy;
380
+ };
381
+ runtime?: {
382
+ date?: DatePolicy;
383
+ random?: RandomPolicy;
384
+ };
385
+ snapshot?: {
386
+ mapsSets?: MapSetSnapshotPolicy;
387
+ };
388
+ }
389
+ export interface Chidori {
390
+ workspace: WorkspaceHost;
391
+ /** Start an immutable multi-turn prompt context (optionally seeded). */
392
+ context(seed?: {
393
+ system?: string;
394
+ tools?: string[];
395
+ }): Context;
396
+ /**
397
+ * Start a multi-turn chat assistant — a stateful wrapper over `context()`
398
+ * that owns the running dialogue. Send turns with `chat.say(message)` or drive
399
+ * an interactive `input()` loop with `chat.loop()`.
400
+ */
401
+ conversation(options?: ConversationOptions): Conversation;
402
+ prompt(text: string, options?: PromptOptions): Promise<string>;
403
+ input(message: string, options?: InputOptions): Promise<string>;
404
+ /**
405
+ * Pause at a named listen point until a matching signal is delivered (or one
406
+ * is already queued in the durable mailbox), then resolve to
407
+ * `{ name, payload, from }`. The inverse of `input()`: the run idles cheaply
408
+ * on disk and an outside party delivers via `POST /sessions/{id}/signal`.
409
+ */
410
+ signal<T = AgentJson>(name: string): Promise<Signal<T>>;
411
+ signal<T = AgentJson>(name: string, options: SignalOptions): Promise<Signal<T> | SignalTimeout>;
412
+ /**
413
+ * Non-blocking: consume a queued signal of this name if present, else resolve
414
+ * to `null`. Records the result (value or null) at this seq so replay is
415
+ * deterministic.
416
+ */
417
+ pollSignal<T = AgentJson>(name: string): Promise<Signal<T> | null>;
418
+ /**
419
+ * Fan-in: pause until ANY of the named signals is delivered (or one is
420
+ * already queued in the durable mailbox). Resolves to the bare consumed
421
+ * signal — its `name` says which fired. Pre-arrived candidates are consumed
422
+ * in delivery order (lowest `delivery_seq` across the whole name set).
423
+ */
424
+ signalAny<T = AgentJson>(names: string[]): Promise<Signal<T>>;
425
+ signalAny<T = AgentJson>(names: string[], options: SignalOptions): Promise<Signal<T> | SignalTimeout>;
426
+ /**
427
+ * Durable value checkpoint: run `fn` once and journal its JSON-serializable
428
+ * result; on replay/resume the recorded value (or error) is returned without
429
+ * re-running `fn`. Wrap expensive deterministic computation in a step so a
430
+ * resumed run does not re-pay it. The callback must be pure, synchronous
431
+ * compute — host effects (`chidori.*`), captured randomness, filesystem
432
+ * writes, timers, and async callbacks are refused inside a step.
433
+ */
434
+ step<T extends AgentJson = AgentJson>(name: string, fn: () => T): Promise<T>;
435
+ callAgent<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson>(path: string, input?: TInput): Promise<TOutput>;
436
+ /**
437
+ * Fork the run into one sub-run per variant from the current anchored state
438
+ * (the VFS plus each variant's explicit `input`), run each variant's own
439
+ * source module, and return every outcome so the agent can compare and pick.
440
+ * The whole fan-out is one recorded durable call: a replay of this run
441
+ * returns the outcomes from cache without re-running the branches.
442
+ */
443
+ branch<T extends AgentJson = AgentJson>(variants: BranchVariant[], options?: BranchOptions): Promise<BranchOutcome<T>[]>;
444
+ tool<TArgs extends JsonObject = JsonObject, TResult extends AgentJson = AgentJson>(name: string, args?: TArgs): Promise<TResult>;
445
+ parallel<TTasks extends readonly (() => Promise<unknown>)[]>(tasks: TTasks, options?: ParallelOptions): Promise<{
446
+ [Index in keyof TTasks]: Awaited<ReturnType<TTasks[Index]>>;
447
+ }>;
448
+ retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
449
+ tryCall<T>(fn: () => Promise<T>): Promise<TryCallResult<T>>;
450
+ template(pathOrText: string, vars?: JsonObject, options?: TemplateOptions): Promise<string>;
451
+ log(message: string, fields?: JsonObject): Promise<void>;
452
+ memory<T extends AgentJson = AgentJson>(action: MemoryAction, key?: string, value?: T, options?: JsonObject): Promise<T | AgentJson[] | null>;
453
+ checkpoint(label?: string, data?: AgentJson): Promise<void>;
454
+ }
455
+ export type AgentFunction<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson> = (input: TInput) => TOutput | Promise<TOutput>;
456
+ export type ToolFunction<TArgs extends JsonObject = JsonObject, TResult extends AgentJson = AgentJson> = (args: TArgs) => TResult | Promise<TResult>;
457
+ /**
458
+ * The chidori host object — the durable surface your agents and tools call
459
+ * (`chidori.log`, `chidori.prompt`, `chidori.tool`, `chidori.input`, …).
460
+ *
461
+ * Import it for typed access; the runtime strips this import and supplies the
462
+ * real object at execution time, so there's no actual module dependency (and no
463
+ * need for a `(input, chidori)` second parameter):
464
+ *
465
+ * ```ts
466
+ * import { chidori, run } from "chidori:agent";
467
+ * run(async (input: { topic: string }) => {
468
+ * await chidori.log("starting", { topic: input.topic });
469
+ * return { ok: true };
470
+ * });
471
+ * ```
472
+ *
473
+ * Accessing it from a plain import outside the runtime throws.
474
+ */
475
+ export declare const chidori: Chidori;
476
+ /**
477
+ * Define the agent entrypoint. Call it once at the top level of an agent module
478
+ * with your handler; the runtime invokes the handler with the run input and
479
+ * uses its return value as the output. This replaces the old "export a function
480
+ * named `agent`" convention.
481
+ *
482
+ * ```ts
483
+ * import { run } from "chidori:agent";
484
+ * run(async (input) => ({ greeting: `hello ${input.name}` }));
485
+ * ```
486
+ */
487
+ export declare function run<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson>(handler: AgentFunction<TInput, TOutput>): void;