@orkestrel/agent 0.0.21 → 0.0.22

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.
@@ -96,6 +96,56 @@ var MESSAGE_TOKEN_OVERHEAD = 4;
96
96
  * exact count.
97
97
  */
98
98
  var IMAGE_TOKEN_ESTIMATE = 512;
99
+ /**
100
+ * Holds the default provider deadline in milliseconds — `120_000`, the wall-clock bound a call
101
+ * runs under when `AgentProviderInput.timeout` is omitted, folded with the caller's signal so
102
+ * whichever trips first cancels the call.
103
+ */
104
+ var DEFAULT_PROVIDER_TIMEOUT = 12e4;
105
+ /**
106
+ * Bounds the decoded error excerpt's input in bytes — `2048`, the leading bytes of a non-OK
107
+ * response body handed to the decoder before the read cancels the remainder, so a `ProviderError`
108
+ * message never carries a longer excerpt.
109
+ */
110
+ var MAX_ERROR_BODY_LENGTH = 2048;
111
+ /**
112
+ * Holds the default relay request limit in bytes — `1_048_576`, the byte budget a relay applies
113
+ * to an inbound body when `RelayOptions.limit` is omitted, refusing a body that reaches it.
114
+ */
115
+ var DEFAULT_RELAY_LIMIT = 1048576;
116
+ /**
117
+ * Names the relay's newline-delimited JSON content type — `'application/x-ndjson; charset=utf-8'`,
118
+ * the header a relay response carries beside `cache-control: no-store`.
119
+ */
120
+ var RELAY_CONTENT_TYPE = "application/x-ndjson; charset=utf-8";
121
+ /**
122
+ * Names the public message for an unexpected upstream relay failure — `'relay provider failed'`,
123
+ * the fixed text every `error` frame carries, so an upstream failure's own message never reaches
124
+ * the browser.
125
+ */
126
+ var RELAY_PROVIDER_MESSAGE = "relay provider failed";
127
+ /**
128
+ * Names the status a relay answers when the `authorize` callback returns anything but `true` or throws —
129
+ * `401`, carried with no body and reaching the browser as a `ProviderError` with the `HTTP` code.
130
+ */
131
+ var UNAUTHORIZED_RELAY_STATUS = 401;
132
+ /**
133
+ * Names the status a relay answers when the upstream provider call cannot be constructed — `502`,
134
+ * carried with no body after `provider.stream` was entered and threw before returning its
135
+ * iterator.
136
+ */
137
+ var UPSTREAM_RELAY_STATUS = 502;
138
+ /**
139
+ * Names the status a relay answers for a body that is missing, unreadable, or rejected by
140
+ * `providerRequestContract` — `400`, carried with no body and reaching the browser as a
141
+ * `ProviderError` with the `HTTP` code.
142
+ */
143
+ var INVALID_RELAY_STATUS = 400;
144
+ /**
145
+ * Names the status a relay answers for a request body at or above its byte budget — `413`,
146
+ * carried with no body and answered for an aborted inbound read as well.
147
+ */
148
+ var OVERSIZED_RELAY_STATUS = 413;
99
149
  //#endregion
100
150
  //#region src/core/errors.ts
101
151
  /**
@@ -107,14 +157,16 @@ var IMAGE_TOKEN_ESTIMATE = 512;
107
157
  * Lets a caller recover the partial content (and any tool calls / usage seen so far)
108
158
  * on cancellation: `catch` the throw, narrow with {@link isProviderAbortError}, and
109
159
  * read `partial`. `code` is the machine-readable condition (`'ABORT'` — the only one this
110
- * error reports), so a `catch` branches on it rather than on the message string.
160
+ * error reports), so a `catch` branches on it rather than on the message string. `cause`
161
+ * holds the failure the cancel superseded when a throw raced the abort — the wire decoder's
162
+ * {@link ProviderError}, say — and is undefined when the cancel was the only failure.
111
163
  */
