@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 +138 -6
- package/dist/agent-env.d.ts +38 -0
- package/dist/agent-env.js +1 -0
- package/dist/agent.d.ts +487 -0
- package/dist/agent.js +46 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.js +318 -0
- package/package.json +34 -33
- package/src/agent-env.ts +43 -0
- package/src/agent.ts +548 -0
- package/src/index.ts +580 -0
- package/CHANGELOG.md +0 -33
- package/Cargo.toml +0 -83
- package/build.rs +0 -7
- package/package_node/index.d.ts +0 -0
- package/package_node/index.js +0 -130
- package/pyproject.toml +0 -35
- package/src/lib.rs +0 -23
- package/src/translations/mod.rs +0 -9
- package/src/translations/nodejs.rs +0 -739
- package/src/translations/python.rs +0 -921
- package/src/translations/rust.rs +0 -737
- package/src/translations/shared.rs +0 -49
- package/src/translations/wasm.rs +0 -1
- package/tests/nodejs/chidori.test.js +0 -41
- package/tests/nodejs/nodeHandle.test.js +0 -15
- package/tests/python/test_chidori.py +0 -30
package/src/index.ts
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chidori TypeScript SDK — HTTP client for a running `chidori serve`
|
|
3
|
+
* instance. Mirrors the Python SDK (`sdk/python/chidori`). Zero runtime
|
|
4
|
+
* dependencies; uses the global `fetch` available in Node 18+ and browsers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SignalSender } from "./agent.js";
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
AgentFunction,
|
|
11
|
+
AgentJson,
|
|
12
|
+
CacheTtl,
|
|
13
|
+
Chidori,
|
|
14
|
+
CompactOptions,
|
|
15
|
+
Context,
|
|
16
|
+
Conversation,
|
|
17
|
+
ConversationLoopOptions,
|
|
18
|
+
ConversationOptions,
|
|
19
|
+
ConversationTurn,
|
|
20
|
+
DatePolicy,
|
|
21
|
+
InputOptions,
|
|
22
|
+
JsonObject,
|
|
23
|
+
JsonSchema,
|
|
24
|
+
LlmResponseJson,
|
|
25
|
+
MapSetSnapshotPolicy,
|
|
26
|
+
MemoryAction,
|
|
27
|
+
ParallelOptions,
|
|
28
|
+
PromptOptions,
|
|
29
|
+
PromptStreamType,
|
|
30
|
+
RandomPolicy,
|
|
31
|
+
RetryOptions,
|
|
32
|
+
RuntimePolicyConfig,
|
|
33
|
+
Signal,
|
|
34
|
+
SignalOptions,
|
|
35
|
+
SignalSender,
|
|
36
|
+
SignalTimeout,
|
|
37
|
+
ToolDefinition,
|
|
38
|
+
ToolFunction,
|
|
39
|
+
TryCallResult,
|
|
40
|
+
TypeScriptImportPolicy,
|
|
41
|
+
WorkspaceEntry,
|
|
42
|
+
WorkspaceFileStatus,
|
|
43
|
+
WorkspaceHost,
|
|
44
|
+
WorkspaceListOptions,
|
|
45
|
+
WorkspaceWriteOptions,
|
|
46
|
+
} from "./agent.js";
|
|
47
|
+
|
|
48
|
+
// Authoring entrypoints: the host object and the `run(handler)` definer. These
|
|
49
|
+
// are value exports (the runtime strips the import and supplies them).
|
|
50
|
+
export { chidori, run } from "./agent.js";
|
|
51
|
+
|
|
52
|
+
/** JSON-serialisable value — what agents produce as output and accept as input. */
|
|
53
|
+
export type Json =
|
|
54
|
+
| null
|
|
55
|
+
| boolean
|
|
56
|
+
| number
|
|
57
|
+
| string
|
|
58
|
+
| Json[]
|
|
59
|
+
| { [key: string]: Json };
|
|
60
|
+
|
|
61
|
+
/** Server-side session status. */
|
|
62
|
+
export type SessionStatus =
|
|
63
|
+
| "running"
|
|
64
|
+
| "completed"
|
|
65
|
+
| "failed"
|
|
66
|
+
| "paused"
|
|
67
|
+
| "cancelled"
|
|
68
|
+
| "awaitingapproval";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Built-in policy profiles selectable per session. Layered on the server
|
|
72
|
+
* policy with stricter-wins semantics: a profile can tighten what the
|
|
73
|
+
* operator's policy allows, never relax it.
|
|
74
|
+
*/
|
|
75
|
+
export type PolicyProfile = "untrusted" | "supervised";
|
|
76
|
+
|
|
77
|
+
/** A single host function call recorded during an agent run. */
|
|
78
|
+
export interface CallRecord {
|
|
79
|
+
seq: number;
|
|
80
|
+
function: string;
|
|
81
|
+
args: Json;
|
|
82
|
+
result: Json;
|
|
83
|
+
duration_ms: number;
|
|
84
|
+
timestamp: string;
|
|
85
|
+
token_usage?: { input_tokens: number; output_tokens: number };
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Source hash recorded in a runtime snapshot manifest. */
|
|
90
|
+
export interface SnapshotSourceFingerprint {
|
|
91
|
+
path: string;
|
|
92
|
+
hash: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Snapshot ABI recorded before loading VM snapshot bytes. */
|
|
96
|
+
export interface SnapshotAbi {
|
|
97
|
+
typescript_runtime: number;
|
|
98
|
+
quickjs_snapshot: number;
|
|
99
|
+
engine_fork: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Determinism policy captured with a runtime snapshot. */
|
|
103
|
+
export interface SnapshotRuntimePolicy {
|
|
104
|
+
typescript_imports: "none" | "relative" | "project";
|
|
105
|
+
date: "disabled" | "fixed" | "host";
|
|
106
|
+
random: "disabled" | "seeded" | "host";
|
|
107
|
+
maps_sets: "reject" | "serialize";
|
|
108
|
+
deterministic_seed: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Pending host operation captured at a durable snapshot safepoint. */
|
|
112
|
+
export interface PendingHostOperation {
|
|
113
|
+
id: number;
|
|
114
|
+
seq: number;
|
|
115
|
+
kind:
|
|
116
|
+
| "prompt"
|
|
117
|
+
| "input"
|
|
118
|
+
| "policy_approval"
|
|
119
|
+
| "tool"
|
|
120
|
+
| "call_agent"
|
|
121
|
+
| "http"
|
|
122
|
+
| "template"
|
|
123
|
+
| "memory"
|
|
124
|
+
| "checkpoint";
|
|
125
|
+
args: Json;
|
|
126
|
+
created_at: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type HostPromiseState =
|
|
130
|
+
| "pending"
|
|
131
|
+
| { resolved: { value: Json; completed_at: string } }
|
|
132
|
+
| { rejected: { error: string; completed_at: string } };
|
|
133
|
+
|
|
134
|
+
export interface HostPromiseRecord {
|
|
135
|
+
operation: PendingHostOperation;
|
|
136
|
+
state: HostPromiseState;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Public snapshot metadata. It is safe to expose in SDK checkpoints; the raw
|
|
141
|
+
* `runtime.snapshot` VM bytes stay server-side unless an admin endpoint opts in.
|
|
142
|
+
*/
|
|
143
|
+
export interface SnapshotManifest {
|
|
144
|
+
run_id: string;
|
|
145
|
+
abi: SnapshotAbi;
|
|
146
|
+
policy: SnapshotRuntimePolicy;
|
|
147
|
+
entry: SnapshotSourceFingerprint;
|
|
148
|
+
modules: SnapshotSourceFingerprint[];
|
|
149
|
+
pending?: PendingHostOperation | null;
|
|
150
|
+
host_promises?: HostPromiseRecord[];
|
|
151
|
+
call_log_len: number;
|
|
152
|
+
snapshot_file: string;
|
|
153
|
+
created_at: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A saved checkpoint — session id, input, the full call log, and optional
|
|
158
|
+
* runtime snapshot metadata. The call log is enough for deterministic replay;
|
|
159
|
+
* the snapshot manifest lets clients inspect durable-resume state without
|
|
160
|
+
* downloading raw VM snapshot bytes.
|
|
161
|
+
*/
|
|
162
|
+
export class Checkpoint {
|
|
163
|
+
constructor(
|
|
164
|
+
public readonly sessionId: string,
|
|
165
|
+
public readonly input: Json,
|
|
166
|
+
public readonly callLog: CallRecord[],
|
|
167
|
+
public readonly snapshotManifest: SnapshotManifest | null = null,
|
|
168
|
+
) {}
|
|
169
|
+
|
|
170
|
+
/** Serialise to JSON. Pairs with `Checkpoint.fromJSON`. */
|
|
171
|
+
toJSON(): {
|
|
172
|
+
session_id: string;
|
|
173
|
+
input: Json;
|
|
174
|
+
call_log: CallRecord[];
|
|
175
|
+
snapshot_manifest?: SnapshotManifest | null;
|
|
176
|
+
} {
|
|
177
|
+
return {
|
|
178
|
+
session_id: this.sessionId,
|
|
179
|
+
input: this.input,
|
|
180
|
+
call_log: this.callLog,
|
|
181
|
+
...(this.snapshotManifest ? { snapshot_manifest: this.snapshotManifest } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static fromJSON(data: {
|
|
186
|
+
session_id: string;
|
|
187
|
+
input: Json;
|
|
188
|
+
call_log: CallRecord[];
|
|
189
|
+
snapshot_manifest?: SnapshotManifest | null;
|
|
190
|
+
}): Checkpoint {
|
|
191
|
+
return new Checkpoint(
|
|
192
|
+
data.session_id,
|
|
193
|
+
data.input,
|
|
194
|
+
data.call_log,
|
|
195
|
+
data.snapshot_manifest ?? null,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returned by `client.signal` when the signal was accepted but did not resolve
|
|
202
|
+
* a pause synchronously. Mirrors the server's 202 Accepted body:
|
|
203
|
+
* * `"queued"` — the run was not waiting on this name; the signal sits in
|
|
204
|
+
* the durable mailbox until a matching listen point drains it.
|
|
205
|
+
* * `"delivered_live"` — a live streaming worker supervises the run; the
|
|
206
|
+
* signal was enqueued into the running agent's in-memory mailbox and the
|
|
207
|
+
* worker was woken to resume a matching pause in-process.
|
|
208
|
+
*/
|
|
209
|
+
export interface SignalQueued {
|
|
210
|
+
/** Session id the signal was delivered to. */
|
|
211
|
+
id: string;
|
|
212
|
+
status: "queued" | "delivered_live";
|
|
213
|
+
/** The signal name, echoed back. */
|
|
214
|
+
name: string;
|
|
215
|
+
/** Monotonic per-run sequence freezing global arrival order across senders. */
|
|
216
|
+
delivery_seq: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** A signal delivery request body for `client.signal`. */
|
|
220
|
+
export interface SignalDelivery {
|
|
221
|
+
/** Required, non-empty: the listen-point name the agent awaits. */
|
|
222
|
+
name: string;
|
|
223
|
+
/** Any JSON payload (default null). */
|
|
224
|
+
payload?: Json;
|
|
225
|
+
/** Sender provenance recorded in the trace (default null). */
|
|
226
|
+
from?: SignalSender | Json;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Type guard distinguishing the two `client.signal` outcomes: a resumed
|
|
231
|
+
* `Session` (the run was paused-waiting on this name) vs a `SignalQueued`
|
|
232
|
+
* descriptor (the signal was enqueued for a later listen point).
|
|
233
|
+
*/
|
|
234
|
+
export function isSignalQueued(result: Session | SignalQueued): result is SignalQueued {
|
|
235
|
+
const status = (result as SignalQueued).status;
|
|
236
|
+
return status === "queued" || status === "delivered_live";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** One execution of an agent — result + call log + status. */
|
|
240
|
+
export class Session {
|
|
241
|
+
constructor(
|
|
242
|
+
public readonly id: string,
|
|
243
|
+
public status: SessionStatus,
|
|
244
|
+
public readonly input: Json,
|
|
245
|
+
public output: Json | null = null,
|
|
246
|
+
public error: string | null = null,
|
|
247
|
+
public callLog: CallRecord[] = [],
|
|
248
|
+
public pendingPrompt: string | null = null,
|
|
249
|
+
private readonly client: AgentClient | null = null,
|
|
250
|
+
public snapshotManifest: SnapshotManifest | null = null,
|
|
251
|
+
/**
|
|
252
|
+
* When the run is `paused` at a `chidori.signal(name)` listen point, the
|
|
253
|
+
* name it is waiting on (so a caller can deliver via `client.signal`).
|
|
254
|
+
* `null` for plain `input()` pauses and non-signal states.
|
|
255
|
+
*/
|
|
256
|
+
public pendingSignalName: string | null = null,
|
|
257
|
+
/**
|
|
258
|
+
* The full awaited name set when paused on a signal listen point: `[name]`
|
|
259
|
+
* for `chidori.signal(name)`, the listen set for `chidori.signalAny(names)`.
|
|
260
|
+
* Empty for non-signal states.
|
|
261
|
+
*/
|
|
262
|
+
public pendingSignalNames: string[] = [],
|
|
263
|
+
/**
|
|
264
|
+
* Absolute deadline (ISO timestamp) for a signal pause created with
|
|
265
|
+
* `timeoutMs`; the server resolves the pause with the timeout sentinel
|
|
266
|
+
* when it passes. `null` when the pause has no timeout.
|
|
267
|
+
*/
|
|
268
|
+
public pendingSignalDeadline: string | null = null,
|
|
269
|
+
) {}
|
|
270
|
+
|
|
271
|
+
get ok(): boolean {
|
|
272
|
+
return this.status === "completed";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Fetch the full call log from the server (if not already loaded) and
|
|
277
|
+
* wrap it in a Checkpoint suitable for saving / later replay.
|
|
278
|
+
*/
|
|
279
|
+
async checkpoint(): Promise<Checkpoint> {
|
|
280
|
+
if ((this.callLog.length === 0 || this.snapshotManifest === null) && this.client) {
|
|
281
|
+
const data = await this.client.getCheckpoint(this.id);
|
|
282
|
+
this.callLog = data.call_log;
|
|
283
|
+
this.snapshotManifest = data.snapshot_manifest ?? null;
|
|
284
|
+
}
|
|
285
|
+
return new Checkpoint(this.id, this.input, this.callLog, this.snapshotManifest);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Replay this session through the server; same inputs, cached results. */
|
|
289
|
+
async replay(): Promise<Session> {
|
|
290
|
+
if (!this.client) {
|
|
291
|
+
throw new Error("Session has no client bound; use client.replay()");
|
|
292
|
+
}
|
|
293
|
+
const cp = await this.checkpoint();
|
|
294
|
+
return this.client.replay(cp);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Stream event yielded by `AgentClient.stream`. */
|
|
299
|
+
export type StreamEvent =
|
|
300
|
+
| { type: "call"; record: CallRecord }
|
|
301
|
+
| {
|
|
302
|
+
type: "prompt_start";
|
|
303
|
+
stream_id: string;
|
|
304
|
+
seq: number;
|
|
305
|
+
prompt_type?: string | null;
|
|
306
|
+
model: string;
|
|
307
|
+
}
|
|
308
|
+
| {
|
|
309
|
+
type: "prompt_delta";
|
|
310
|
+
stream_id: string;
|
|
311
|
+
seq: number;
|
|
312
|
+
prompt_type?: string | null;
|
|
313
|
+
delta: string;
|
|
314
|
+
}
|
|
315
|
+
| {
|
|
316
|
+
type: "prompt_end";
|
|
317
|
+
stream_id: string;
|
|
318
|
+
seq: number;
|
|
319
|
+
prompt_type?: string | null;
|
|
320
|
+
error?: string | null;
|
|
321
|
+
}
|
|
322
|
+
| {
|
|
323
|
+
/**
|
|
324
|
+
* The streamed run paused at a `signal()` / `signalAny()` listen point
|
|
325
|
+
* and stays live: the worker keeps supervising, and a delivered signal
|
|
326
|
+
* (or the `timeoutMs` deadline) resumes it in-process — further events
|
|
327
|
+
* follow on the same stream. Deliver with `client.signal`.
|
|
328
|
+
*/
|
|
329
|
+
type: "paused";
|
|
330
|
+
id: string;
|
|
331
|
+
status: "paused";
|
|
332
|
+
pending_seq: number;
|
|
333
|
+
pending_signal_name?: string | null;
|
|
334
|
+
pending_signal_names?: string[];
|
|
335
|
+
pending_signal_deadline?: string | null;
|
|
336
|
+
}
|
|
337
|
+
| { type: "done"; id: string; status: SessionStatus; output?: Json; error?: string };
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* HTTP client for an `chidori serve` instance.
|
|
341
|
+
*
|
|
342
|
+
* ```ts
|
|
343
|
+
* const client = new AgentClient("http://localhost:8080");
|
|
344
|
+
* const session = await client.run({ document: "Rust is a systems language." });
|
|
345
|
+
* console.log(session.output);
|
|
346
|
+
*
|
|
347
|
+
* const cp = await session.checkpoint();
|
|
348
|
+
* const replayed = await client.replay(cp); // zero LLM calls
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
export class AgentClient {
|
|
352
|
+
readonly baseUrl: string;
|
|
353
|
+
|
|
354
|
+
constructor(baseUrl: string = "http://localhost:8080") {
|
|
355
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async health(): Promise<Json> {
|
|
359
|
+
return (await this.getJSON("/health")) as Json;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create a new session and run the agent with the given input.
|
|
364
|
+
*
|
|
365
|
+
* `options.policyProfile` optionally names a built-in policy profile
|
|
366
|
+
* ("untrusted" or "supervised") applied to every run of this session.
|
|
367
|
+
* It is layered on the server policy with stricter-wins semantics — it
|
|
368
|
+
* can tighten what the operator allows, never relax it. Under
|
|
369
|
+
* "supervised", gated calls pause the session as "awaitingapproval";
|
|
370
|
+
* approve or deny them via the server's /approve endpoint.
|
|
371
|
+
*/
|
|
372
|
+
async run(input: Json, options?: { policyProfile?: PolicyProfile }): Promise<Session> {
|
|
373
|
+
const body: Record<string, unknown> = { input };
|
|
374
|
+
if (options?.policyProfile) {
|
|
375
|
+
body.policy_profile = options.policyProfile;
|
|
376
|
+
}
|
|
377
|
+
const data = await this.postJSON("/sessions", body);
|
|
378
|
+
return this.sessionFrom(data, input);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Replay an agent from a saved checkpoint. */
|
|
382
|
+
async replay(checkpoint: Checkpoint): Promise<Session> {
|
|
383
|
+
const data = await this.postJSON("/sessions", {
|
|
384
|
+
input: checkpoint.input,
|
|
385
|
+
replay_from: checkpoint.callLog,
|
|
386
|
+
});
|
|
387
|
+
return this.sessionFrom(data, checkpoint.input);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Supply a response to a paused `input()` call and continue the run.
|
|
392
|
+
* The same session id advances to completed (or re-pauses on a later
|
|
393
|
+
* `input()`).
|
|
394
|
+
*/
|
|
395
|
+
async resume(sessionId: string, response: string): Promise<Session> {
|
|
396
|
+
const data = await this.postJSON(`/sessions/${sessionId}/resume`, {
|
|
397
|
+
response,
|
|
398
|
+
});
|
|
399
|
+
return this.sessionFrom(data, (data.input as Json | undefined) ?? null);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Deliver a signal `{ name, payload?, from? }` to a run
|
|
404
|
+
* (`POST /sessions/{id}/signal`).
|
|
405
|
+
*
|
|
406
|
+
* Two outcomes, distinguished by `isSignalQueued`:
|
|
407
|
+
* * the run was paused-waiting on this exact name → it resolves the pause
|
|
408
|
+
* and resumes; this returns the advanced `Session` (200), now `completed`
|
|
409
|
+
* or re-`paused`.
|
|
410
|
+
* * otherwise → the signal is accepted asynchronously; this returns a
|
|
411
|
+
* `SignalQueued` descriptor (202) carrying the assigned `delivery_seq`,
|
|
412
|
+
* with `status` `"queued"` (durable mailbox) or `"delivered_live"` (a
|
|
413
|
+
* live streaming worker received it in-memory and resumes a matching
|
|
414
|
+
* pause in-process).
|
|
415
|
+
*
|
|
416
|
+
* Throws on 400 (empty name), 404 (unknown session), or 409 (terminal run).
|
|
417
|
+
*/
|
|
418
|
+
async signal(
|
|
419
|
+
sessionId: string,
|
|
420
|
+
delivery: SignalDelivery,
|
|
421
|
+
): Promise<Session | SignalQueued> {
|
|
422
|
+
const { status, data } = await this.postJSONWithStatus(
|
|
423
|
+
`/sessions/${sessionId}/signal`,
|
|
424
|
+
{
|
|
425
|
+
name: delivery.name,
|
|
426
|
+
payload: delivery.payload ?? null,
|
|
427
|
+
from: delivery.from ?? null,
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
const accepted = (data as { status?: string }).status;
|
|
431
|
+
if (status === 202 || accepted === "queued" || accepted === "delivered_live") {
|
|
432
|
+
return data as unknown as SignalQueued;
|
|
433
|
+
}
|
|
434
|
+
return this.sessionFrom(data, (data.input as Json | undefined) ?? null);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Fetch an existing session by id. */
|
|
438
|
+
async getSession(id: string): Promise<Session> {
|
|
439
|
+
const data = (await this.getJSON(`/sessions/${id}`)) as Record<string, unknown>;
|
|
440
|
+
return this.sessionFrom(data, (data.input as Json | undefined) ?? null);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** List all sessions. Returns the raw summaries. */
|
|
444
|
+
async listSessions(): Promise<Array<{ id: string; status: SessionStatus; error?: string }>> {
|
|
445
|
+
const data = (await this.getJSON("/sessions")) as {
|
|
446
|
+
sessions: Array<{ id: string; status: SessionStatus; error?: string }>;
|
|
447
|
+
};
|
|
448
|
+
return data.sessions;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Fetch the full call log and optional snapshot manifest for a session. */
|
|
452
|
+
async getCheckpoint(id: string): Promise<{
|
|
453
|
+
call_log: CallRecord[];
|
|
454
|
+
snapshot_manifest?: SnapshotManifest | null;
|
|
455
|
+
}> {
|
|
456
|
+
return (await this.getJSON(`/sessions/${id}/checkpoint`)) as {
|
|
457
|
+
call_log: CallRecord[];
|
|
458
|
+
snapshot_manifest?: SnapshotManifest | null;
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Fetch only the snapshot manifest metadata for a session, never VM bytes. */
|
|
463
|
+
async getSnapshotManifest(id: string): Promise<SnapshotManifest> {
|
|
464
|
+
const data = (await this.getJSON(`/sessions/${id}/snapshot`)) as {
|
|
465
|
+
snapshot_manifest: SnapshotManifest;
|
|
466
|
+
};
|
|
467
|
+
return data.snapshot_manifest;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Stream an agent run: yields host function calls, prompt stream lifecycle
|
|
472
|
+
* events (`prompt_start`, `prompt_delta`, `prompt_end`), then a final
|
|
473
|
+
* `done` event. Prompt events include `prompt_type` so UIs can filter
|
|
474
|
+
* progress streams separately from final-answer streams.
|
|
475
|
+
*/
|
|
476
|
+
async *stream(input: Json): AsyncGenerator<StreamEvent, void, void> {
|
|
477
|
+
const resp = await fetch(`${this.baseUrl}/sessions/stream`, {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
|
|
480
|
+
body: JSON.stringify({ input }),
|
|
481
|
+
});
|
|
482
|
+
if (!resp.ok || !resp.body) {
|
|
483
|
+
throw new Error(`stream request failed: ${resp.status}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Minimal SSE parser — just enough for the events our server emits.
|
|
487
|
+
const reader = resp.body.getReader();
|
|
488
|
+
const decoder = new TextDecoder();
|
|
489
|
+
let buffer = "";
|
|
490
|
+
while (true) {
|
|
491
|
+
const { value, done } = await reader.read();
|
|
492
|
+
if (done) break;
|
|
493
|
+
buffer += decoder.decode(value, { stream: true });
|
|
494
|
+
let idx: number;
|
|
495
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
496
|
+
const frame = buffer.slice(0, idx);
|
|
497
|
+
buffer = buffer.slice(idx + 2);
|
|
498
|
+
const parsed = parseSseFrame(frame);
|
|
499
|
+
if (parsed) yield parsed;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// -- internals ----------------------------------------------------------
|
|
505
|
+
|
|
506
|
+
private sessionFrom(data: Record<string, unknown>, input: Json): Session {
|
|
507
|
+
return new Session(
|
|
508
|
+
data.id as string,
|
|
509
|
+
(data.status as SessionStatus) ?? "failed",
|
|
510
|
+
input,
|
|
511
|
+
(data.output as Json | undefined) ?? null,
|
|
512
|
+
(data.error as string | undefined) ?? null,
|
|
513
|
+
(data.call_log as CallRecord[] | undefined) ?? [],
|
|
514
|
+
(data.pending_prompt as string | undefined) ?? null,
|
|
515
|
+
this,
|
|
516
|
+
(data.snapshot_manifest as SnapshotManifest | undefined) ?? null,
|
|
517
|
+
(data.pending_signal_name as string | undefined) ?? null,
|
|
518
|
+
(data.pending_signal_names as string[] | undefined) ?? [],
|
|
519
|
+
(data.pending_signal_deadline as string | undefined) ?? null,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private async getJSON(path: string): Promise<unknown> {
|
|
524
|
+
const resp = await fetch(this.baseUrl + path);
|
|
525
|
+
if (!resp.ok) throw await httpError(resp);
|
|
526
|
+
return await resp.json();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private async postJSON(path: string, body: unknown): Promise<Record<string, unknown>> {
|
|
530
|
+
return (await this.postJSONWithStatus(path, body)).data;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private async postJSONWithStatus(
|
|
534
|
+
path: string,
|
|
535
|
+
body: unknown,
|
|
536
|
+
): Promise<{ status: number; data: Record<string, unknown> }> {
|
|
537
|
+
const resp = await fetch(this.baseUrl + path, {
|
|
538
|
+
method: "POST",
|
|
539
|
+
headers: { "Content-Type": "application/json" },
|
|
540
|
+
body: JSON.stringify(body),
|
|
541
|
+
});
|
|
542
|
+
if (!resp.ok) throw await httpError(resp);
|
|
543
|
+
return { status: resp.status, data: (await resp.json()) as Record<string, unknown> };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function httpError(resp: Response): Promise<Error> {
|
|
548
|
+
const text = await resp.text().catch(() => "");
|
|
549
|
+
return new Error(`HTTP ${resp.status}: ${text}`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function parseSseFrame(frame: string): StreamEvent | null {
|
|
553
|
+
let event = "message";
|
|
554
|
+
const dataLines: string[] = [];
|
|
555
|
+
for (const line of frame.split("\n")) {
|
|
556
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
557
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
558
|
+
}
|
|
559
|
+
if (dataLines.length === 0) return null;
|
|
560
|
+
try {
|
|
561
|
+
const data = JSON.parse(dataLines.join("\n"));
|
|
562
|
+
if (event === "call") return { type: "call", record: data as CallRecord };
|
|
563
|
+
if (event === "prompt_start") {
|
|
564
|
+
return { type: "prompt_start", ...(data as object) } as StreamEvent;
|
|
565
|
+
}
|
|
566
|
+
if (event === "prompt_delta") {
|
|
567
|
+
return { type: "prompt_delta", ...(data as object) } as StreamEvent;
|
|
568
|
+
}
|
|
569
|
+
if (event === "prompt_end") {
|
|
570
|
+
return { type: "prompt_end", ...(data as object) } as StreamEvent;
|
|
571
|
+
}
|
|
572
|
+
if (event === "paused") {
|
|
573
|
+
return { type: "paused", ...(data as object) } as StreamEvent;
|
|
574
|
+
}
|
|
575
|
+
if (event === "done") return { type: "done", ...(data as object) } as StreamEvent;
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## 0.1.24 (2023-07-28)
|
|
9
|
-
|
|
10
|
-
### Fixed
|
|
11
|
-
|
|
12
|
-
- Fixing releases
|
|
13
|
-
|
|
14
|
-
## 0.1.23 (2023-07-28)
|
|
15
|
-
|
|
16
|
-
### Fixed
|
|
17
|
-
|
|
18
|
-
- Fixing releases
|
|
19
|
-
|
|
20
|
-
## 0.1.22 (2023-07-28)
|
|
21
|
-
|
|
22
|
-
### Added
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
### Fixed
|
|
26
|
-
|
|
27
|
-
### Changed
|
|
28
|
-
|
|
29
|
-
- Moved to SQL queries as triggers to nodes rather than graphql
|
|
30
|
-
|
|
31
|
-
### Changed
|
|
32
|
-
|
|
33
|
-
|
package/Cargo.toml
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "chidori"
|
|
3
|
-
version= "0.1.24"
|
|
4
|
-
edition = "2021"
|
|
5
|
-
description = "A Rust library for executing prompt-graph models"
|
|
6
|
-
license = "MIT"
|
|
7
|
-
homepage = "https://docs.thousandbirds.ai"
|
|
8
|
-
repository = "https://github.com/ThousandBirdsInc/chidori"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
12
|
-
[lib]
|
|
13
|
-
name = "_chidori"
|
|
14
|
-
crate-type = ["cdylib", "lib"]
|
|
15
|
-
bench = false
|
|
16
|
-
|
|
17
|
-
[features]
|
|
18
|
-
python = [
|
|
19
|
-
"dep:pyo3",
|
|
20
|
-
"dep:pyo3-asyncio",
|
|
21
|
-
"dep:pyo3-log",
|
|
22
|
-
]
|
|
23
|
-
nodejs = [
|
|
24
|
-
"dep:neon"
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# https://chat.openai.com/c/52edc960-dc19-4df7-b36f-30caad9c1905
|
|
29
|
-
[dependencies]
|
|
30
|
-
protobuf = "3.2.0"
|
|
31
|
-
sqlparser = "0.34.0"
|
|
32
|
-
# we do not enable wasm-bindgen serde-serialize because it causes this issue https://github.com/rustwasm/wasm-bindgen/pull/3031
|
|
33
|
-
# serde-wasm-bindgen should be used instead
|
|
34
|
-
|
|
35
|
-
# Vendor protoc and openssl so we don't need to separately install them
|
|
36
|
-
openssl = { version = "0.10.55", features = ["vendored"] }
|
|
37
|
-
|
|
38
|
-
once_cell = "1"
|
|
39
|
-
prompt-graph-core = { path = "../prompt-graph-core", version = "^0.1.24" }
|
|
40
|
-
wasm-bindgen = {version = "0.2.86", features = []}
|
|
41
|
-
handlebars = "4.3.7"
|
|
42
|
-
anyhow = { version = "1.0", default-features = false }
|
|
43
|
-
indoc = "1.0.3"
|
|
44
|
-
serde = { version = "1.0", features = ["derive"] }
|
|
45
|
-
serde-wasm-bindgen = "0.4"
|
|
46
|
-
serde_json = "1.0.96"
|
|
47
|
-
tonic = "0.9"
|
|
48
|
-
prost = "0.11"
|
|
49
|
-
tokio = { version = "1", features = ["full"] }
|
|
50
|
-
env_logger = "0.10.0"
|
|
51
|
-
log = "0.4.16"
|
|
52
|
-
futures = "0.3.15"
|
|
53
|
-
|
|
54
|
-
# Optional unless the python feature is enabled
|
|
55
|
-
pyo3 = { version = "0.18.3", features = ["extension-module", "abi3-py37"], optional = true }
|
|
56
|
-
pyo3-asyncio = { version = "0.18", features = ["attributes", "tokio-runtime"], optional = true }
|
|
57
|
-
pyo3-log = { version = "0.8.2", optional = true }
|
|
58
|
-
|
|
59
|
-
neon-serde3 = "0.10.0"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
[dependencies.prompt-graph-exec]
|
|
63
|
-
path = "../prompt-graph-exec"
|
|
64
|
-
version = "0.1.0"
|
|
65
|
-
default-features = false
|
|
66
|
-
|
|
67
|
-
[dependencies.neon]
|
|
68
|
-
version = "0.10.1"
|
|
69
|
-
default-features = false
|
|
70
|
-
features = ["napi-6", "channel-api", "promise-api", "try-catch-api"]
|
|
71
|
-
optional = true
|
|
72
|
-
|
|
73
|
-
[dev-dependencies]
|
|
74
|
-
wasm-bindgen-test = "0.2"
|
|
75
|
-
|
|
76
|
-
[build-dependencies]
|
|
77
|
-
tonic-build = "0.9.2"
|
|
78
|
-
pyo3-build-config = "0.19.1"
|
|
79
|
-
|
|
80
|
-
[profile.release]
|
|
81
|
-
lto = "fat"
|
|
82
|
-
codegen-units = 1
|
|
83
|
-
strip = true
|
package/build.rs
DELETED
package/package_node/index.d.ts
DELETED
|
File without changes
|