@orkestrel/agent 0.0.21 → 0.0.23

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