112
164
  var ProviderAbortError = class extends Error {
113
165
  /** Names the machine-readable condition — `'ABORT'`: a stream cancelled mid-flight. */
114
166
  code = "ABORT";
115
167
  partial;
116
- constructor(partial) {
117
- super("provider stream aborted");
168
+ constructor(partial, options) {
169
+ super("provider stream aborted", options);
118
170
  this.name = "ProviderAbortError";
119
171
  this.partial = partial;
120
172
  }
@@ -275,6 +327,140 @@ var AgentError = class extends Error {
275
327
  function isAgentError(value) {
276
328
  return value instanceof AgentError;
277
329
  }
330
+ /**
331
+ * Reports a coded provider failure with its HTTP status and underlying cause when available.
332
+ *
333
+ * @remarks
334
+ * The provider base throws this error for a non-OK HTTP response, a missing response
335
+ * body, or a missing settled result when strict assembly is enabled. Concrete wire
336
+ * decoders also use it for malformed records and relayed provider failures.
337
+ * `status` is present only for an `HTTP` failure; other codes leave it undefined.
338
+ */
339
+ var ProviderError = class extends Error {
340
+ /** Names the machine-readable condition — `'HTTP'`: a non-OK response, including a relay refusing an oversized request with 413; `'PROTOCOL'`: a missing response body, a malformed wire record, or a strict stream with no settled result; `'PROVIDER'`: an upstream failure carried by a relay error record. */
341
+ code;
342
+ /** Holds the response status for an HTTP failure, or undefined for other codes. */
343
+ status;
344
+ constructor(code, message, options) {
345
+ super(message, options);
346
+ this.name = "ProviderError";
347
+ this.code = code;
348
+ this.status = options?.status;
349
+ }
350
+ };
351
+ /**
352
+ * Narrows a caught value to the provider failure class through instanceof.
353
+ *
354
+ * @param value - The caught value
355
+ * @returns True if the value is a provider failure; false otherwise
356
+ * @example
357
+ * ```ts
358
+ * isProviderError(new ProviderError('HTTP', 'unavailable', { status: 503 })) // true
359
+ * ```
360
+ */
361
+ function isProviderError(value) {
362
+ return value instanceof ProviderError;
363
+ }
364
+ //#endregion
365
+ //#region src/core/shapers.ts
366
+ /**
367
+ * Describes a tool call's JSON wire projection.
368
+ *
369
+ * @remarks
370
+ * The wire is strictly narrower than the domain: non-JSON arguments are refused.
371
+ * ToolCall.caller is not in this shape and never crosses a wire. The guard refuses
372
+ * that extra member; the contract parser drops it.
373
+ */
374
+ var toolCallShape = (0, _orkestrel_contract.objectShape)({
375
+ id: (0, _orkestrel_contract.stringShape)(),
376
+ name: (0, _orkestrel_contract.stringShape)(),
377
+ arguments: (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)())
378
+ });
379
+ /**
380
+ * Describes a conversation message's JSON wire projection.
381
+ *
382
+ * @remarks
383
+ * The wire is strictly narrower than Message: non-JSON call arguments are refused,
384
+ * and caller context is refused by the guard or dropped by the parser.
385
+ */
386
+ var messageShape = (0, _orkestrel_contract.objectShape)({
387
+ id: (0, _orkestrel_contract.stringShape)(),
388
+ role: (0, _orkestrel_contract.literalShape)([
389
+ "system",
390
+ "user",
391
+ "assistant",
392
+ "tool"
393
+ ]),
394
+ content: (0, _orkestrel_contract.stringShape)(),
395
+ calls: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(toolCallShape)),
396
+ images: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()))
397
+ });
398
+ /**
399
+ * Describes a provider request's JSON wire projection.
400
+ *
401
+ * @remarks
402
+ * The wire is strictly narrower than ProviderRequest: non-JSON arguments, parameters,
403
+ * or schema members are refused, and caller context is refused or dropped.
404
+ */
405
+ var providerRequestShape = (0, _orkestrel_contract.objectShape)({
406
+ messages: (0, _orkestrel_contract.arrayShape)(messageShape),
407
+ tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
408
+ name: (0, _orkestrel_contract.stringShape)(),
409
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
410
+ parameters: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)()))
411
+ }))),
412
+ options: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
413
+ think: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)()),
414
+ schema: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)()))
415
+ }))
416
+ });
417
+ /**
418
+ * Describes a provider result's JSON wire projection.
419
+ *
420
+ * @remarks
421
+ * The wire is strictly narrower than ProviderResult: non-JSON call arguments are
422
+ * refused, and caller context is refused or dropped.
423
+ */
424
+ var providerResultShape = (0, _orkestrel_contract.objectShape)({
425
+ content: (0, _orkestrel_contract.stringShape)(),
426
+ thinking: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
427
+ tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(toolCallShape)),
428
+ usage: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
429
+ prompt: (0, _orkestrel_contract.numberShape)(),
430
+ completion: (0, _orkestrel_contract.numberShape)(),
431
+ total: (0, _orkestrel_contract.numberShape)()
432
+ }))
433
+ });
434
+ /**
435
+ * Describes the channel-discriminated JSON relay wire projection.
436
+ *
437
+ * @remarks
438
+ * The wire is strictly narrower than RelayFrame through its result and partial
439
+ * fields: non-JSON arguments are refused, and caller context is refused or dropped.
440
+ */
441
+ var relayFrameShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
442
+ channel: (0, _orkestrel_contract.literalShape)(["content", "thinking"]),
443
+ text: (0, _orkestrel_contract.stringShape)()
444
+ }), (0, _orkestrel_contract.objectShape)({
445
+ channel: (0, _orkestrel_contract.literalShape)(["result"]),
446
+ result: providerResultShape
447
+ }), (0, _orkestrel_contract.objectShape)({
448
+ channel: (0, _orkestrel_contract.literalShape)(["abort"]),
449
+ partial: providerResultShape
450
+ }), (0, _orkestrel_contract.objectShape)({
451
+ channel: (0, _orkestrel_contract.literalShape)(["error"]),
452
+ message: (0, _orkestrel_contract.stringShape)()
453
+ }));
454
+ //#endregion
455
+ //#region src/core/contracts.ts
456
+ /** Validates and projects conversation messages at a JSON wire boundary. */
457
+ var messageContract = (0, _orkestrel_contract.createContract)(messageShape);
458
+ /** Validates and projects provider requests at a JSON wire boundary. */
459
+ var providerRequestContract = (0, _orkestrel_contract.createContract)(providerRequestShape);
460
+ /** Validates and projects provider results at a JSON wire boundary. */
461
+ var providerResultContract = (0, _orkestrel_contract.createContract)(providerResultShape);
462
+ /** Validates and projects channel-discriminated relay frames at a JSON wire boundary. */
463
+ var relayFrameContract = (0, _orkestrel_contract.createContract)(relayFrameShape);
278
464
  //#endregion
279
465
  //#region src/core/helpers.ts
280
466
  /**
@@ -582,11 +768,13 @@ function sanitizeUsage(usage) {
582
768
  * @example
583
769
  * ```ts
584
770
  * joinThinking(undefined, 'first') // 'first'
771
+ * joinThinking('', 'x') // 'x'
585
772
  * joinThinking('first', 'second') // 'first\n\nsecond'
586
773
  * ```
587
774
  */
588
775
  function joinThinking(running, next) {
589
- return running === void 0 ? next : `${running}\n\n${next}`;
776
+ if (running === void 0 || running.length === 0) return next;
777
+ return next.length === 0 ? running : `${running}\n\n${next}`;
590
778
  }
