@1kbirds/chidori 3.6.0 → 3.7.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 +78 -3
- package/dist/agent.d.ts +479 -47
- package/dist/agent.js +28 -0
- package/dist/index.d.ts +131 -7
- package/dist/index.js +207 -24
- package/package.json +2 -1
- package/src/agent.ts +539 -55
- package/src/index.ts +258 -22
package/dist/agent.js
CHANGED
|
@@ -44,3 +44,31 @@ export function run(handler) {
|
|
|
44
44
|
throw new Error("run() is only available inside the chidori runtime; this import is " +
|
|
45
45
|
"replaced when an agent runs under chidori.");
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Wrap a function into a {@link ToolHandle} by attaching its documented
|
|
49
|
+
* signature — a `name`, a `description`, and a JSON-schema for its
|
|
50
|
+
* `parameters`. That signature is all the model needs to call the function;
|
|
51
|
+
* the function itself is just code. No `tools/` directory, no registry —
|
|
52
|
+
* import (or define inline) the handle like any other value. `parameters`
|
|
53
|
+
* defaults to an empty object schema; `description` to `""`. Inside the
|
|
54
|
+
* runtime this import is replaced by the live implementation, exactly like
|
|
55
|
+
* `run` and `chidori`.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { chidori, run, defineTool } from "chidori:agent";
|
|
59
|
+
*
|
|
60
|
+
* const search = defineTool({
|
|
61
|
+
* name: "search",
|
|
62
|
+
* description: "Search the corpus.",
|
|
63
|
+
* parameters: { type: "object", properties: { q: { type: "string" } }, required: ["q"] },
|
|
64
|
+
* run: async ({ q }) => (await fetch(BASE + q)).json(),
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* run(async () => chidori.prompt("find chidori", { tools: [search], maxTurns: 6 }));
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function defineTool(def) {
|
|
71
|
+
void def;
|
|
72
|
+
throw new Error("defineTool() is only available inside the chidori runtime; this import " +
|
|
73
|
+
"is replaced when an agent runs under chidori.");
|
|
74
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* dependencies; uses the global `fetch` available in Node 18+ and browsers.
|
|
5
5
|
*/
|
|
6
6
|
import type { SignalSender } from "./agent.js";
|
|
7
|
-
export type { AgentFunction, AgentJson, CacheTtl, Chidori, CompactOptions, Context, Conversation, ConversationLoopOptions, ConversationOptions, ConversationTurn, DatePolicy, InputOptions, JsonObject, JsonSchema, LlmResponseJson, MapSetSnapshotPolicy,
|
|
7
|
+
export type { ActorHandle, ActorMessage, ActorOutcome, ActorOutcomeStatus, ActorRestartStrategy, Actors, ActorStatus, ActorStillRunning, AgentFunction, AgentJson, AgentOutput, AppData, BranchOptions, BranchOutcome, BranchStatus, BranchVariant, CacheTtl, Chidori, ChidoriUtil, CompactOptions, Context, Conversation, ConversationLoopOptions, ConversationOptions, ConversationTurn, DatePolicy, DetachedAgentHandle, DetachedAgentOutcome, DetachedAgents, DetachedAgentStatus, InputOptions, JoinActorOptions, JsonObject, JsonSchema, LlmResponseJson, LogFields, MapSetSnapshotPolicy, MemoryStore, ParallelOptions, PromptOptions, PromptStreamType, RandomPolicy, ReceiveOptions, RetryOptions, RuntimePolicyConfig, Signal, SignalOptions, SignalSender, SignalTimeout, SpawnActorOptions, SpawnAgentOptions, ToolDefinition, ToolFunction, TryCallResult, TypeScriptImportPolicy, WorkspaceEntry, WorkspaceFileStatus, WorkspaceHost, WorkspaceListOptions, WorkspaceWriteOptions, } from "./agent.js";
|
|
8
8
|
export { chidori, run } from "./agent.js";
|
|
9
9
|
/** JSON-serialisable value — what agents produce as output and accept as input. */
|
|
10
10
|
export type Json = null | boolean | number | string | Json[] | {
|
|
@@ -168,7 +168,7 @@ export declare class Session {
|
|
|
168
168
|
pendingSignalName: string | null;
|
|
169
169
|
/**
|
|
170
170
|
* The full awaited name set when paused on a signal listen point: `[name]`
|
|
171
|
-
* for `chidori.signal(name)`, the listen set for `chidori.
|
|
171
|
+
* for `chidori.signal(name)`, the listen set for the fan-in `chidori.signal(names)`.
|
|
172
172
|
* Empty for non-signal states.
|
|
173
173
|
*/
|
|
174
174
|
pendingSignalNames: string[];
|
|
@@ -178,6 +178,18 @@ export declare class Session {
|
|
|
178
178
|
* when it passes. `null` when the pause has no timeout.
|
|
179
179
|
*/
|
|
180
180
|
pendingSignalDeadline: string | null;
|
|
181
|
+
/**
|
|
182
|
+
* The artifact under review for an `input()` pause created with
|
|
183
|
+
* `{ details }` (a draft, a diff) — surface it so a human never approves
|
|
184
|
+
* blind. `null` when the pause carries no details.
|
|
185
|
+
*/
|
|
186
|
+
pendingDetails: string | null;
|
|
187
|
+
/**
|
|
188
|
+
* The durable run directory id (`.chidori/runs/<run_id>`) this session
|
|
189
|
+
* journals into. Deliberately distinct from the session id: `chidori
|
|
190
|
+
* resume <agent.ts> <run_id>` and `chidori trace <run_id>` take THIS id.
|
|
191
|
+
*/
|
|
192
|
+
runId: string | null;
|
|
181
193
|
constructor(id: string, status: SessionStatus, input: Json, output?: Json | null, error?: string | null, callLog?: CallRecord[], pendingPrompt?: string | null, client?: AgentClient | null, snapshotManifest?: SnapshotManifest | null,
|
|
182
194
|
/**
|
|
183
195
|
* When the run is `paused` at a `chidori.signal(name)` listen point, the
|
|
@@ -187,7 +199,7 @@ export declare class Session {
|
|
|
187
199
|
pendingSignalName?: string | null,
|
|
188
200
|
/**
|
|
189
201
|
* The full awaited name set when paused on a signal listen point: `[name]`
|
|
190
|
-
* for `chidori.signal(name)`, the listen set for `chidori.
|
|
202
|
+
* for `chidori.signal(name)`, the listen set for the fan-in `chidori.signal(names)`.
|
|
191
203
|
* Empty for non-signal states.
|
|
192
204
|
*/
|
|
193
205
|
pendingSignalNames?: string[],
|
|
@@ -196,7 +208,19 @@ export declare class Session {
|
|
|
196
208
|
* `timeoutMs`; the server resolves the pause with the timeout sentinel
|
|
197
209
|
* when it passes. `null` when the pause has no timeout.
|
|
198
210
|
*/
|
|
199
|
-
pendingSignalDeadline?: string | null
|
|
211
|
+
pendingSignalDeadline?: string | null,
|
|
212
|
+
/**
|
|
213
|
+
* The artifact under review for an `input()` pause created with
|
|
214
|
+
* `{ details }` (a draft, a diff) — surface it so a human never approves
|
|
215
|
+
* blind. `null` when the pause carries no details.
|
|
216
|
+
*/
|
|
217
|
+
pendingDetails?: string | null,
|
|
218
|
+
/**
|
|
219
|
+
* The durable run directory id (`.chidori/runs/<run_id>`) this session
|
|
220
|
+
* journals into. Deliberately distinct from the session id: `chidori
|
|
221
|
+
* resume <agent.ts> <run_id>` and `chidori trace <run_id>` take THIS id.
|
|
222
|
+
*/
|
|
223
|
+
runId?: string | null);
|
|
200
224
|
get ok(): boolean;
|
|
201
225
|
/**
|
|
202
226
|
* Fetch the full call log from the server (if not already loaded) and
|
|
@@ -230,7 +254,7 @@ export type StreamEvent = {
|
|
|
230
254
|
error?: string | null;
|
|
231
255
|
} | {
|
|
232
256
|
/**
|
|
233
|
-
* The streamed run paused at a `signal()`
|
|
257
|
+
* The streamed run paused at a `signal()` listen point
|
|
234
258
|
* and stays live: the worker keeps supervising, and a delivered signal
|
|
235
259
|
* (or the `timeoutMs` deadline) resumes it in-process — further events
|
|
236
260
|
* follow on the same stream. Deliver with `client.signal`.
|
|
@@ -249,6 +273,86 @@ export type StreamEvent = {
|
|
|
249
273
|
output?: Json;
|
|
250
274
|
error?: string;
|
|
251
275
|
};
|
|
276
|
+
/** Base class for every error the SDK throws. `catch (e) { if (e instanceof
|
|
277
|
+
* AgentClientError) ... }` covers HTTP failures, timeouts, and connection
|
|
278
|
+
* errors alike. */
|
|
279
|
+
export declare class AgentClientError extends Error {
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A non-2xx HTTP response. Carries the parsed `status` so callers can
|
|
283
|
+
* distinguish the server's documented semantics — e.g. for `client.signal`:
|
|
284
|
+
* 400 (empty name), 404 (unknown session), 409 (terminal run) — instead of
|
|
285
|
+
* string-matching the message.
|
|
286
|
+
*/
|
|
287
|
+
export declare class HttpError extends AgentClientError {
|
|
288
|
+
/** HTTP method of the failed request. */
|
|
289
|
+
readonly method: string;
|
|
290
|
+
/** Request path relative to the base URL. */
|
|
291
|
+
readonly path: string;
|
|
292
|
+
/** HTTP status code (400, 404, 409, 500, ...). */
|
|
293
|
+
readonly status: number;
|
|
294
|
+
/** Raw response body text (may be empty). */
|
|
295
|
+
readonly body: string;
|
|
296
|
+
/** The server's `error` field, when the body was `{ "error": ... }`. */
|
|
297
|
+
readonly detail: string | null;
|
|
298
|
+
constructor(
|
|
299
|
+
/** HTTP method of the failed request. */
|
|
300
|
+
method: string,
|
|
301
|
+
/** Request path relative to the base URL. */
|
|
302
|
+
path: string,
|
|
303
|
+
/** HTTP status code (400, 404, 409, 500, ...). */
|
|
304
|
+
status: number,
|
|
305
|
+
/** Raw response body text (may be empty). */
|
|
306
|
+
body: string,
|
|
307
|
+
/** The server's `error` field, when the body was `{ "error": ... }`. */
|
|
308
|
+
detail?: string | null);
|
|
309
|
+
static fromResponse(method: string, path: string, resp: Response): Promise<HttpError>;
|
|
310
|
+
}
|
|
311
|
+
/** The request exceeded the client's `timeoutMs` without completing. */
|
|
312
|
+
export declare class TimeoutError extends AgentClientError {
|
|
313
|
+
readonly method: string;
|
|
314
|
+
readonly path: string;
|
|
315
|
+
readonly timeoutMs: number;
|
|
316
|
+
constructor(method: string, path: string, timeoutMs: number);
|
|
317
|
+
}
|
|
318
|
+
/** The request never produced an HTTP response (refused, reset, DNS, ...). */
|
|
319
|
+
export declare class ConnectionError extends AgentClientError {
|
|
320
|
+
readonly method: string;
|
|
321
|
+
readonly path: string;
|
|
322
|
+
constructor(method: string, path: string, cause: unknown);
|
|
323
|
+
}
|
|
324
|
+
export interface AgentClientOptions {
|
|
325
|
+
/**
|
|
326
|
+
* Bearer token for a server running with `CHIDORI_API_KEY` set (the
|
|
327
|
+
* production posture — see docs/deployment.md). Sent as
|
|
328
|
+
* `Authorization: Bearer <apiKey>` on every request, including `stream()`.
|
|
329
|
+
* Omit against an unauthenticated loopback server.
|
|
330
|
+
*/
|
|
331
|
+
apiKey?: string;
|
|
332
|
+
/**
|
|
333
|
+
* Extra headers merged into every request (after `apiKey`, so an explicit
|
|
334
|
+
* `Authorization` entry here wins). Escape hatch for proxies and custom
|
|
335
|
+
* auth schemes the `apiKey` option doesn't cover.
|
|
336
|
+
*/
|
|
337
|
+
headers?: Record<string, string>;
|
|
338
|
+
/**
|
|
339
|
+
* Per-request timeout in milliseconds; `0` disables it. Defaults to
|
|
340
|
+
* 300 000 (5 minutes) — generous because `run()` executes the whole agent
|
|
341
|
+
* before responding, but finite so a hung server surfaces as a
|
|
342
|
+
* `TimeoutError` instead of blocking forever. For `stream()` the timeout
|
|
343
|
+
* covers connection establishment only, never an open event stream.
|
|
344
|
+
*/
|
|
345
|
+
timeoutMs?: number;
|
|
346
|
+
/**
|
|
347
|
+
* How many times to retry **idempotent GET requests** after a connection
|
|
348
|
+
* error, timeout, or retryable status (429/502/503/504). Defaults to 2.
|
|
349
|
+
* POST requests are never retried — `run`/`resume`/`signal` are not
|
|
350
|
+
* idempotent, and a blind retry could execute an agent twice.
|
|
351
|
+
*/
|
|
352
|
+
retries?: number;
|
|
353
|
+
/** Base delay between retries in milliseconds, doubling per attempt (default 250). */
|
|
354
|
+
retryDelayMs?: number;
|
|
355
|
+
}
|
|
252
356
|
/**
|
|
253
357
|
* HTTP client for an `chidori serve` instance.
|
|
254
358
|
*
|
|
@@ -260,10 +364,19 @@ export type StreamEvent = {
|
|
|
260
364
|
* const cp = await session.checkpoint();
|
|
261
365
|
* const replayed = await client.replay(cp); // zero LLM calls
|
|
262
366
|
* ```
|
|
367
|
+
*
|
|
368
|
+
* Failures throw typed errors (all extending {@link AgentClientError}):
|
|
369
|
+
* {@link HttpError} with a `.status` for non-2xx responses,
|
|
370
|
+
* {@link TimeoutError} after `timeoutMs`, {@link ConnectionError} when no
|
|
371
|
+
* response arrived at all.
|
|
263
372
|
*/
|
|
264
373
|
export declare class AgentClient {
|
|
265
374
|
readonly baseUrl: string;
|
|
266
|
-
|
|
375
|
+
readonly timeoutMs: number;
|
|
376
|
+
readonly retries: number;
|
|
377
|
+
readonly retryDelayMs: number;
|
|
378
|
+
private readonly baseHeaders;
|
|
379
|
+
constructor(baseUrl?: string, options?: AgentClientOptions);
|
|
267
380
|
health(): Promise<Json>;
|
|
268
381
|
/**
|
|
269
382
|
* Create a new session and run the agent with the given input.
|
|
@@ -323,10 +436,21 @@ export declare class AgentClient {
|
|
|
323
436
|
* events (`prompt_start`, `prompt_delta`, `prompt_end`), then a final
|
|
324
437
|
* `done` event. Prompt events include `prompt_type` so UIs can filter
|
|
325
438
|
* progress streams separately from final-answer streams.
|
|
439
|
+
*
|
|
440
|
+
* `options.policyProfile` mirrors `run()`: a built-in profile layered on
|
|
441
|
+
* the server policy with stricter-wins semantics for this session.
|
|
326
442
|
*/
|
|
327
|
-
stream(input: Json
|
|
443
|
+
stream(input: Json, options?: {
|
|
444
|
+
policyProfile?: PolicyProfile;
|
|
445
|
+
}): AsyncGenerator<StreamEvent, void, void>;
|
|
328
446
|
private sessionFrom;
|
|
329
447
|
private getJSON;
|
|
330
448
|
private postJSON;
|
|
331
449
|
private postJSONWithStatus;
|
|
450
|
+
/**
|
|
451
|
+
* One HTTP exchange with timeout and (for idempotent requests) retries.
|
|
452
|
+
* Resolves with a 2xx `Response`; throws {@link HttpError},
|
|
453
|
+
* {@link TimeoutError}, or {@link ConnectionError} otherwise.
|
|
454
|
+
*/
|
|
455
|
+
private request;
|
|
332
456
|
}
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,8 @@ export class Session {
|
|
|
59
59
|
pendingSignalName;
|
|
60
60
|
pendingSignalNames;
|
|
61
61
|
pendingSignalDeadline;
|
|
62
|
+
pendingDetails;
|
|
63
|
+
runId;
|
|
62
64
|
constructor(id, status, input, output = null, error = null, callLog = [], pendingPrompt = null, client = null, snapshotManifest = null,
|
|
63
65
|
/**
|
|
64
66
|
* When the run is `paused` at a `chidori.signal(name)` listen point, the
|
|
@@ -68,7 +70,7 @@ export class Session {
|
|
|
68
70
|
pendingSignalName = null,
|
|
69
71
|
/**
|
|
70
72
|
* The full awaited name set when paused on a signal listen point: `[name]`
|
|
71
|
-
* for `chidori.signal(name)`, the listen set for `chidori.
|
|
73
|
+
* for `chidori.signal(name)`, the listen set for the fan-in `chidori.signal(names)`.
|
|
72
74
|
* Empty for non-signal states.
|
|
73
75
|
*/
|
|
74
76
|
pendingSignalNames = [],
|
|
@@ -77,7 +79,19 @@ export class Session {
|
|
|
77
79
|
* `timeoutMs`; the server resolves the pause with the timeout sentinel
|
|
78
80
|
* when it passes. `null` when the pause has no timeout.
|
|
79
81
|
*/
|
|
80
|
-
pendingSignalDeadline = null
|
|
82
|
+
pendingSignalDeadline = null,
|
|
83
|
+
/**
|
|
84
|
+
* The artifact under review for an `input()` pause created with
|
|
85
|
+
* `{ details }` (a draft, a diff) — surface it so a human never approves
|
|
86
|
+
* blind. `null` when the pause carries no details.
|
|
87
|
+
*/
|
|
88
|
+
pendingDetails = null,
|
|
89
|
+
/**
|
|
90
|
+
* The durable run directory id (`.chidori/runs/<run_id>`) this session
|
|
91
|
+
* journals into. Deliberately distinct from the session id: `chidori
|
|
92
|
+
* resume <agent.ts> <run_id>` and `chidori trace <run_id>` take THIS id.
|
|
93
|
+
*/
|
|
94
|
+
runId = null) {
|
|
81
95
|
this.id = id;
|
|
82
96
|
this.status = status;
|
|
83
97
|
this.input = input;
|
|
@@ -90,6 +104,8 @@ export class Session {
|
|
|
90
104
|
this.pendingSignalName = pendingSignalName;
|
|
91
105
|
this.pendingSignalNames = pendingSignalNames;
|
|
92
106
|
this.pendingSignalDeadline = pendingSignalDeadline;
|
|
107
|
+
this.pendingDetails = pendingDetails;
|
|
108
|
+
this.runId = runId;
|
|
93
109
|
}
|
|
94
110
|
get ok() {
|
|
95
111
|
return this.status === "completed";
|
|
@@ -115,6 +131,84 @@ export class Session {
|
|
|
115
131
|
return this.client.replay(cp);
|
|
116
132
|
}
|
|
117
133
|
}
|
|
134
|
+
/** Base class for every error the SDK throws. `catch (e) { if (e instanceof
|
|
135
|
+
* AgentClientError) ... }` covers HTTP failures, timeouts, and connection
|
|
136
|
+
* errors alike. */
|
|
137
|
+
export class AgentClientError extends Error {
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A non-2xx HTTP response. Carries the parsed `status` so callers can
|
|
141
|
+
* distinguish the server's documented semantics — e.g. for `client.signal`:
|
|
142
|
+
* 400 (empty name), 404 (unknown session), 409 (terminal run) — instead of
|
|
143
|
+
* string-matching the message.
|
|
144
|
+
*/
|
|
145
|
+
export class HttpError extends AgentClientError {
|
|
146
|
+
method;
|
|
147
|
+
path;
|
|
148
|
+
status;
|
|
149
|
+
body;
|
|
150
|
+
detail;
|
|
151
|
+
constructor(
|
|
152
|
+
/** HTTP method of the failed request. */
|
|
153
|
+
method,
|
|
154
|
+
/** Request path relative to the base URL. */
|
|
155
|
+
path,
|
|
156
|
+
/** HTTP status code (400, 404, 409, 500, ...). */
|
|
157
|
+
status,
|
|
158
|
+
/** Raw response body text (may be empty). */
|
|
159
|
+
body,
|
|
160
|
+
/** The server's `error` field, when the body was `{ "error": ... }`. */
|
|
161
|
+
detail = null) {
|
|
162
|
+
super(`${method} ${path} failed: HTTP ${status}${detail || body ? `: ${detail ?? body}` : ""}`);
|
|
163
|
+
this.method = method;
|
|
164
|
+
this.path = path;
|
|
165
|
+
this.status = status;
|
|
166
|
+
this.body = body;
|
|
167
|
+
this.detail = detail;
|
|
168
|
+
this.name = "HttpError";
|
|
169
|
+
}
|
|
170
|
+
static async fromResponse(method, path, resp) {
|
|
171
|
+
const body = await resp.text().catch(() => "");
|
|
172
|
+
let detail = null;
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(body);
|
|
175
|
+
if (typeof parsed.error === "string")
|
|
176
|
+
detail = parsed.error;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// not JSON — leave detail null
|
|
180
|
+
}
|
|
181
|
+
return new HttpError(method, path, resp.status, body, detail);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** The request exceeded the client's `timeoutMs` without completing. */
|
|
185
|
+
export class TimeoutError extends AgentClientError {
|
|
186
|
+
method;
|
|
187
|
+
path;
|
|
188
|
+
timeoutMs;
|
|
189
|
+
constructor(method, path, timeoutMs) {
|
|
190
|
+
super(`${method} ${path} timed out after ${timeoutMs}ms`);
|
|
191
|
+
this.method = method;
|
|
192
|
+
this.path = path;
|
|
193
|
+
this.timeoutMs = timeoutMs;
|
|
194
|
+
this.name = "TimeoutError";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** The request never produced an HTTP response (refused, reset, DNS, ...). */
|
|
198
|
+
export class ConnectionError extends AgentClientError {
|
|
199
|
+
method;
|
|
200
|
+
path;
|
|
201
|
+
constructor(method, path, cause) {
|
|
202
|
+
super(`${method} ${path} failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
203
|
+
cause,
|
|
204
|
+
});
|
|
205
|
+
this.method = method;
|
|
206
|
+
this.path = path;
|
|
207
|
+
this.name = "ConnectionError";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Response statuses worth retrying on idempotent requests. */
|
|
211
|
+
const RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
|
|
118
212
|
/**
|
|
119
213
|
* HTTP client for an `chidori serve` instance.
|
|
120
214
|
*
|
|
@@ -126,11 +220,27 @@ export class Session {
|
|
|
126
220
|
* const cp = await session.checkpoint();
|
|
127
221
|
* const replayed = await client.replay(cp); // zero LLM calls
|
|
128
222
|
* ```
|
|
223
|
+
*
|
|
224
|
+
* Failures throw typed errors (all extending {@link AgentClientError}):
|
|
225
|
+
* {@link HttpError} with a `.status` for non-2xx responses,
|
|
226
|
+
* {@link TimeoutError} after `timeoutMs`, {@link ConnectionError} when no
|
|
227
|
+
* response arrived at all.
|
|
129
228
|
*/
|
|
130
229
|
export class AgentClient {
|
|
131
230
|
baseUrl;
|
|
132
|
-
|
|
231
|
+
timeoutMs;
|
|
232
|
+
retries;
|
|
233
|
+
retryDelayMs;
|
|
234
|
+
baseHeaders;
|
|
235
|
+
constructor(baseUrl = "http://localhost:8080", options = {}) {
|
|
133
236
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
237
|
+
this.timeoutMs = options.timeoutMs ?? 300_000;
|
|
238
|
+
this.retries = options.retries ?? 2;
|
|
239
|
+
this.retryDelayMs = options.retryDelayMs ?? 250;
|
|
240
|
+
this.baseHeaders = {
|
|
241
|
+
...(options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}),
|
|
242
|
+
...(options.headers ?? {}),
|
|
243
|
+
};
|
|
134
244
|
}
|
|
135
245
|
async health() {
|
|
136
246
|
return (await this.getJSON("/health"));
|
|
@@ -224,15 +334,44 @@ export class AgentClient {
|
|
|
224
334
|
* events (`prompt_start`, `prompt_delta`, `prompt_end`), then a final
|
|
225
335
|
* `done` event. Prompt events include `prompt_type` so UIs can filter
|
|
226
336
|
* progress streams separately from final-answer streams.
|
|
337
|
+
*
|
|
338
|
+
* `options.policyProfile` mirrors `run()`: a built-in profile layered on
|
|
339
|
+
* the server policy with stricter-wins semantics for this session.
|
|
227
340
|
*/
|
|
228
|
-
async *stream(input) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
341
|
+
async *stream(input, options) {
|
|
342
|
+
// The timeout covers connection establishment (until response headers
|
|
343
|
+
// arrive), not the open event stream — a healthy run may stream for a
|
|
344
|
+
// long time between events.
|
|
345
|
+
const controller = new AbortController();
|
|
346
|
+
const timer = this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : null;
|
|
347
|
+
const body = { input };
|
|
348
|
+
if (options?.policyProfile) {
|
|
349
|
+
body.policy_profile = options.policyProfile;
|
|
350
|
+
}
|
|
351
|
+
let resp;
|
|
352
|
+
try {
|
|
353
|
+
resp = await fetch(`${this.baseUrl}/sessions/stream`, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers: {
|
|
356
|
+
...this.baseHeaders,
|
|
357
|
+
"Content-Type": "application/json",
|
|
358
|
+
Accept: "text/event-stream",
|
|
359
|
+
},
|
|
360
|
+
body: JSON.stringify(body),
|
|
361
|
+
signal: controller.signal,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
throw controller.signal.aborted
|
|
366
|
+
? new TimeoutError("POST", "/sessions/stream", this.timeoutMs)
|
|
367
|
+
: new ConnectionError("POST", "/sessions/stream", err);
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
if (timer)
|
|
371
|
+
clearTimeout(timer);
|
|
372
|
+
}
|
|
234
373
|
if (!resp.ok || !resp.body) {
|
|
235
|
-
throw
|
|
374
|
+
throw await HttpError.fromResponse("POST", "/sessions/stream", resp);
|
|
236
375
|
}
|
|
237
376
|
// Minimal SSE parser — just enough for the events our server emits.
|
|
238
377
|
const reader = resp.body.getReader();
|
|
@@ -255,31 +394,75 @@ export class AgentClient {
|
|
|
255
394
|
}
|
|
256
395
|
// -- internals ----------------------------------------------------------
|
|
257
396
|
sessionFrom(data, input) {
|
|
258
|
-
return new Session(data.id, data.status ?? "failed", input, data.output ?? null, data.error ?? null, data.call_log ?? [], data.pending_prompt ?? null, this, data.snapshot_manifest ?? null, data.pending_signal_name ?? null, data.pending_signal_names ?? [], data.pending_signal_deadline ?? null);
|
|
397
|
+
return new Session(data.id, data.status ?? "failed", input, data.output ?? null, data.error ?? null, data.call_log ?? [], data.pending_prompt ?? null, this, data.snapshot_manifest ?? null, data.pending_signal_name ?? null, data.pending_signal_names ?? [], data.pending_signal_deadline ?? null, data.pending_details ?? null, data.run_id ?? null);
|
|
259
398
|
}
|
|
260
399
|
async getJSON(path) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
400
|
+
// GETs are idempotent: retry connection failures, timeouts, and
|
|
401
|
+
// retryable statuses with exponential backoff.
|
|
402
|
+
const resp = await this.request("GET", path, undefined, this.retries);
|
|
264
403
|
return await resp.json();
|
|
265
404
|
}
|
|
266
405
|
async postJSON(path, body) {
|
|
267
406
|
return (await this.postJSONWithStatus(path, body)).data;
|
|
268
407
|
}
|
|
269
408
|
async postJSONWithStatus(path, body) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
headers: { "Content-Type": "application/json" },
|
|
273
|
-
body: JSON.stringify(body),
|
|
274
|
-
});
|
|
275
|
-
if (!resp.ok)
|
|
276
|
-
throw await httpError(resp);
|
|
409
|
+
// POSTs are never retried: run/resume/signal are not idempotent.
|
|
410
|
+
const resp = await this.request("POST", path, body, 0);
|
|
277
411
|
return { status: resp.status, data: (await resp.json()) };
|
|
278
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* One HTTP exchange with timeout and (for idempotent requests) retries.
|
|
415
|
+
* Resolves with a 2xx `Response`; throws {@link HttpError},
|
|
416
|
+
* {@link TimeoutError}, or {@link ConnectionError} otherwise.
|
|
417
|
+
*/
|
|
418
|
+
async request(method, path, body, retries) {
|
|
419
|
+
let lastError = null;
|
|
420
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
421
|
+
if (attempt > 0) {
|
|
422
|
+
await sleep(this.retryDelayMs * 2 ** (attempt - 1));
|
|
423
|
+
}
|
|
424
|
+
const controller = new AbortController();
|
|
425
|
+
const timer = this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : null;
|
|
426
|
+
try {
|
|
427
|
+
let resp;
|
|
428
|
+
try {
|
|
429
|
+
resp = await fetch(this.baseUrl + path, {
|
|
430
|
+
method,
|
|
431
|
+
signal: controller.signal,
|
|
432
|
+
headers: body !== undefined
|
|
433
|
+
? { ...this.baseHeaders, "Content-Type": "application/json" }
|
|
434
|
+
: this.baseHeaders,
|
|
435
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
throw controller.signal.aborted
|
|
440
|
+
? new TimeoutError(method, path, this.timeoutMs)
|
|
441
|
+
: new ConnectionError(method, path, err);
|
|
442
|
+
}
|
|
443
|
+
if (!resp.ok)
|
|
444
|
+
throw await HttpError.fromResponse(method, path, resp);
|
|
445
|
+
return resp;
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
const retryable = err instanceof TimeoutError ||
|
|
449
|
+
err instanceof ConnectionError ||
|
|
450
|
+
(err instanceof HttpError && RETRYABLE_STATUS.has(err.status));
|
|
451
|
+
if (!retryable || attempt === retries)
|
|
452
|
+
throw err;
|
|
453
|
+
lastError = err;
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
if (timer)
|
|
457
|
+
clearTimeout(timer);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Unreachable: the loop either returns or throws on the last attempt.
|
|
461
|
+
throw lastError ?? new ConnectionError(method, path, "retries exhausted");
|
|
462
|
+
}
|
|
279
463
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return new Error(`HTTP ${resp.status}: ${text}`);
|
|
464
|
+
function sleep(ms) {
|
|
465
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
283
466
|
}
|
|
284
467
|
function parseSseFrame(frame) {
|
|
285
468
|
let event = "message";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1kbirds/chidori",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "TypeScript SDK for the Chidori agent framework — HTTP client plus agent authoring types.",
|
|
5
5
|
"keywords": ["ai", "agents", "typescript", "llm", "replay"],
|
|
6
6
|
"publishConfig": { "access": "public" },
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"files": ["dist", "src", "README.md"],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc",
|
|
30
|
+
"prepare": "npm run build",
|
|
30
31
|
"typecheck": "tsc --noEmit",
|
|
31
32
|
"pretest": "tsc",
|
|
32
33
|
"test": "node --test test/*.test.mjs"
|