591
779
  /**
592
780
  * Adds two {@link TokenUsage} values field by field — the running total an agent run keeps
@@ -943,6 +1131,133 @@ function intersectKeys(parent, child) {
943
1131
  const allowed = new Set(parent);
944
1132
  return child.filter((key) => allowed.has(key));
945
1133
  }
1134
+ /**
1135
+ * Assembles a provider result with only populated optional fields.
1136
+ *
1137
+ * @param content - The authoritative answer
1138
+ * @param thinking - The joined reasoning
1139
+ * @param tools - The accumulated calls
1140
+ * @param usage - The reported token usage
1141
+ * @returns The assembled result
1142
+ * @example
1143
+ * ```ts
1144
+ * buildProviderResult('ok', '', [], undefined) // { content: 'ok' }
1145
+ * ```
1146
+ */
1147
+ function buildProviderResult(content, thinking, tools, usage) {
1148
+ return {
1149
+ content,
1150
+ ...thinking.length === 0 ? {} : { thinking },
1151
+ ...tools.length === 0 ? {} : { tools },
1152
+ ...usage === void 0 ? {} : { usage }
1153
+ };
1154
+ }
1155
+ /**
1156
+ * Reads a UTF-8 prefix of a byte stream and cancels its remainder.
1157
+ *
1158
+ * @remarks
1159
+ * The `limit` parameter bounds bytes passed to the decoder, including a partial final
1160
+ * character. An omitted limit reads to completion. A source may deliver a chunk larger
1161
+ * than the remaining limit; its unused bytes are discarded without decoding.
1162
+ * The read can overshoot by one source chunk. An abort leaves completion false.
1163
+ *
1164
+ * @param body - The readable byte stream
1165
+ * @param limit - The maximum byte prefix, or undefined for the complete stream
1166
+ * @param signal - The optional cancellation bound; abort returns the decoded prefix
1167
+ * @returns The decoded prefix and whether EOF occurred within the byte budget
1168
+ * @example
1169
+ * ```ts
1170
+ * import { readText } from '@orkestrel/agent'
1171
+ *
1172
+ * const body = new Response('answer').body
1173
+ * if (body !== null) await readText(body, 3) // { text: 'ans', complete: false }
1174
+ * ```
1175
+ */
1176
+ async function readText(body, limit, signal) {
1177
+ const reader = body.getReader();
1178
+ const decoder = new TextDecoder();
1179
+ const cleanup = new AbortController();
1180
+ let remaining = limit ?? Infinity;
1181
+ let text = "";
1182
+ let complete = false;
1183
+ try {
1184
+ signal?.addEventListener("abort", () => {
1185
+ reader.cancel(signal.reason).catch(() => {});
1186
+ }, {
1187
+ once: true,
1188
+ signal: cleanup.signal
1189
+ });
1190
+ if (signal?.aborted) await reader.cancel(signal.reason).catch(() => {});
1191
+ while (remaining > 0) {
1192
+ if (signal?.aborted) break;
1193
+ const step = await reader.read();
1194
+ if (signal?.aborted) break;
1195
+ if (step.done) {
1196
+ complete = true;
1197
+ break;
1198
+ }
1199
+ const bytes = step.value.subarray(0, remaining);
1200
+ text += decoder.decode(bytes, { stream: true });
1201
+ remaining -= bytes.byteLength;
1202
+ }
1203
+ return {
1204
+ text: text + decoder.decode(),
1205
+ complete
1206
+ };
1207
+ } finally {
1208
+ cleanup.abort();
1209
+ try {
1210
+ await reader.cancel();
1211
+ } catch {} finally {
1212
+ reader.releaseLock();
1213
+ }
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Decodes UTF-8 chunks with a final flush and releases the stream on every exit.
1218
+ *
1219
+ * @param body - The readable byte stream
1220
+ * @param signal - The optional cancellation bound; abort ends iteration without further chunks
1221
+ * @returns Decoded text chunks, including a held decoder tail
1222
+ * @example
1223
+ * ```ts
1224
+ * const body = new Response('answer').body
1225
+ * if (body !== null) for await (const chunk of readChunks(body)) render(chunk)
1226
+ * ```
1227
+ */
1228
+ async function* readChunks(body, signal) {
1229
+ const reader = body.getReader();
1230
+ const decoder = new TextDecoder();
1231
+ const cleanup = new AbortController();
1232
+ try {
1233
+ signal?.addEventListener("abort", () => {
1234
+ reader.cancel(signal.reason).catch(() => {});
1235
+ }, {
1236
+ once: true,
1237
+ signal: cleanup.signal
1238
+ });
1239
+ if (signal?.aborted) {
1240
+ await reader.cancel(signal.reason).catch(() => {});
1241
+ return;
1242
+ }
1243
+ for (;;) {
1244
+ const step = await reader.read();
1245
+ if (signal?.aborted) return;
1246
+ if (step.done) break;
1247
+ const chunk = decoder.decode(step.value, { stream: true });
1248
+ if (chunk.length > 0) yield chunk;
1249
+ }
1250
+ const tail = decoder.decode();
1251
+ if (tail.length > 0) yield tail;
1252
+ } finally {
1253
+ cleanup.abort();
1254
+ try {
1255
+ await reader.cancel();
1256
+ } catch {} finally {
1257
+ reader.releaseLock();
1258
+ }
1259
+ }
1260
+ }
946
1261
  //#endregion
947
1262
  //#region src/core/conversations/Conversation.ts
948
1263
  /**
@@ -1815,7 +2130,7 @@ var Agent = class {
1815
2130
  } catch (error) {
1816
2131
  if (abort.signal.aborted) {
1817
2132
  if (isProviderAbortError(error)) {
1818
- if (error.partial.thinking !== void 0) thinking = joinThinking(thinking, error.partial.thinking);
2133
+ if (error.partial.thinking !== void 0 && error.partial.thinking.length > 0) thinking = joinThinking(thinking, error.partial.thinking);
1819
2134
  if (error.partial.usage !== void 0) {
1820
2135
  const abortUsage = sanitizeUsage(error.partial.usage);
1821
2136
  budget?.consume({
@@ -2126,41 +2441,32 @@ var Authority = class {
2126
2441
  //#endregion
2127
2442
  //#region src/core/validators.ts
2128
2443
  /**
2129
- * Checks whether an `unknown` is structurally a {@link Message} record — the per-message step of
2130
- * the {@link isConversationSnapshot} and {@link isSection} read-boundary narrows, where a present
2131
- * `calls` must be an array of valid {@link import('@orkestrel/tool').ToolCall}s. Total, never
2132
- * throwing, and never an assertion; the conversation analogue of
2133
- * {@link import('@orkestrel/workspace').isFile}.
2444
+ * Checks whether a value satisfies the domain conversation-message contract.
2134
2445
  *
2135
2446
  * @remarks
2136
- * A total guard (it never throws — adversarial input returns `false`). It checks the message's
2137
- * shape: a record with a `string` `id`, a `string` `role`, a `string` `content`, and — when present
2138
- * — a `calls` array every element of which is a valid
2139
- * {@link import('@orkestrel/tool').ToolCall} ({@link isToolCall} — the
2140
- * fail-closed deepening: a tampered `calls` element rejects the message, so the snapshot
2141
- * reads back as absent rather than replaying a malformed call) and an `images` that is an array
2142
- * (an absent optional passes). The `role` is left as a broad `string` here (an open
2143
- * {@link import('./types.js').MessageRole}, so any storage-read role string is accepted
2144
- * defensively rather than rejected against the current literal set). Enough to safely impose the
2145
- * {@link Message} type at
2146
- * a storage boundary without a cast.
2147
- *
2148
- * @param value - The value to test (one element of a snapshot's `messages` / a section's `messages`)
2149
- * @returns True if `value` has the structural shape of a {@link Message}; false otherwise
2447
+ * Roles belong to MessageRole and image elements are strings. Tool arguments may
2448
+ * carry non-JSON values, as the domain type permits; the message wire contract is
2449
+ * narrower. Unreadable fields and hostile inputs return false.
2150
2450
  *
2451
+ * @param value - The unknown message candidate
2452
+ * @returns True if the domain message fields are valid; false otherwise
2151
2453
  * @example
2152
2454
  * ```ts
2153
2455
  * isMessage({ id: '1', role: 'user', content: 'hi' }) // true
2154
- * isMessage({ id: '1', role: 'assistant', content: '', calls: [] }) // true
2155
- * isMessage({ id: '1', role: 'user' }) // false (missing content)
2156
- * isMessage({ id: '1', role: 'assistant', content: '', calls: [null] }) // false (malformed call)
2456
+ * isMessage({ id: '1', role: 'other', content: '' }) // false
2457
+ * isMessage({ id: '1', role: 'user', content: '', images: [1] }) // false
2157
2458
  * ```
2158
2459
  */
2159
2460
  function isMessage(value) {
2160
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
2161
- if (!(0, _orkestrel_contract.isString)(value.id) || !(0, _orkestrel_contract.isString)(value.role) || !(0, _orkestrel_contract.isString)(value.content)) return false;
2162
- if (value.calls !== void 0 && !((0, _orkestrel_contract.isArray)(value.calls) && value.calls.every(_orkestrel_tool.isToolCall))) return false;
2163
- return value.images === void 0 || (0, _orkestrel_contract.isArray)(value.images);
2461
+ const checked = (0, _orkestrel_contract.attempt)(() => {
2462
+ if (!(0, _orkestrel_contract.isRecord)(value)) return false;
2463
+ const { id, role, content, calls, images } = value;
2464
+ if (!(0, _orkestrel_contract.isString)(id) || !(0, _orkestrel_contract.isString)(content)) return false;
2465
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return false;
2466
+ if (calls !== void 0 && !(0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall)(calls)) return false;
2467
+ return images === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(images);
2468
+ });
2469
+ return checked.success && checked.value;
2164
2470
  }
2165
2471
  /**
2166
2472
  * Checks whether an `unknown` is structurally a {@link Section} record — a `string` `id` and
@@ -2386,6 +2692,493 @@ var MemoryConversationStore = class {
2386
2692
  }
2387
2693
  };
2388
2694
  //#endregion
2695
+ //#region src/core/AgentProvider.ts
2696
+ /**
2697
+ * Implements bounded HTTP streaming and result assembly behind concrete wire seams.
2698
+ *
2699
+ * @remarks
2700
+ * Every call owns its parser, splitter, deadline, and accumulation. Success bodies
2701
+ * have no size limit. A qwen3 implicit-open reclassification corrects the final
2702
+ * content while content deltas already yielded cannot be recalled. A subclass fills
2703
+ * `name`, `frame`, `body`, `read`, and `finish`; the constructor takes the `split`
2704
+ * and `strict` switches to control reasoning separation and settled-result requirements.
2705
+ *
2706
+ * @example Writing a provider for a new wire
2707
+ * ```ts
2708
+ * import type {
2709
+ * ProviderIncrement,
2710
+ * ProviderOptions,
2711
+ * ProviderParserInterface,
2712
+ * ProviderRequest,
2713
+ * } from '@orkestrel/agent'
2714
+ * import { AgentProvider } from '@orkestrel/agent'
2715
+ *
2716
+ * class TextFrame implements ProviderParserInterface<string> {
2717
+ * parse(chunk: string): readonly string[] {
2718
+ * return [chunk]
2719
+ * }
2720
+ * clear(): void {} // Raw text retains no framing state.
2721
+ * }
2722
+ *
2723
+ * interface TextOptions extends ProviderOptions {
2724
+ * readonly url: string
2725
+ * }
2726
+ *
2727
+ * class TextProvider extends AgentProvider<string> {
2728
+ * readonly name = 'text'
2729
+ * constructor(options: TextOptions) {
2730
+ * super({ ...options, path: '/generate' })
2731
+ * }
2732
+ * frame(): ProviderParserInterface<string> {
2733
+ * return new TextFrame()
2734
+ * }
2735
+ * body(request: ProviderRequest): object {
2736
+ * return { messages: request.messages }
2737
+ * }
2738
+ * read(record: string): ProviderIncrement {
2739
+ * return { content: record, thinking: '', tools: [] }
2740
+ * }
2741
+ * finish(_parser: ProviderParserInterface<string>): readonly string[] {
2742
+ * return [] // Raw text retains no records at end of input.
2743
+ * }
2744
+ * }
2745
+ * ```
2746
+ */
2747
+ var AgentProvider = class {
2748
+ #id;
2749
+ #url;
2750
+ #path;
2751
+ #timeout;
2752
+ #transport;
2753
+ #headers;
2754
+ #format;
2755
+ #split;
2756
+ #strict;
2757
+ constructor(input) {
2758
+ this.#id = crypto.randomUUID();
2759
+ this.#url = input.url;
2760
+ this.#path = input.path ?? "";
2761
+ this.#timeout = input.timeout ?? 12e4;
2762
+ this.#transport = input.fetch ?? globalThis.fetch.bind(globalThis);
2763
+ this.#headers = input.headers;
2764
+ this.#format = input.format;
2765
+ this.#split = input.split ?? true;
2766
+ this.#strict = input.strict ?? false;
2767
+ }
2768
+ /** Exposes the instance's minted UUID. */
2769
+ get id() {
2770
+ return this.#id;
2771
+ }
2772
+ /** Exposes the context framing exactly as supplied. */
2773
+ get format() {
2774
+ return this.#format;
2775
+ }
2776
+ /**
2777
+ * Generates a complete turn by draining the shared stream engine.
2778
+ *
2779
+ * @param messages - The conversation turns
2780
+ * @param signal - The caller's cancellation bound
2781
+ * @param tools - The advertised tool definitions
2782
+ * @param options - The per-call generation configuration
2783
+ * @returns The terminal stream result
2784
+ */
2785
+ async generate(messages, signal, tools, options) {
2786
+ const stream = this.stream(messages, signal, tools, options);
2787
+ let step = await stream.next();
2788
+ while (!step.done) step = await stream.next();
2789
+ return step.value;
2790
+ }
2791
+ /**
2792
+ * Streams decoded deltas and returns the authoritative or assembled turn result.
2793
+ *
2794
+ * @param messages - The conversation turns
2795
+ * @param signal - The caller's cancellation bound
2796
+ * @param tools - The advertised tool definitions
2797
+ * @param options - The per-call generation configuration
2798
+ * @returns Content and native thinking deltas followed by the settled result
2799
+ * @throws ProviderAbortError Thrown when the combined cancellation bound fires
2800
+ * @throws ProviderError Thrown for an HTTP or protocol failure
2801
+ */
2802
+ async *stream(messages, signal, tools, options) {
2803
+ if (signal.aborted) throw new ProviderAbortError({ content: "" });
2804
+ const timeout = new _orkestrel_timeout.Timeout({ ms: this.#timeout });
2805
+ timeout.start();
2806
+ const combined = AbortSignal.any([timeout.signal, signal]);
2807
+ let parser;
2808
+ let splitter;
2809
+ let state = {
2810
+ content: "",
2811
+ thinking: "",
2812
+ tools: []
2813
+ };
2814
+ try {
2815
+ parser = this.frame();
2816
+ splitter = this.#split ? createThinkSplitter() : void 0;
2817
+ const response = await this.#request({
2818
+ messages,
2819
+ ...tools === void 0 ? {} : { tools },
2820
+ ...options === void 0 ? {} : { options }
2821
+ }, combined);
2822
+ if (response.body === null) throw new ProviderError("PROTOCOL", "provider error: no response body");
2823
+ for await (const chunk of readChunks(response.body, combined)) {
2824
+ combined.throwIfAborted();
2825
+ for (const record of parser.parse(chunk)) {
2826
+ yield* this.#fold(record, state, splitter, combined, (increment) => {
2827
+ state = increment;
2828
+ });
2829
+ combined.throwIfAborted();
2830
+ if (state.result !== void 0) return state.result;
2831
+ }
2832
+ }
2833
+ combined.throwIfAborted();
2834
+ for (const record of this.finish(parser)) {
2835
+ yield* this.#fold(record, state, splitter, combined, (increment) => {
2836
+ state = increment;
2837
+ });
2838
+ combined.throwIfAborted();
2839
+ if (state.result !== void 0) return state.result;
2840
+ }
2841
+ const tail = splitter?.flush() ?? "";
2842
+ if (tail.length > 0) yield {
2843
+ channel: "content",
2844
+ text: tail
2845
+ };
2846
+ combined.throwIfAborted();
2847
+ if (this.#strict) throw new ProviderError("PROTOCOL", "provider error: missing settled result");
2848
+ return buildProviderResult(splitter?.content ?? state.content, joinThinking(splitter?.thinking, state.thinking), state.tools, state.usage);
2849
+ } catch (error) {
2850
+ if (combined.aborted) {
2851
+ splitter?.flush();
2852
+ throw new ProviderAbortError(buildProviderResult(splitter?.content ?? state.content, joinThinking(splitter?.thinking, state.thinking), state.tools, state.usage), error === combined.reason ? void 0 : { cause: error });
2853
+ }
2854
+ throw error;
2855
+ } finally {
2856
+ try {
2857
+ parser?.clear();
2858
+ } finally {
2859
+ timeout.clear();
2860
+ }
2861
+ }
2862
+ }
2863
+ *#fold(record, previous, splitter, signal, commit) {
2864
+ const increment = this.read(record);
2865
+ if (increment.result !== void 0) {
2866
+ commit(increment);
2867
+ return;
2868
+ }
2869
+ const content = splitter?.split(increment.content) ?? increment.content;
2870
+ const usage = increment.usage ?? previous.usage;
2871
+ commit({
2872
+ content: previous.content + increment.content,
2873
+ thinking: previous.thinking + increment.thinking,
2874
+ tools: [...previous.tools, ...increment.tools],
2875
+ ...usage === void 0 ? {} : { usage }
2876
+ });
2877
+ if (content.length > 0) yield {
2878
+ channel: "content",
2879
+ text: content
2880
+ };
2881
+ signal.throwIfAborted();
2882
+ if (increment.thinking.length > 0) yield {
2883
+ channel: "thinking",
2884
+ text: increment.thinking
2885
+ };
2886
+ }
2887
+ async #request(request, signal) {
2888
+ const headers = await this.#requestHeaders(signal);
2889
+ signal.throwIfAborted();
2890
+ const response = await this.#transport(this.#url + this.#path, {
2891
+ method: "POST",
2892
+ headers,
2893
+ body: JSON.stringify(this.body(request)),
2894
+ signal
2895
+ });
2896
+ if (!response.ok) {
2897
+ let detail;
2898
+ try {
2899
+ detail = response.body === null ? "" : (await readText(response.body, MAX_ERROR_BODY_LENGTH, signal)).text;
2900
+ } catch (cause) {
2901
+ throw new ProviderError("HTTP", `provider error: ${response.status} - (error body unavailable)`, {
2902
+ status: response.status,
2903
+ cause
2904
+ });
2905
+ }
2906
+ signal.throwIfAborted();
2907
+ throw new ProviderError("HTTP", `provider error: ${response.status}${detail === "" ? "" : ` - ${detail}`}`, { status: response.status });
2908
+ }
2909
+ return response;
2910
+ }
2911
+ async #requestHeaders(signal) {
2912
+ const headers = new Headers({ "Content-Type": "application/json" });
2913
+ if (this.#headers === void 0) return headers;
2914
+ const cleanup = new AbortController();
2915
+ const aborted = Promise.withResolvers();
2916
+ signal.addEventListener("abort", () => aborted.reject(signal.reason), {
2917
+ once: true,
2918
+ signal: cleanup.signal
2919
+ });
2920
+ try {
2921
+ signal.throwIfAborted();
2922
+ const hook = this.#headers;
2923
+ const entries = await Promise.race([Promise.resolve().then(() => hook(signal)), aborted.promise]);
2924
+ for (const [key, value] of Object.entries(entries)) headers.set(key, value);
2925
+ return headers;
2926
+ } finally {
2927
+ cleanup.abort();
2928
+ }
2929
+ }
2930
+ };
2931
+ //#endregion
2932
+ //#region src/core/providers/RelayProvider.ts
2933
+ /**
2934
+ * Carries provider calls over an authenticated NDJSON relay endpoint.
2935
+ *
2936
+ * @remarks
2937
+ * The `ToolCall.caller` member never crosses the hop. The wire body is an owned snapshot of
2938
+ * the projection read through property descriptors, so a serializer reachable only through a
2939
+ * `get` trap or a prototype is never consulted; an own function-valued property such as a
2940
+ * `toJSON` method is a value outside JSON and is refused before fetching, with the clone's
2941
+ * failure as the refusal's `cause`. A remote abort reconstructs a `ProviderAbortError` instance
2942
+ * without aborting the
2943
+ * local signal; the `Agent` runtime treats that instance as an error unless its own bound
2944
+ * signal is aborted.
2945
+ * Content is preserved verbatim, including literal thinking tags.
2946
+ * A refusal reaches the browser as a `ProviderError` instance with the `HTTP` code and status.
2947
+ * This is the browser end alone; {@link createRelay} mounts the server end, and
2948
+ * {@link createRelayProvider}'s example is the browser half of that pair.
2949
+ *
2950
+ * @example
2951
+ * ```ts
2952
+ * import type { ProviderResult } from '@orkestrel/agent'
2953
+ * import { RelayProvider } from '@orkestrel/agent'
2954
+ * // The browser application supplies this parser dependency.
2955
+ * import { createNDJSONParser } from '@orkestrel/ndjson'
2956
+ *
2957
+ * export function ask(bearer: string, signal: AbortSignal): Promise<ProviderResult> {
2958
+ * const browser = new RelayProvider({
2959
+ * url: 'https://relay.example/relay',
2960
+ * parser: createNDJSONParser,
2961
+ * headers: () => ({ authorization: `Bearer ${bearer}` }),
2962
+ * })
2963
+ * return browser.generate([{ id: 'ask', role: 'user', content: 'ping' }], signal)
2964
+ * }
2965
+ * ```
2966
+ */
2967
+ var RelayProvider = class extends AgentProvider {
2968
+ #parser;
2969
+ constructor(options) {
2970
+ const { url, timeout, fetch, headers, format } = options;
2971
+ super({
2972
+ url,
2973
+ split: false,
2974
+ strict: true,
2975
+ ...timeout === void 0 ? {} : { timeout },
2976
+ ...fetch === void 0 ? {} : { fetch },
2977
+ ...headers === void 0 ? {} : { headers },
2978
+ ...format === void 0 ? {} : { format }
2979
+ });
2980
+ this.#parser = options.parser;
2981
+ }
2982
+ /** Identifies the relay backend. */
2983
+ name = "relay";
2984
+ /** Creates fresh framing state for each response. */
2985
+ frame() {
2986
+ return this.#parser();
2987
+ }
2988
+ /**
2989
+ * Projects declared request fields and refuses values the JSON wire cannot carry.
2990
+ *
2991
+ * @remarks
2992
+ * The snapshot is taken from property descriptors, so a custom serializer a projected
2993
+ * value carries is ignored rather than consulted and cannot reach the wire.
2994
+ *
2995
+ * @param request - The domain conversation and call configuration
2996
+ * @returns The validated wire request with caller context omitted
2997
+ * @throws {ProviderError} Thrown when the snapshot cannot be taken — a hostile read or a
2998
+ * value outside JSON — carrying that failure as its `cause`, and when the snapshot the
2999
+ * projection produced is not a valid wire request
3000
+ */
3001
+ body(request) {
3002
+ let snapshot;
3003
+ try {
3004
+ const projected = {
3005
+ messages: request.messages.map((message) => ({
3006
+ id: message.id,
3007
+ role: message.role,
3008
+ content: message.content,
3009
+ ...message.calls === void 0 ? {} : { calls: message.calls.map((call) => ({
3010
+ id: call.id,
3011
+ name: call.name,
3012
+ arguments: call.arguments
3013
+ })) },
3014
+ ...message.images === void 0 ? {} : { images: message.images }
3015
+ })),
3016
+ ...request.tools === void 0 ? {} : { tools: request.tools.map((tool) => ({
3017
+ name: tool.name,
3018
+ ...tool.description === void 0 ? {} : { description: tool.description },
3019
+ ...tool.parameters === void 0 ? {} : { parameters: tool.parameters }
3020
+ })) },
3021
+ ...request.options === void 0 ? {} : { options: {
3022
+ ...request.options.think === void 0 ? {} : { think: request.options.think },
3023
+ ...request.options.schema === void 0 ? {} : { schema: request.options.schema }
3024
+ } }
3025
+ };
3026
+ snapshot = (0, _orkestrel_contract.cloneJSONValue)(projected);
3027
+ } catch (cause) {
3028
+ throw new ProviderError("PROTOCOL", "relay request is not JSON", { cause });
3029
+ }
3030
+ if (!providerRequestContract.is(snapshot)) throw new ProviderError("PROTOCOL", "relay request is not JSON");
3031
+ return snapshot;
3032
+ }
3033
+ /**
3034
+ * Validates a relay frame and translates its channel into the shared stream engine.
3035
+ *
3036
+ * @param record - The framed wire record
3037
+ * @returns A delta contribution or authoritative result
3038
+ * @throws {ProviderAbortError} Thrown for a remote abort carrying its partial
3039
+ * @throws {ProviderError} Thrown for a malformed frame or remote provider failure
3040
+ */
3041
+ read(record) {
3042
+ if (!relayFrameContract.is(record)) throw new ProviderError("PROTOCOL", "invalid relay frame");
3043
+ switch (record.channel) {
3044
+ case "content": return {
3045
+ content: record.text,
3046
+ thinking: "",
3047
+ tools: []
3048
+ };
3049
+ case "thinking": return {
3050
+ content: "",
3051
+ thinking: record.text,
3052
+ tools: []
3053
+ };
3054
+ case "result": return {
3055
+ content: "",
3056
+ thinking: "",
3057
+ tools: [],
3058
+ result: record.result
3059
+ };
3060
+ case "abort": throw new ProviderAbortError(record.partial);
3061
+ case "error": throw new ProviderError("PROVIDER", record.message);
3062
+ }
3063
+ }
3064
+ /**
3065
+ * Recovers an unterminated final frame by completing its NDJSON line.
3066
+ *
3067
+ * @param parser - The call's retained framing state
3068
+ * @returns Records completed by the final newline
3069
+ */
3070
+ finish(parser) {
3071
+ return parser.parse("\n");
3072
+ }
3073
+ };
3074
+ //#endregion
3075
+ //#region src/core/RelayStream.ts
3076
+ /**
3077
+ * Streams a provider call as validated NDJSON frames under response backpressure.
3078
+ *
3079
+ * @remarks
3080
+ * Cancellation aborts upstream before returning its iterator. Request listeners are
3081
+ * released on settlement. Unexpected provider failures carry a fixed public message.
3082
+ * An inbound abort leaves the response body neither closed nor errored — a server runtime
3083
+ * cancels that body when the client disconnects — so a consumer that aborts the inbound
3084
+ * signal itself must cancel the body rather than keep reading it.
3085
+ *
3086
+ * @example
3087
+ * ```ts
3088
+ * import type { ProviderInterface } from '@orkestrel/agent'
3089
+ * import { RelayStream } from '@orkestrel/agent'
3090
+ *
3091
+ * export function respond(provider: ProviderInterface, signal: AbortSignal): Response {
3092
+ * return new RelayStream({ provider, request: { messages: [] }, signal }).response
3093
+ * }
3094
+ * ```
3095
+ */
3096
+ var RelayStream = class {
3097
+ #upstream = new AbortController();
3098
+ #encoder = new TextEncoder();
3099
+ #iterator;
3100
+ #signal;
3101
+ #listener;
3102
+ #response;
3103
+ #settled = false;
3104
+ constructor(options) {
3105
+ this.#signal = options.signal;
3106
+ this.#listener = this.#cancel.bind(this);
3107
+ if (this.#signal.aborted) this.#abortProvider();
3108
+ this.#iterator = options.provider.stream(options.request.messages, this.#upstream.signal, options.request.tools, options.request.options);
3109
+ this.#signal.addEventListener("abort", this.#listener, { once: true });
3110
+ this.#response = new Response(new ReadableStream({
3111
+ pull: this.#pull.bind(this),
3112
+ cancel: this.#cancel.bind(this)
3113
+ }), { headers: {
3114
+ "content-type": RELAY_CONTENT_TYPE,
3115
+ "cache-control": "no-store"
3116
+ } });
3117
+ }
3118
+ /** Exposes the pull-driven response for the provider call. */
3119
+ get response() {
3120
+ return this.#response;
3121
+ }
3122
+ async #pull(controller) {
3123
+ if (this.#settled) return;
3124
+ try {
3125
+ const step = await this.#iterator.next();
3126
+ if (this.#settled) return;
3127
+ this.#write(controller, step.done ? {
3128
+ channel: "result",
3129
+ result: step.value
3130
+ } : step.value);
3131
+ if (step.done) this.#finish(controller);
3132
+ } catch (error) {
3133
+ if (this.#settled) return;
3134
+ const frame = error instanceof ProviderAbortError ? {
3135
+ channel: "abort",
3136
+ partial: error.partial
3137
+ } : {
3138
+ channel: "error",
3139
+ message: RELAY_PROVIDER_MESSAGE
3140
+ };
3141
+ this.#write(controller, relayFrameContract.is(frame) ? frame : {
3142
+ channel: "error",
3143
+ message: RELAY_PROVIDER_MESSAGE
3144
+ });
3145
+ this.#finish(controller);
3146
+ this.#abortProvider();
3147
+ await this.#return();
3148
+ }
3149
+ }
3150
+ async #cancel() {
3151
+ if (this.#settled) return;
3152
+ this.#settled = true;
3153
+ this.#abortProvider();
3154
+ try {
3155
+ await this.#return();
3156
+ } finally {
3157
+ this.#release();
3158
+ }
3159
+ }
3160
+ async #return() {
3161
+ try {
3162
+ await this.#iterator.return({ content: "" });
3163
+ } catch {}
3164
+ }
3165
+ #write(controller, frame) {
3166
+ if (!relayFrameContract.is(frame)) throw new ProviderError("PROTOCOL", "invalid relay frame");
3167
+ controller.enqueue(this.#encoder.encode(`${JSON.stringify(frame)}\n`));
3168
+ }
3169
+ #finish(controller) {
3170
+ this.#settled = true;
3171
+ this.#release();
3172
+ controller.close();
3173
+ }
3174
+ #abortProvider() {
3175
+ this.#upstream.abort(this.#signal.reason);
3176
+ }
3177
+ #release() {
3178
+ this.#signal.removeEventListener("abort", this.#listener);
3179
+ }
3180
+ };
3181
+ //#endregion
2389
3182
  //#region src/core/scopes/Scope.ts
2390
3183
  /**
2391
3184
  * Represents a named, immutable filter over a richer context's items — an optional allow-list per
@@ -2640,6 +3433,105 @@ var ThinkSplitter = class {
2640
3433
  //#endregion
2641
3434
  //#region src/core/factories.ts
2642
3435
  /**
3436
+ * Creates an authorized relay handler that validates a bounded JSON request before streaming.
3437
+ *
3438
+ * @remarks
3439
+ * Answers with the `401` status when authorization refuses or throws, the `400`
3440
+ * status when the body is missing, unreadable, or invalid, the `413` status when
3441
+ * the body fills its byte budget or the inbound read is aborted, and the `502`
3442
+ * status when the upstream provider call cannot be constructed. Refusals carry no body.
3443
+ *
3444
+ * @param options - The upstream provider, authorization decision, and optional byte budget
3445
+ * @returns A fetch-standard handler suitable for a router
3446
+ * @example Mounting the relay on your server
3447
+ * ```ts
3448
+ * import type { ProviderInterface } from '@orkestrel/agent'
3449
+ * import { createRelay } from '@orkestrel/agent'
3450
+ * import { createDispatcher } from '@orkestrel/router'
3451
+ * import { createServer } from '@orkestrel/server'
3452
+ *
3453
+ * declare const upstream: ProviderInterface // the server-side provider holding the credential
3454
+ * declare const bearer: string
3455
+ *
3456
+ * const handler = createRelay({
3457
+ * provider: upstream,
3458
+ * authorize: (request) => request.headers.get('authorization') === `Bearer ${bearer}`,
3459
+ * })
3460
+ * const dispatcher = createDispatcher({
3461
+ * routes: [{ method: 'POST', path: '/relay', handler }],
3462
+ * })
3463
+ *
3464
+ * export function serve(request: Request): Promise<Response> {
3465
+ * return dispatcher.handle(request, undefined)
3466
+ * }
3467
+ *
3468
+ * const server = createServer({ dispatcher, state: () => undefined })
3469
+ * await server.start()
3470
+ * process.on('SIGTERM', () => server.stop()) // signal cancellation, drain, then close the listener
3471
+ * ```
3472
+ */
3473
+ function createRelay(options) {
3474
+ const { provider, authorize, limit } = options;
3475
+ return async (request) => {
3476
+ try {
3477
+ if (await authorize(request) !== true) return new Response(void 0, { status: 401 });
3478
+ } catch {
3479
+ return new Response(void 0, { status: 401 });
3480
+ }
3481
+ if (request.body === null) return new Response(void 0, { status: 400 });
3482
+ let read;
3483
+ try {
3484
+ read = await readText(request.body, limit ?? 1048576, request.signal);
3485
+ } catch {
3486
+ return new Response(void 0, { status: request.signal.aborted ? 413 : 400 });
3487
+ }
3488
+ if (!read.complete) return new Response(void 0, { status: 413 });
3489
+ const parsed = (0, _orkestrel_contract.parseJSONAs)(read.text, providerRequestContract.is);
3490
+ if (parsed === void 0) return new Response(void 0, { status: 400 });
3491
+ try {
3492
+ return new RelayStream({
3493
+ provider,
3494
+ request: parsed,
3495
+ signal: request.signal
3496
+ }).response;
3497
+ } catch {
3498
+ return new Response(void 0, { status: 502 });
3499
+ }
3500
+ };
3501
+ }
3502
+ /**
3503
+ * Creates a provider that carries calls through a relay endpoint.
3504
+ *
3505
+ * @remarks
3506
+ * This is the browser end alone. {@link createRelay} mounts the server end, and its example
3507
+ * is the server half this one pairs with.
3508
+ *
3509
+ * @param options - The endpoint, parser factory, and HTTP call configuration
3510
+ * @returns The concrete relay provider
3511
+ * @example Reaching the relay from the browser
3512
+ * ```ts
3513
+ * import type { ProviderInterface } from '@orkestrel/agent'
3514
+ * import { createRelayProvider } from '@orkestrel/agent'
3515
+ * import { createAbort } from '@orkestrel/abort'
3516
+ * // The browser application supplies this parser dependency.
3517
+ * import { createNDJSONParser } from '@orkestrel/ndjson'
3518
+ *
3519
+ * declare const bearer: string
3520
+ * const abort = createAbort()
3521
+ * const messages = [{ id: '1', role: 'user', content: 'Say hello.' }] as const
3522
+ *
3523
+ * const browser: ProviderInterface = createRelayProvider({
3524
+ * url: 'https://app.example/relay',
3525
+ * parser: createNDJSONParser,
3526
+ * headers: () => ({ authorization: `Bearer ${bearer}` }),
3527
+ * })
3528
+ * const result = await browser.generate(messages, abort.signal) // a ProviderResult like a local provider's
3529
+ * ```
3530
+ */
3531
+ function createRelayProvider(options) {
3532
+ return new RelayProvider(options);
3533
+ }
3534
+ /**
2643
3535
  * Creates a conversation — a {@link ConversationInterface} grouping messages above a flat
2644
3536
  * message store it owns directly, with compaction into summarized sections, a regenerated
2645
3537
  * rollup `summary`, on-demand `rehydrate`, and substring `search`, driven by a
@@ -2660,8 +3552,8 @@ var ThinkSplitter = class {
2660
3552
  *
2661
3553
  * @example Conversations & compaction
2662
3554
  * ```ts
2663
- * import { createConversation } from '@orkestrel/agent'
2664
3555
  * import type { ProviderInterface } from '@orkestrel/agent'
3556
+ * import { createConversation } from '@orkestrel/agent'
2665
3557
  *
2666
3558
  * declare const provider: ProviderInterface // any concrete implementation supplied by the host app
2667
3559
  * // The summarizer seam — built from the provider by the runtime; core stays provider-agnostic.
@@ -3222,6 +4114,7 @@ exports.Agent = Agent;
3222
4114
  exports.AgentContext = AgentContext;
3223
4115
  exports.AgentError = AgentError;
3224
4116
  exports.AgentJobError = AgentJobError;
4117
+ exports.AgentProvider = AgentProvider;
3225
4118
  exports.AgentRegistry = AgentRegistry;
3226
4119
  exports.Authority = Authority;
3227
4120
  exports.CONVERSATION_RECAP_PREFIX = CONVERSATION_RECAP_PREFIX;
@@ -3232,23 +4125,36 @@ exports.ConversationManager = ConversationManager;
3232
4125
  exports.DEFAULT_AGENT_LIMIT = DEFAULT_AGENT_LIMIT;
3233
4126
  exports.DEFAULT_AUTHORITY_ZONE = DEFAULT_AUTHORITY_ZONE;
3234
4127
  exports.DEFAULT_CONVERSATION_KEEP = DEFAULT_CONVERSATION_KEEP;
4128
+ exports.DEFAULT_PROVIDER_TIMEOUT = DEFAULT_PROVIDER_TIMEOUT;
4129
+ exports.DEFAULT_RELAY_LIMIT = DEFAULT_RELAY_LIMIT;
3235
4130
  exports.DatabaseConversationStore = DatabaseConversationStore;
3236
4131
  exports.IMAGE_TOKEN_ESTIMATE = IMAGE_TOKEN_ESTIMATE;
4132
+ exports.INVALID_RELAY_STATUS = INVALID_RELAY_STATUS;
3237
4133
  exports.Instruction = Instruction;
3238
4134
  exports.InstructionManager = InstructionManager;
4135
+ exports.MAX_ERROR_BODY_LENGTH = MAX_ERROR_BODY_LENGTH;
3239
4136
  exports.MESSAGE_TOKEN_OVERHEAD = MESSAGE_TOKEN_OVERHEAD;
3240
4137
  exports.MemoryConversationStore = MemoryConversationStore;
4138
+ exports.OVERSIZED_RELAY_STATUS = OVERSIZED_RELAY_STATUS;
3241
4139
  exports.ProviderAbortError = ProviderAbortError;
4140
+ exports.ProviderError = ProviderError;
4141
+ exports.RELAY_CONTENT_TYPE = RELAY_CONTENT_TYPE;
4142
+ exports.RELAY_PROVIDER_MESSAGE = RELAY_PROVIDER_MESSAGE;
4143
+ exports.RelayProvider = RelayProvider;
4144
+ exports.RelayStream = RelayStream;
3242
4145
  exports.Scope = Scope;
3243
4146
  exports.ScopeManager = ScopeManager;
3244
4147
  exports.THINK_CLOSE = THINK_CLOSE;
3245
4148
  exports.THINK_OPEN = THINK_OPEN;
3246
4149
  exports.ThinkSplitter = ThinkSplitter;
4150
+ exports.UNAUTHORIZED_RELAY_STATUS = UNAUTHORIZED_RELAY_STATUS;
4151
+ exports.UPSTREAM_RELAY_STATUS = UPSTREAM_RELAY_STATUS;
3247
4152
  exports.WORKSPACE_SECTION_HEADER = WORKSPACE_SECTION_HEADER;
3248
4153
  exports.agentResultToJSON = agentResultToJSON;
3249
4154
  exports.assembleResult = assembleResult;
3250
4155
  exports.attachImages = attachImages;
3251
4156
  exports.attachUserImages = attachUserImages;
4157
+ exports.buildProviderResult = buildProviderResult;
3252
4158
  exports.buildRecapMessage = buildRecapMessage;
3253
4159
  exports.buildSummaryMessage = buildSummaryMessage;
3254
4160
  exports.collectImageData = collectImageData;
@@ -3265,6 +4171,8 @@ exports.createDatabaseConversationStore = createDatabaseConversationStore;
3265
4171
  exports.createInstruction = createInstruction;
3266
4172
  exports.createInstructionManager = createInstructionManager;
3267
4173
  exports.createMemoryConversationStore = createMemoryConversationStore;
4174
+ exports.createRelay = createRelay;
4175
+ exports.createRelayProvider = createRelayProvider;
3268
4176
  exports.createScope = createScope;
3269
4177
  exports.createScopeManager = createScopeManager;
3270
4178
  exports.createThinkSplitter = createThinkSplitter;
@@ -3281,8 +4189,19 @@ exports.isConversationError = isConversationError;
3281
4189
  exports.isConversationSnapshot = isConversationSnapshot;
3282
4190
  exports.isMessage = isMessage;
3283
4191
  exports.isProviderAbortError = isProviderAbortError;
4192
+ exports.isProviderError = isProviderError;
3284
4193
  exports.isSection = isSection;
3285
4194
  exports.joinThinking = joinThinking;
4195
+ exports.messageContract = messageContract;
4196
+ exports.messageShape = messageShape;
4197
+ exports.providerRequestContract = providerRequestContract;
4198
+ exports.providerRequestShape = providerRequestShape;
4199
+ exports.providerResultContract = providerResultContract;
4200
+ exports.providerResultShape = providerResultShape;
4201
+ exports.readChunks = readChunks;
4202
+ exports.readText = readText;
4203
+ exports.relayFrameContract = relayFrameContract;
4204
+ exports.relayFrameShape = relayFrameShape;
3286
4205
  exports.renderFencedFile = renderFencedFile;
3287
4206
  exports.renderSection = renderSection;
3288
4207
  exports.resolveClose = resolveClose;
@@ -3292,5 +4211,6 @@ exports.sanitizeToken = sanitizeToken;
3292
4211
  exports.sanitizeUsage = sanitizeUsage;
3293
4212
  exports.settleAgentJob = settleAgentJob;
3294
4213
  exports.sumUsage = sumUsage;
4214
+ exports.toolCallShape = toolCallShape;
3295
4215
 
3296
4216
  //# sourceMappingURL=index.cjs.map