@theokit/agents 4.26.2 → 4.27.1

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.
@@ -84,9 +84,21 @@ interface InProcessTransportOptions {
84
84
  * throws SYNCHRONOUSLY surfaces the error when the stream is READ (via `controller.error`), not from the
85
85
  * `sendMessages` promise — whereas `HttpTransport` throws from `sendMessages` on a non-2xx response.
86
86
  */
87
+ /**
88
+ * Uma aprovação estacionada foi descartada porque o turno terminou sem decisão.
89
+ *
90
+ * M92 — tipado de propósito. Antes a promessa simplesmente **nunca** resolvia, e a chamada de tool do
91
+ * SDK pendurava; `resolve(false)` seria pior ainda, porque é indistinguível de "o usuário negou".
92
+ */
93
+ declare class ApprovalAbortedError extends Error {
94
+ readonly approvalId: string;
95
+ constructor(approvalId: string, motivo: string);
96
+ }
87
97
  declare class InProcessTransport implements AgentTransport {
88
98
  #private;
89
99
  constructor(options: InProcessTransportOptions);
100
+ /** Quantas aprovações estão estacionadas. Existe para o teste poder provar a eviction. */
101
+ get pendentes(): number;
90
102
  sendMessages(options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0]): Promise<ReadableStream<UIMessageChunk>>;
91
103
  reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null>;
92
104
  approve(approvalId: string, decision: ApprovalDecision): Promise<void>;
@@ -168,9 +180,21 @@ interface AgentClientState {
168
180
  * `useSyncExternalStore`; a standalone (no-React) client (M44) can subscribe directly. Being
169
181
  * framework-agnostic, it is unit-tested without a DOM.
170
182
  */
183
+ /**
184
+ * M92 — opções do cliente. Aditivo: sem elas, o comportamento é o de sempre.
185
+ */
186
+ interface AgentClientOptions {
187
+ /**
188
+ * Janela de coalescing em ms. `0` ou ausente = emite por delta de token (comportamento pré-M92).
189
+ *
190
+ * Opt-in de propósito: este é um pacote publicado, e mudar a frequência de emit por padrão mudaria o
191
+ * comportamento observável de quem conta emits ou depende da latência do primeiro token.
192
+ */
193
+ readonly emitIntervalMs?: number;
194
+ }
171
195
  declare class AgentClient<TInput = unknown> {
172
196
  #private;
173
- constructor(transport: AgentTransport, contextResolver?: () => RequestContext | undefined);
197
+ constructor(transport: AgentTransport, contextResolver?: () => RequestContext | undefined, options?: AgentClientOptions);
174
198
  /** Subscribe to state changes; returns an unsubscribe fn. */
175
199
  subscribe: (listener: () => void) => (() => void);
176
200
  /** The current immutable snapshot (stable reference until the next emit). */
@@ -224,4 +248,4 @@ declare function agentHandle<TInput = unknown, TToolNames extends string = strin
224
248
  /** Narrow an unknown binding to an {@link AgentHandle} (has a string `path`, is not a transport). */
225
249
  declare function isAgentHandle(value: unknown): value is AgentHandle;
226
250
 
227
- export { type ApprovalDecision as A, type ChannelPushSource as C, type InProcessApprovalRequestLike as I, type RequestContext as R, type UseAgentStatus as U, type AgentHandle as a, type AgentTransport as b, AgentClient as c, type AgentClientState as d, ChannelTransport as e, type ChannelTransportOptions as f, type ChannelTurnHandlers as g, type InProcessAwaitApproval as h, type InProcessRunInput as i, type InProcessRunner as j, InProcessTransport as k, type InProcessTransportOptions as l, agentHandle as m, isAgentHandle as n };
251
+ export { type ApprovalDecision as A, type ChannelPushSource as C, type InProcessApprovalRequestLike as I, type RequestContext as R, type UseAgentStatus as U, type AgentHandle as a, type AgentTransport as b, AgentClient as c, type AgentClientOptions as d, type AgentClientState as e, ApprovalAbortedError as f, ChannelTransport as g, type ChannelTransportOptions as h, type ChannelTurnHandlers as i, type InProcessAwaitApproval as j, type InProcessRunInput as k, type InProcessRunner as l, InProcessTransport as m, type InProcessTransportOptions as n, agentHandle as o, isAgentHandle as p };
@@ -165,29 +165,100 @@ function generatorToStream(gen) {
165
165
  });
166
166
  }
167
167
  __name(generatorToStream, "generatorToStream");
168
+ var ApprovalAbortedError = class extends Error {
169
+ static {
170
+ __name(this, "ApprovalAbortedError");
171
+ }
172
+ approvalId;
173
+ constructor(approvalId, motivo) {
174
+ super(`Aprova\xE7\xE3o '${approvalId}' descartada: ${motivo}.`), this.approvalId = approvalId;
175
+ this.name = "ApprovalAbortedError";
176
+ }
177
+ };
168
178
  var InProcessTransport = class {
169
179
  static {
170
180
  __name(this, "InProcessTransport");
171
181
  }
172
182
  #run;
173
- /** Pending inline approvals: approvalId → resolver of the parked `awaitApproval` promise. */
183
+ /**
184
+ * Aprovações inline estacionadas: `approvalId` → como resolver **ou rejeitar** a promessa parada.
185
+ *
186
+ * M92 — o `reject` entrou junto com a eviction. Antes havia só o `resolve`, e nada apagava a entrada
187
+ * quando o turno abortava: a promessa ficava pendente **para sempre**, e a chamada de tool do SDK
188
+ * pendurava com ela. Uma promessa que nunca resolve **nem** rejeita é a forma mais silenciosa de
189
+ * engolir um erro — nem stack trace existe (`error-handling.md § 2`).
190
+ */
174
191
  #pending = /* @__PURE__ */ new Map();
192
+ /** O turno corrente. Um `send()` novo incrementa e varre o anterior. */
193
+ #turno = 0;
175
194
  constructor(options) {
176
195
  this.#run = options.run;
177
196
  }
178
- #awaitApproval = /* @__PURE__ */ __name((req) => new Promise((resolve, reject) => {
179
- if (this.#pending.has(req.approvalId)) {
180
- reject(new Error(`Duplicate pending approval id '${req.approvalId}' \u2014 ids must be unique.`));
181
- return;
197
+ /**
198
+ * Cria o `awaitApproval` DESTE turno, com o número e o sinal fechados no closure.
199
+ *
200
+ * Campo compartilhado não serve, e a revisão do M92 mediu por quê: um runner do turno 1 que
201
+ * estaciona **depois** do `send` do turno 2 lê o campo já sobrescrito e nasce etiquetado turno 2 —
202
+ * o abort do turno 1 não o varre, e a promessa pendura. A primeira correção do M92 trocou "ler no
203
+ * momento da aprovação" por "ler no `send`", e continuou errada pela mesma razão: um campo só.
204
+ *
205
+ * O closure é o único lugar onde o turno de um runner pode viver sem ser sobrescrito por outro.
206
+ */
207
+ #criarAwaitApproval(turno, sinal) {
208
+ return (req) => new Promise((resolve, reject) => {
209
+ if (sinal?.aborted === true) {
210
+ reject(new ApprovalAbortedError(req.approvalId, "o turno j\xE1 estava abortado"));
211
+ return;
212
+ }
213
+ if (this.#pending.has(req.approvalId)) {
214
+ reject(new Error(`Duplicate pending approval id '${req.approvalId}' \u2014 ids must be unique.`));
215
+ return;
216
+ }
217
+ this.#pending.set(req.approvalId, {
218
+ resolve,
219
+ reject,
220
+ turno
221
+ });
222
+ });
223
+ }
224
+ /**
225
+ * Varre as aprovações de um turno, rejeitando cada uma com erro TIPADO.
226
+ *
227
+ * Rejeitar e não `resolve(false)`: um `false` é indistinguível de *"o usuário negou"*, e a diferença
228
+ * importa — negar é decisão, abortar é interrupção. O SDK precisa das duas para desenrolar a chamada
229
+ * de tool corretamente.
230
+ */
231
+ #varrerTurno(turno, motivo) {
232
+ for (const [id, entrada] of [
233
+ ...this.#pending
234
+ ]) {
235
+ if (entrada.turno !== turno) continue;
236
+ this.#pending.delete(id);
237
+ entrada.reject(new ApprovalAbortedError(id, motivo));
182
238
  }
183
- this.#pending.set(req.approvalId, resolve);
184
- }), "#awaitApproval");
239
+ }
240
+ /** Quantas aprovações estão estacionadas. Existe para o teste poder provar a eviction. */
241
+ get pendentes() {
242
+ return this.#pending.size;
243
+ }
185
244
  sendMessages(options) {
186
245
  const { messages, abortSignal, metadata } = options;
246
+ this.#varrerTurno(this.#turno, "um turno novo come\xE7ou");
247
+ this.#turno += 1;
248
+ const turnoAtual = this.#turno;
249
+ if (abortSignal?.aborted === true) {
250
+ this.#varrerTurno(turnoAtual, "o turno j\xE1 estava abortado");
251
+ } else {
252
+ abortSignal?.addEventListener("abort", () => {
253
+ this.#varrerTurno(turnoAtual, "o turno foi abortado");
254
+ }, {
255
+ once: true
256
+ });
257
+ }
187
258
  const generator = this.#run({
188
259
  message: extractLastUserText(messages),
189
260
  signal: abortSignal ?? void 0,
190
- awaitApproval: this.#awaitApproval,
261
+ awaitApproval: this.#criarAwaitApproval(turnoAtual, abortSignal ?? void 0),
191
262
  // M43 — forward per-request context (the seam's `metadata`) to the runner.
192
263
  context: metadata
193
264
  });
@@ -197,12 +268,12 @@ var InProcessTransport = class {
197
268
  return Promise.resolve(null);
198
269
  }
199
270
  approve(approvalId, decision) {
200
- const resolve = this.#pending.get(approvalId);
201
- if (resolve === void 0) {
271
+ const entrada = this.#pending.get(approvalId);
272
+ if (entrada === void 0) {
202
273
  return Promise.reject(new Error(`No pending approval '${approvalId}' (unknown or already settled).`));
203
274
  }
204
275
  this.#pending.delete(approvalId);
205
- resolve(decision);
276
+ entrada.resolve(decision);
206
277
  return Promise.resolve();
207
278
  }
208
279
  };
@@ -343,9 +414,29 @@ var AgentClient = class {
343
414
  status: "idle",
344
415
  error: void 0
345
416
  };
346
- constructor(transport, contextResolver) {
417
+ /**
418
+ * M92 — o prefixo commitado, materializado UMA vez por escrita em vez de por delta de token.
419
+ *
420
+ * `#committed` só muda em dois lugares (medido): no `done` de `send()` e em `reset()`. Entre deltas
421
+ * ele é constante, então reconstruí-lo a cada `#emit` é trabalho que a estrutura já garante inútil.
422
+ *
423
+ * Invalidação é **na escrita**, não por comparação: comparar custaria o mesmo O(C) que isto evita, e
424
+ * memoizar por comprimento erraria em `reset()` — comprimento igual com conteúdo diferente é
425
+ * possível, e o bug seria invisível.
426
+ *
427
+ * Honestidade sobre o tamanho do ganho: medido, o spread custa **0,0062 ms por delta @400 mensagens**
428
+ * — 3,1 ms num turno de 500 deltas. É real e é micro. A ordem de grandeza deste milestone está no
429
+ * coalescing abaixo, porque o que pende de cada emit (a derivação da timeline) custa **3,274 ms por
430
+ * chamada** no mesmo tamanho de thread (M86).
431
+ */
432
+ #prefixo = [];
433
+ /** Coalescing opt-in: `0` (default) emite por delta, como sempre. */
434
+ #emitIntervalMs;
435
+ #timerDeEmit;
436
+ constructor(transport, contextResolver, options) {
347
437
  this.#transport = transport;
348
438
  this.#contextResolver = contextResolver;
439
+ this.#emitIntervalMs = options?.emitIntervalMs ?? 0;
349
440
  }
350
441
  /** Subscribe to state changes; returns an unsubscribe fn. */
351
442
  subscribe = /* @__PURE__ */ __name((listener) => {
@@ -356,15 +447,19 @@ var AgentClient = class {
356
447
  }, "subscribe");
357
448
  /** The current immutable snapshot (stable reference until the next emit). */
358
449
  getSnapshot = /* @__PURE__ */ __name(() => this.#snapshot, "getSnapshot");
450
+ /**
451
+ * Emite AGORA. Usado diretamente nas transições de status — ver `#agendarEmit`.
452
+ */
359
453
  #emit() {
360
- const thread = this.#currentUser ? [
361
- ...this.#committed,
454
+ if (this.#timerDeEmit !== void 0) {
455
+ clearTimeout(this.#timerDeEmit);
456
+ this.#timerDeEmit = void 0;
457
+ }
458
+ const cauda = this.#currentUser ? [
362
459
  this.#currentUser,
363
460
  ...this.#messages
364
- ] : [
365
- ...this.#committed,
366
- ...this.#messages
367
- ];
461
+ ] : this.#messages;
462
+ const thread = this.#prefixo.concat(cauda);
368
463
  this.#snapshot = {
369
464
  messages: this.#messages,
370
465
  thread,
@@ -373,6 +468,28 @@ var AgentClient = class {
373
468
  };
374
469
  for (const listener of this.#listeners) listener();
375
470
  }
471
+ /**
472
+ * Emite por JANELA quando o coalescing está ligado; imediatamente quando não está.
473
+ *
474
+ * Borda de saída: o timer emite ao **fim** da janela, com o estado mais recente. O que isto compra
475
+ * não é um emit mais barato — é **menos emits**, e o que pende de cada um é a derivação de 3,274 ms
476
+ * medida no M86.
477
+ *
478
+ * As transições de status (`done`/`error`/`abort`) NÃO passam por aqui: elas chamam `#emit` direto,
479
+ * porque um estado final preso num timer de 16 ms é um estado final perdido se o processo sair antes
480
+ * — e `exec` sai logo após o turno.
481
+ */
482
+ #agendarEmit() {
483
+ if (this.#emitIntervalMs <= 0) {
484
+ this.#emit();
485
+ return;
486
+ }
487
+ if (this.#timerDeEmit !== void 0) return;
488
+ this.#timerDeEmit = setTimeout(() => {
489
+ this.#timerDeEmit = void 0;
490
+ this.#emit();
491
+ }, this.#emitIntervalMs);
492
+ }
376
493
  #upsert(message) {
377
494
  const next = [
378
495
  ...this.#messages
@@ -399,7 +516,7 @@ var AgentClient = class {
399
516
  id: this.#currentAssistantId
400
517
  };
401
518
  this.#upsert(stamped);
402
- this.#emit();
519
+ this.#agendarEmit();
403
520
  });
404
521
  if (aborted()) return;
405
522
  this.#status = "done";
@@ -419,6 +536,7 @@ var AgentClient = class {
419
536
  this.#currentUser,
420
537
  ...this.#messages
421
538
  ];
539
+ this.#prefixo = this.#committed;
422
540
  }
423
541
  this.abort();
424
542
  const controller = new AbortController();
@@ -476,6 +594,7 @@ var AgentClient = class {
476
594
  this.abort();
477
595
  this.#messages = [];
478
596
  this.#committed = [];
597
+ this.#prefixo = this.#committed;
479
598
  this.#currentUser = void 0;
480
599
  this.#error = void 0;
481
600
  this.#status = "idle";
@@ -511,10 +630,11 @@ export {
511
630
  consumeChunkStream,
512
631
  HttpTransport,
513
632
  extractLastUserText,
633
+ ApprovalAbortedError,
514
634
  InProcessTransport,
515
635
  ChannelTransport,
516
636
  AgentClient,
517
637
  agentHandle,
518
638
  isAgentHandle
519
639
  };
520
- //# sourceMappingURL=chunk-M2JFE6IM.js.map
640
+ //# sourceMappingURL=chunk-NTDOKNSU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/consume-ui-message-stream.ts","../src/client/http-transport.ts","../src/client/last-user-text.ts","../src/client/in-process-transport.ts","../src/client/channel-transport.ts","../src/client/agent-client.ts","../src/client/agent-handle.ts"],"sourcesContent":["import type { UIMessage, UIMessageChunk } from 'ai'\n\n/**\n * M2 (theokit-ai-first) — read a TheoKit agent endpoint's `UIMessageStream` SSE `Response`\n * into reconstructed assistant `UIMessage`s, reusing the `ai` package's own consumer\n * primitives (`parseJsonEventStream` + `readUIMessageStream`) — the exact path\n * `@ai-sdk/react`'s `useChat` runs internally. No reinvented wire parser (Rule 9).\n *\n * `ai` is an OPTIONAL peer dependency, so it is imported dynamically: an app that never\n * calls an agent never pays for it, and importing `theokit/client` does not hard-require\n * `ai` (mirrors how the agent runtime dynamically imports `@theokit/sdk`). An agent app\n * always has `ai` installed (it is the UIMessageStream consumer).\n *\n * `onMessage` is invoked on every reconstruction step with the latest snapshot of the\n * assistant message, so a caller (the `useAgent` hook) can render streaming updates.\n */\nexport async function consumeUIMessageStream(\n response: Response,\n onMessage: (message: UIMessage) => void,\n): Promise<void> {\n const chunkStream = await responseToChunkStream(response)\n await consumeChunkStream(chunkStream, onMessage)\n}\n\n/**\n * M41 (ADR-0050 D3) — the reusable middle piece: a UIMessageStream SSE `Response` →\n * `ReadableStream<UIMessageChunk>`, reusing `ai`'s own `parseJsonEventStream` (the exact primitive\n * `useChat` runs). This is precisely what a `ChatTransport.sendMessages` returns, so `HttpTransport`\n * builds on it directly (no reinvented wire parser — Rule 9). A body-less response yields an empty stream.\n */\nexport async function responseToChunkStream(\n response: Response,\n): Promise<ReadableStream<UIMessageChunk>> {\n if (response.body === null) {\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n controller.close()\n },\n })\n }\n\n const { parseJsonEventStream, uiMessageChunkSchema } = await import('ai')\n\n // ai validates each SSE JSON frame against its own strict chunk schema (the exact gate `useChat`\n // runs), then yields `{ success, value }`; forward the valid chunks.\n const parsed = parseJsonEventStream({ stream: response.body, schema: uiMessageChunkSchema })\n return new ReadableStream<UIMessageChunk>({\n async start(controller) {\n for await (const result of parsed) {\n if (result.success) controller.enqueue(result.value)\n }\n controller.close()\n },\n })\n}\n\n/**\n * M41 (ADR-0050 D6) — read a `ReadableStream<UIMessageChunk>` into reconstructed assistant\n * `UIMessage`s via `ai`'s `readUIMessageStream`. Shared by `consumeUIMessageStream` (Response path)\n * and the framework-agnostic `AgentClient` store (transport path). `onMessage` fires on every\n * reconstruction step so a caller can render streaming updates.\n */\nexport async function consumeChunkStream(\n stream: ReadableStream<UIMessageChunk>,\n onMessage: (message: UIMessage) => void,\n): Promise<void> {\n const { readUIMessageStream } = await import('ai')\n // #136 — a provider failure (401/429/5xx) arrives as a `{ type: 'error', errorText }` chunk, NOT a\n // thrown rejection (the in-process runner and the SSE path both emit it as data). With the default\n // `readUIMessageStream({ stream })` (no `onError`, `terminateOnError` off) that chunk is silently\n // swallowed — the stream ends \"clean\" and the store settles to 'done' instead of 'error'.\n // `onError` captures the error; `terminateOnError` stops reconstructing partial messages after it AND\n // (under ai@7.0.14) errors the underlying iterator — so the `for await` below rejects and\n // `AgentClient.#drive`'s existing catch surfaces it (status='error', error set). The post-loop\n // `throw` is a defensive fallback that still surfaces the captured error if a future `ai` version\n // stops rejecting under `terminateOnError`; it is dead code under ai@7.0.14 but cheap version-robustness.\n let streamError: Error | undefined\n for await (const message of readUIMessageStream({\n stream,\n onError: (err) => {\n streamError = err instanceof Error ? err : new Error(String(err))\n },\n terminateOnError: true,\n })) {\n onMessage(message)\n }\n if (streamError !== undefined) throw streamError\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { responseToChunkStream } from './consume-ui-message-stream.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** Extra request headers — a static record OR a resolver called per request (for dynamic auth). */\nexport type HeadersResolver = Record<string, string> | (() => Record<string, string> | undefined)\n\nexport interface HttpTransportOptions {\n /** Agent endpoint path or URL, e.g. `/api/agents/support`. */\n api: string\n /**\n * Extra request headers (e.g. auth). Static record OR a resolver evaluated on EVERY request — pass a\n * resolver when the value is dynamic (a rotating JWT), so a stale header is never sent. Merged UNDER\n * per-request headers.\n */\n headers?: HeadersResolver\n /** Override fetch (primarily for tests / non-browser hosts) — static; resolved once at construction. */\n fetch?: typeof fetch\n}\n\n/** Normalize `ai`'s `Record | Headers | undefined` header option into a plain record. */\nfunction toRecord(headers: Record<string, string> | Headers | undefined): Record<string, string> {\n if (headers === undefined) return {}\n if (headers instanceof Headers) return Object.fromEntries(headers.entries())\n return headers\n}\n\n/**\n * M41 (ADR-0050 D3) — `ChatTransport` over the web agent path.\n *\n * - `sendMessages`: `POST <api>` with the UIMessageStream `accept` + the `X-Theo-Action` CSRF header\n * (HTTP method + headers are identical to the pre-M41 `useAgent` fetch; the body shape is a superset —\n * `{ ...input, messages: [UIMessage] }` — which the server's dual-path parser accepts, so no\n * regression), captures the server-minted `x-theokit-run-id`, and returns `ReadableStream<UIMessageChunk>`\n * via `ai`'s own SSE parser (`responseToChunkStream`).\n * - `reconnectToStream`: `GET <api>/runs/<runId>/stream` (M37 durable transport); 404 → `null` (the run\n * completed / was evicted). A caller may pass a `Last-Event-ID` header to resume only the tail; by\n * default the server replays the run from the start and the client upserts by message id (idempotent).\n * - `approve`: `POST <api>/approve/<id>` (out-of-band HITL settle).\n *\n * Implemented directly (not by subclassing `DefaultChatTransport`) because reconnect keys on our\n * server-minted `runId` captured from a response header, which the base class does not expose — see\n * ADR-0050 D3.\n */\nexport class HttpTransport implements AgentTransport {\n readonly #api: string\n readonly #headers: HeadersResolver\n readonly #fetch: typeof fetch\n /** Server-minted id of the last run (from `x-theokit-run-id`) — the reconnect key. */\n #lastRunId: string | undefined\n\n constructor(options: HttpTransportOptions) {\n this.#api = options.api\n this.#headers = options.headers ?? {}\n // BIND the default fetch to globalThis — the native `fetch` throws `TypeError: Illegal invocation` when\n // invoked as a method (`this.#fetch(...)` would set `this` to this transport instance, not the window).\n // An injected fetch (tests / non-browser hosts) is a plain function and is used as-is.\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n /** Resolve the configured headers per request (a resolver evaluates now — dynamic auth is never stale). */\n #resolveHeaders(): Record<string, string> {\n return (typeof this.#headers === 'function' ? this.#headers() : this.#headers) ?? {}\n }\n\n async sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, headers, body, chatId } = options\n // Only spread an object body (never a primitive — that would emit char-indexed keys). `body` is typed\n // `object | undefined` (ai's `ChatRequestOptions`), so no cast is needed — the guard narrows to\n // `object`. (A runtime-`null` body, which the type forbids, spreads to `{}` — harmless.) The server\n // accepts `{ messages }` (ai shape) AND `{ ...input }` (typed input), preferring the turn text.\n const extra = typeof body === 'object' ? body : undefined\n const response = await this.#fetch(this.#api, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'X-Theo-Action': '1',\n ...this.#resolveHeaders(),\n ...toRecord(headers),\n },\n // Send the stable `chatId` as the top-level `id` — the server reads it as the sessionId, so ONE\n // conversation (SDK history + session-scoped tools like `todolist`) persists across turns instead of\n // resetting on a fresh random session each request. Placed last so a session is never shadowed by an\n // `id` field inside the typed input. Undefined chatId ⇒ key omitted ⇒ server falls back (unchanged).\n body: JSON.stringify({ ...extra, messages, id: chatId }),\n signal: abortSignal,\n })\n if (!response.ok) {\n throw new Error(\n `Agent request to ${this.#api} failed: ${response.status} ${response.statusText}`,\n )\n }\n this.#lastRunId = response.headers.get('x-theokit-run-id') ?? undefined\n return responseToChunkStream(response)\n }\n\n async reconnectToStream(\n options: Parameters<ChatTransport<UIMessage>['reconnectToStream']>[0],\n ): Promise<ReadableStream<UIMessageChunk> | null> {\n if (this.#lastRunId === undefined) return null\n const response = await this.#fetch(`${this.#api}/runs/${this.#lastRunId}/stream`, {\n method: 'GET',\n headers: { ...this.#resolveHeaders(), ...toRecord(options.headers) },\n })\n if (response.status === 404) return null\n if (!response.ok) {\n throw new Error(\n `Agent reconnect to run ${this.#lastRunId} failed: ${response.status} ${response.statusText}`,\n )\n }\n return responseToChunkStream(response)\n }\n\n async approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n const response = await this.#fetch(`${this.#api}/approve/${approvalId}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'X-Theo-Action': '1',\n ...this.#resolveHeaders(),\n },\n body: JSON.stringify(decision),\n })\n if (!response.ok) {\n throw new Error(`Approve ${approvalId} failed: ${response.status} ${response.statusText}`)\n }\n }\n}\n","import type { UIMessage } from 'ai'\n\n/**\n * M41/M42 — extract the turn text from the last user message's text parts. Shared by the transports\n * that hand a plain `message` string to an in-process/push runner (`InProcessTransport`,\n * `ChannelTransport`) rather than POSTing the `messages[]` array (`HttpTransport`). One definition (G12).\n */\nexport function extractLastUserText(messages: readonly UIMessage[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]\n if (message.role !== 'user') continue\n const text = message.parts\n .filter((part): part is { type: 'text'; text: string } => part.type === 'text')\n .map((part) => part.text)\n .join('')\n if (text.length > 0) return text\n }\n return ''\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { extractLastUserText } from './last-user-text.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** An inline approval request handed to the transport's resolver (structural — no server import). */\nexport interface InProcessApprovalRequestLike {\n approvalId: string\n toolName: string\n opts: unknown\n}\n\n/** Resolve one gated-tool approval inline (mirrors the SDK's `boolean | HitlDecision` return). */\nexport type InProcessAwaitApproval = (\n req: InProcessApprovalRequestLike,\n) => Promise<boolean | ApprovalDecision>\n\n/** The input the injected runner receives (structurally compatible with `StreamAgentTurnInProcessInput`). */\nexport interface InProcessRunInput {\n message: string\n sessionId?: string\n signal?: AbortSignal\n awaitApproval?: InProcessAwaitApproval\n /** M43 — per-request context (from `sendMessages`'s `metadata`) — tenant / provider / auth for the runner. */\n context?: unknown\n}\n\n/**\n * The in-process turn runner. The consumer binds `streamAgentTurnInProcess(mod, apiKey, …)`:\n * `new InProcessTransport({ run: (input) => streamAgentTurnInProcess(mod, apiKey, input) })`.\n * Injecting it keeps this client module decoupled from `server/` and makes the transport testable.\n */\nexport type InProcessRunner = (input: InProcessRunInput) => AsyncGenerator<UIMessageChunk>\n\nexport interface InProcessTransportOptions {\n run: InProcessRunner\n}\n\n/** Bridge an `AsyncGenerator<UIMessageChunk>` into a pull-based `ReadableStream<UIMessageChunk>`. */\nfunction generatorToStream(gen: AsyncGenerator<UIMessageChunk>): ReadableStream<UIMessageChunk> {\n return new ReadableStream<UIMessageChunk>({\n async pull(controller) {\n try {\n const result = await gen.next()\n if (result.done === true) {\n controller.close()\n return\n }\n // After the done-guard, `result` is an IteratorYieldResult<UIMessageChunk> — value is typed.\n controller.enqueue(result.value)\n } catch (err) {\n controller.error(err)\n }\n },\n async cancel() {\n await gen.return(undefined)\n },\n })\n}\n\n/**\n * M41 (ADR-0050 D4) — `ChatTransport` over the in-process seam (`streamAgentTurnInProcess`), for the\n * terminal/desktop surfaces that run client + server in ONE process (no HTTP loopback).\n *\n * - `sendMessages`: bridge the injected runner's `AsyncGenerator<UIMessageChunk>` into a\n * `ReadableStream<UIMessageChunk>` (honoring `abortSignal`, which the runner forwards to the SDK).\n * - `reconnectToStream`: always `null` — a single process has no dropped server-side stream to resume\n * (mirrors `ai`'s `DirectChatTransport`).\n * - `approve`: resolve the pending inline approval by id (the run parks on `awaitApproval`). An unknown\n * id rejects (fail-fast, Rule 8 — never a silent resolve).\n *\n * Error asymmetry vs `HttpTransport` (by design, matching the `ChatTransport` contract): a runner that\n * throws SYNCHRONOUSLY surfaces the error when the stream is READ (via `controller.error`), not from the\n * `sendMessages` promise — whereas `HttpTransport` throws from `sendMessages` on a non-2xx response.\n */\n/**\n * Uma aprovação estacionada foi descartada porque o turno terminou sem decisão.\n *\n * M92 — tipado de propósito. Antes a promessa simplesmente **nunca** resolvia, e a chamada de tool do\n * SDK pendurava; `resolve(false)` seria pior ainda, porque é indistinguível de \"o usuário negou\".\n */\nexport class ApprovalAbortedError extends Error {\n constructor(\n readonly approvalId: string,\n motivo: string,\n ) {\n super(`Aprovação '${approvalId}' descartada: ${motivo}.`)\n this.name = 'ApprovalAbortedError'\n }\n}\n\nexport class InProcessTransport implements AgentTransport {\n readonly #run: InProcessRunner\n /**\n * Aprovações inline estacionadas: `approvalId` → como resolver **ou rejeitar** a promessa parada.\n *\n * M92 — o `reject` entrou junto com a eviction. Antes havia só o `resolve`, e nada apagava a entrada\n * quando o turno abortava: a promessa ficava pendente **para sempre**, e a chamada de tool do SDK\n * pendurava com ela. Uma promessa que nunca resolve **nem** rejeita é a forma mais silenciosa de\n * engolir um erro — nem stack trace existe (`error-handling.md § 2`).\n */\n readonly #pending = new Map<\n string,\n {\n resolve: (decision: boolean | ApprovalDecision) => void\n reject: (err: Error) => void\n turno: number\n }\n >()\n\n /** O turno corrente. Um `send()` novo incrementa e varre o anterior. */\n #turno = 0\n\n constructor(options: InProcessTransportOptions) {\n this.#run = options.run\n }\n\n /**\n * Cria o `awaitApproval` DESTE turno, com o número e o sinal fechados no closure.\n *\n * Campo compartilhado não serve, e a revisão do M92 mediu por quê: um runner do turno 1 que\n * estaciona **depois** do `send` do turno 2 lê o campo já sobrescrito e nasce etiquetado turno 2 —\n * o abort do turno 1 não o varre, e a promessa pendura. A primeira correção do M92 trocou \"ler no\n * momento da aprovação\" por \"ler no `send`\", e continuou errada pela mesma razão: um campo só.\n *\n * O closure é o único lugar onde o turno de um runner pode viver sem ser sobrescrito por outro.\n */\n #criarAwaitApproval(turno: number, sinal: AbortSignal | undefined): InProcessAwaitApproval {\n return (req) =>\n new Promise<boolean | ApprovalDecision>((resolve, reject) => {\n // Abortado ANTES de a aprovação estacionar: varrer no `sendMessages` não alcança este caso,\n // porque naquele momento não havia nada a varrer.\n if (sinal?.aborted === true) {\n reject(new ApprovalAbortedError(req.approvalId, 'o turno já estava abortado'))\n return\n }\n // Ids de aprovação são UUIDs do servidor — colisão é bug real (dois turnos reusando um id).\n // Falha rápido em vez de sobrescrever em silêncio o resolver estacionado do turno anterior.\n if (this.#pending.has(req.approvalId)) {\n reject(new Error(`Duplicate pending approval id '${req.approvalId}' — ids must be unique.`))\n return\n }\n this.#pending.set(req.approvalId, { resolve, reject, turno })\n })\n }\n\n /**\n * Varre as aprovações de um turno, rejeitando cada uma com erro TIPADO.\n *\n * Rejeitar e não `resolve(false)`: um `false` é indistinguível de *\"o usuário negou\"*, e a diferença\n * importa — negar é decisão, abortar é interrupção. O SDK precisa das duas para desenrolar a chamada\n * de tool corretamente.\n */\n #varrerTurno(turno: number, motivo: string): void {\n for (const [id, entrada] of [...this.#pending]) {\n if (entrada.turno !== turno) continue\n this.#pending.delete(id)\n entrada.reject(new ApprovalAbortedError(id, motivo))\n }\n }\n\n /** Quantas aprovações estão estacionadas. Existe para o teste poder provar a eviction. */\n get pendentes(): number {\n return this.#pending.size\n }\n\n sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, metadata } = options\n // M92 — um `send()` novo varre o turno anterior: aprovações daquele turno nunca mais serão\n // decididas, e deixá-las no `Map` é vazamento com uma promessa pendurada em cada uma.\n this.#varrerTurno(this.#turno, 'um turno novo começou')\n this.#turno += 1\n const turnoAtual = this.#turno\n\n // O sinal de abort do turno é a costura que já chegava aqui e não era usada. `once` porque um\n // `AbortSignal` dispara no máximo uma vez, e reter o listener manteria o transporte vivo.\n // Sinal JÁ abortado não dispara `addEventListener` — a revisão do M92 mediu: `pendentes=1` e a\n // promessa PENDENTE, ou seja, exatamente o travamento que este milestone existe para fechar,\n // ainda alcançável. A varredura roda na hora e o listener cobre o abort que vier depois.\n if (abortSignal?.aborted === true) {\n this.#varrerTurno(turnoAtual, 'o turno já estava abortado')\n } else {\n abortSignal?.addEventListener(\n 'abort',\n () => {\n this.#varrerTurno(turnoAtual, 'o turno foi abortado')\n },\n { once: true },\n )\n }\n const generator = this.#run({\n message: extractLastUserText(messages),\n signal: abortSignal ?? undefined,\n awaitApproval: this.#criarAwaitApproval(turnoAtual, abortSignal ?? undefined),\n // M43 — forward per-request context (the seam's `metadata`) to the runner.\n context: metadata,\n })\n return Promise.resolve(generatorToStream(generator))\n }\n\n reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null> {\n return Promise.resolve(null)\n }\n\n approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n const entrada = this.#pending.get(approvalId)\n if (entrada === undefined) {\n return Promise.reject(\n new Error(`No pending approval '${approvalId}' (unknown or already settled).`),\n )\n }\n this.#pending.delete(approvalId)\n entrada.resolve(decision)\n return Promise.resolve()\n }\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { extractLastUserText } from './last-user-text.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** Handlers the transport hands to the injected push source for one turn. */\nexport interface ChannelTurnHandlers {\n /** One pushed JSONL line (a serialized `UIMessageChunk`). */\n onLine: (line: string) => void\n /** The turn ended — no more lines. */\n onClose: () => void\n /** The push source failed. */\n onError?: (err: unknown) => void\n}\n\n/**\n * The injected push source — a Tauri `Channel`/`invoke` bridge, kept STRUCTURAL so core adds no\n * `@tauri-apps/*` dependency and the transport is testable with a fake (ADR-0051 D2). The Tauri app\n * wires it: `new Channel()`, `channel.onmessage = onLine`, `invoke('run_agent', { message, channel })`,\n * returning a teardown that aborts the sidecar turn.\n */\nexport interface ChannelPushSource {\n /**\n * Start a turn; deliver each JSONL `UIMessageChunk` line to `onLine`, then `onClose`. Return a teardown.\n * `turn.context` (M43) is the per-request context (from the seam's `metadata`) — the Tauri `invoke`\n * forwards it to the sidecar. When no context is set it is present as `context: undefined` (the key is\n * NOT absent) — a sidecar that checks `'context' in turn` should treat `undefined` as \"no context\".\n */\n start(turn: { message: string; context?: unknown }, handlers: ChannelTurnHandlers): () => void\n /** Optional HITL settle (another Tauri `invoke`). */\n settle?(approvalId: string, decision: ApprovalDecision): Promise<void>\n}\n\nexport interface ChannelTransportOptions {\n source: ChannelPushSource\n}\n\n/**\n * M42 (ADR-0051) — `ChatTransport` over a Tauri-`Channel`-shaped push source, for the desktop webview.\n *\n * - `sendMessages`: start the turn via the injected source and bridge its pushed JSONL frames into a\n * `ReadableStream<UIMessageChunk>` (built in `start` — a Channel is push, so the stream's queue buffers\n * frames; ADR-0051 D3). A malformed JSONL line is SKIPPED, never fatal (ADR-0051 D4, Rule 8). `abortSignal`\n * tears down the source and closes the stream.\n * - `reconnectToStream`: always `null` — the M36 sidecar runs the turn directly (no durable server stream);\n * this is the honest parity for a single-process push surface (ADR-0051 D5; mirrors `InProcessTransport`).\n * - `approve`: routes to the injected `settle` (another Tauri `invoke`); absent `settle` → a typed error.\n *\n * The push source is INJECTED — core stays Tauri-agnostic and this transport is unit-tested with a fake.\n */\nexport class ChannelTransport implements AgentTransport {\n readonly #source: ChannelPushSource\n\n constructor(options: ChannelTransportOptions) {\n this.#source = options.source\n }\n\n sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, metadata } = options\n const message = extractLastUserText(messages)\n const source = this.#source\n\n // Per-stream teardown/abort-detach, hoisted so `cancel` (consumer stops reading) can also tear the\n // source down. `closed` makes every terminal transition idempotent (no enqueue/close after close).\n let closed = false\n let teardown: () => void = () => undefined\n let detachAbort: () => void = () => undefined\n\n const stream = new ReadableStream<UIMessageChunk>({\n start(controller) {\n const finish = (settle: () => void): void => {\n if (closed) return\n closed = true\n detachAbort()\n settle()\n }\n teardown = source.start(\n { message, context: metadata },\n {\n onLine: (line) => {\n if (closed) return\n // Skip a malformed pushed line — one bad frame must never crash the webview (ADR-0051 D4).\n let parsed: unknown\n try {\n parsed = JSON.parse(line)\n } catch {\n return\n }\n // Discriminant guard: a `UIMessageChunk` is an object with a string `type`. The trust\n // boundary here is the LOCAL sidecar (not the network), so a discriminant check — not the\n // full `ai` schema (which isn't exposed standalone) — is proportionate: it rejects\n // structureless / wrong-shape payloads before they reach `readUIMessageStream`. Same\n // skip-not-crash policy as a parse error.\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof (parsed as { type?: unknown }).type !== 'string'\n ) {\n return\n }\n controller.enqueue(parsed as UIMessageChunk)\n },\n onClose: () => {\n finish(() => {\n controller.close()\n })\n },\n onError: (err) => {\n finish(() => {\n controller.error(err)\n })\n },\n },\n )\n if (abortSignal !== undefined) {\n const onAbort = (): void => {\n finish(() => {\n teardown()\n controller.close()\n })\n }\n if (abortSignal.aborted) onAbort()\n else {\n abortSignal.addEventListener('abort', onAbort)\n detachAbort = () => {\n abortSignal.removeEventListener('abort', onAbort)\n }\n }\n }\n },\n cancel() {\n // The consumer stopped reading (reader.cancel) — abort the source turn + drop the abort listener.\n if (closed) return\n closed = true\n detachAbort()\n teardown()\n },\n })\n return Promise.resolve(stream)\n }\n\n reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null> {\n return Promise.resolve(null)\n }\n\n async approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n if (this.#source.settle === undefined) {\n throw new Error(`This channel source has no HITL settle — cannot approve '${approvalId}'.`)\n }\n await this.#source.settle(approvalId, decision)\n }\n}\n","import type { UIMessage, UIMessageChunk } from 'ai'\n\nimport { consumeChunkStream } from './consume-ui-message-stream.js'\nimport type { AgentTransport, ApprovalDecision, RequestContext } from './transport.js'\n\nexport type UseAgentStatus = 'idle' | 'streaming' | 'done' | 'error'\n\n/** The observable state the store exposes (stable reference between emits — `useSyncExternalStore` contract). */\nexport interface AgentClientState {\n /** The CURRENT turn's assistant messages (per-turn; reset each `send`). Back-compat — unchanged since M41. */\n messages: UIMessage[]\n /**\n * M46 — the full conversation: committed turns + the current turn's user message + in-flight assistant.\n * Accumulated across sends (never reset except by `reset()`), with stable ids committed exactly once.\n * Render this instead of hand-rolling a transcript from `messages`.\n */\n thread: UIMessage[]\n status: UseAgentStatus\n error: Error | undefined\n}\n\n/** Derive the turn text from a typed input: `input.message` when present, else the serialized input. */\nfunction inputToText(input: unknown): string {\n if (\n typeof input === 'object' &&\n input !== null &&\n typeof (input as { message?: unknown }).message === 'string'\n ) {\n return (input as { message: string }).message\n }\n if (typeof input === 'string') return input\n return JSON.stringify(input)\n}\n\n/** Build a user `UIMessage` from a typed input (text from `{ message }`, else the serialized input). */\nfunction buildUserMessage(input: unknown): UIMessage {\n return {\n id: crypto.randomUUID(),\n role: 'user',\n parts: [{ type: 'text', text: inputToText(input) }],\n }\n}\n\n/**\n * M41 (ADR-0050 D6) — the framework-agnostic agent client store.\n *\n * Holds `messages`/`status`/`error`, drives an {@link AgentTransport}, and notifies subscribers on\n * change. It is the SINGLE consolidation point: web (`HttpTransport`) and terminal/desktop\n * (`InProcessTransport`) run the SAME store. `useAgent` is a thin React binding over it via\n * `useSyncExternalStore`; a standalone (no-React) client (M44) can subscribe directly. Being\n * framework-agnostic, it is unit-tested without a DOM.\n */\n/**\n * M92 — opções do cliente. Aditivo: sem elas, o comportamento é o de sempre.\n */\nexport interface AgentClientOptions {\n /**\n * Janela de coalescing em ms. `0` ou ausente = emite por delta de token (comportamento pré-M92).\n *\n * Opt-in de propósito: este é um pacote publicado, e mudar a frequência de emit por padrão mudaria o\n * comportamento observável de quem conta emits ou depende da latência do primeiro token.\n */\n readonly emitIntervalMs?: number\n}\n\nexport class AgentClient<TInput = unknown> {\n readonly #transport: AgentTransport\n readonly #chatId = crypto.randomUUID()\n readonly #listeners = new Set<() => void>()\n /** M43 — resolves per-request context (evaluated on every send/reconnect — dynamic, never stale). */\n readonly #contextResolver: (() => RequestContext | undefined) | undefined\n\n #messages: UIMessage[] = []\n #status: UseAgentStatus = 'idle'\n #error: Error | undefined\n #controller: AbortController | null = null\n // M46 — conversation accumulation (React-free; all surfaces inherit it via the snapshot).\n /** Committed (finished) turns — user + assistant, with stable fabricated ids. */\n #committed: UIMessage[] = []\n /** The current turn's user message (in `thread` but never in `messages` — back-compat). */\n #currentUser: UIMessage | undefined\n /** A stable id for the current turn's assistant (the SDK leaves it empty — we fabricate one). */\n #currentAssistantId = ''\n #snapshot: AgentClientState = { messages: [], thread: [], status: 'idle', error: undefined }\n\n /**\n * M92 — o prefixo commitado, materializado UMA vez por escrita em vez de por delta de token.\n *\n * `#committed` só muda em dois lugares (medido): no `done` de `send()` e em `reset()`. Entre deltas\n * ele é constante, então reconstruí-lo a cada `#emit` é trabalho que a estrutura já garante inútil.\n *\n * Invalidação é **na escrita**, não por comparação: comparar custaria o mesmo O(C) que isto evita, e\n * memoizar por comprimento erraria em `reset()` — comprimento igual com conteúdo diferente é\n * possível, e o bug seria invisível.\n *\n * Honestidade sobre o tamanho do ganho: medido, o spread custa **0,0062 ms por delta @400 mensagens**\n * — 3,1 ms num turno de 500 deltas. É real e é micro. A ordem de grandeza deste milestone está no\n * coalescing abaixo, porque o que pende de cada emit (a derivação da timeline) custa **3,274 ms por\n * chamada** no mesmo tamanho de thread (M86).\n */\n #prefixo: UIMessage[] = []\n\n /** Coalescing opt-in: `0` (default) emite por delta, como sempre. */\n readonly #emitIntervalMs: number\n #timerDeEmit: ReturnType<typeof setTimeout> | undefined\n\n constructor(\n transport: AgentTransport,\n contextResolver?: () => RequestContext | undefined,\n options?: AgentClientOptions,\n ) {\n this.#transport = transport\n this.#contextResolver = contextResolver\n this.#emitIntervalMs = options?.emitIntervalMs ?? 0\n }\n\n /** Subscribe to state changes; returns an unsubscribe fn. */\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n /** The current immutable snapshot (stable reference until the next emit). */\n getSnapshot = (): AgentClientState => this.#snapshot\n\n /**\n * Emite AGORA. Usado diretamente nas transições de status — ver `#agendarEmit`.\n */\n #emit(): void {\n if (this.#timerDeEmit !== undefined) {\n clearTimeout(this.#timerDeEmit)\n this.#timerDeEmit = undefined\n }\n // thread = prefixo commitado + o user deste turno + o assistant em voo (`messages`). O prefixo vem\n // materializado (`#prefixo`); só a cauda é concatenada aqui. A referência do SNAPSHOT muda só neste\n // ponto, que é o contrato que `useSyncExternalStore` exige.\n // `concat` ÚNICO, não dois spreads.\n //\n // A primeira versão do M92 trocou `#committed` por `#prefixo` e manteve o spread — e `#prefixo` era\n // o **mesmo array**, um alias puro. A revisão mediu: 0,16 µs @C=20 → ~2 µs @C=400, ainda linear em\n // C, ou seja, byte-idêntico ao anterior. O DoD pedia getter preguiçoso ou concat único; eu tinha\n // entregue um rename.\n //\n // `concat` copia o prefixo uma vez por emit em vez de espalhá-lo elemento a elemento, e o motor\n // usa memcpy para arrays densos. Continua O(C) — não há como devolver um array novo sem copiar —\n // mas com constante menor, e é honesto dizer que o ganho aqui é de constante, não de ordem.\n const cauda = this.#currentUser ? [this.#currentUser, ...this.#messages] : this.#messages\n const thread = this.#prefixo.concat(cauda)\n this.#snapshot = { messages: this.#messages, thread, status: this.#status, error: this.#error }\n for (const listener of this.#listeners) listener()\n }\n\n /**\n * Emite por JANELA quando o coalescing está ligado; imediatamente quando não está.\n *\n * Borda de saída: o timer emite ao **fim** da janela, com o estado mais recente. O que isto compra\n * não é um emit mais barato — é **menos emits**, e o que pende de cada um é a derivação de 3,274 ms\n * medida no M86.\n *\n * As transições de status (`done`/`error`/`abort`) NÃO passam por aqui: elas chamam `#emit` direto,\n * porque um estado final preso num timer de 16 ms é um estado final perdido se o processo sair antes\n * — e `exec` sai logo após o turno.\n */\n #agendarEmit(): void {\n if (this.#emitIntervalMs <= 0) {\n this.#emit()\n return\n }\n if (this.#timerDeEmit !== undefined) return\n this.#timerDeEmit = setTimeout(() => {\n this.#timerDeEmit = undefined\n this.#emit()\n }, this.#emitIntervalMs)\n }\n\n #upsert(message: UIMessage): void {\n const next = [...this.#messages]\n const idx = next.findIndex((existing) => existing.id === message.id)\n if (idx >= 0) next[idx] = message\n else next.push(message)\n this.#messages = next\n }\n\n async #drive(\n open: () => Promise<ReadableStream<UIMessageChunk> | null>,\n controller: AbortController,\n ): Promise<void> {\n // Read via a function (not a narrowed local) — `aborted` flips ASYNC across the awaits below, so the\n // control-flow narrowing of a direct `signal.aborted` read would be wrong. A stale drive (its\n // controller aborted because a newer send/abort took over) MUST NOT clobber the newer status.\n const aborted = (): boolean => controller.signal.aborted\n try {\n const stream = await open()\n if (aborted()) return\n if (stream === null) {\n // Nothing to resume (e.g. reconnect after the run completed). Settle without error.\n this.#status = this.#messages.length > 0 ? 'done' : 'idle'\n this.#emit()\n return\n }\n await consumeChunkStream(stream, (message) => {\n if (aborted()) return\n // The SDK leaves the assistant message id empty — fabricate a stable per-turn id so every chunk\n // upserts into the SAME message and the committed copy has a collision-free key (M46).\n const stamped = message.id ? message : { ...message, id: this.#currentAssistantId }\n this.#upsert(stamped)\n // O ÚNICO ponto por delta de token — todos os outros `#emit` deste arquivo são transições de\n // status, e essas nunca esperam timer (ADR-2).\n this.#agendarEmit()\n })\n if (aborted()) return\n this.#status = 'done'\n this.#emit()\n } catch (err) {\n if (aborted()) return\n this.#error = err instanceof Error ? err : new Error(String(err))\n this.#status = 'error'\n this.#emit()\n }\n }\n\n /** Send a typed input; opens a fresh stream (replaces prior messages). */\n send = (input: TInput): void => {\n // M46 — commit the PRIOR turn into history exactly once, but ONLY if it finished cleanly (`done`).\n // An errored or aborted turn (status !== 'done') is dropped, keeping committed history uncorrupted.\n if (this.#status === 'done' && this.#currentUser) {\n this.#committed = [...this.#committed, this.#currentUser, ...this.#messages]\n // Invalidação NA ESCRITA (ADR-3): este é um dos dois únicos pontos que mexem em `#committed`.\n this.#prefixo = this.#committed\n }\n this.abort()\n const controller = new AbortController()\n this.#controller = controller\n const userMsg = buildUserMessage(input)\n this.#currentUser = userMsg\n this.#currentAssistantId = crypto.randomUUID()\n this.#messages = []\n this.#error = undefined\n this.#status = 'streaming'\n this.#emit()\n const context = this.#contextResolver?.()\n void this.#drive(\n () =>\n this.#transport.sendMessages({\n trigger: 'submit-message',\n chatId: this.#chatId,\n messageId: undefined,\n messages: [userMsg],\n abortSignal: controller.signal,\n // Only object inputs flow as the request `body` (the turn text is always in `messages`);\n // a primitive input is carried by the user message, never spread into the body.\n body: typeof input === 'object' && input !== null ? input : undefined,\n // M43 — per-request context reaches every transport (headers → HTTP, metadata → in-process/channel).\n headers: context?.headers,\n metadata: context?.metadata,\n }),\n controller,\n )\n }\n\n /** Resume an interrupted stream via the transport's `reconnectToStream` (no-op when unavailable). */\n reconnect = (): void => {\n const controller = new AbortController()\n this.#controller = controller\n // Reconnecting before any send() (or after reset()) leaves #currentAssistantId empty — fabricate one\n // so a replayed assistant never lands in `thread` with an empty, non-unique id (M46 invariant).\n if (!this.#currentAssistantId) this.#currentAssistantId = crypto.randomUUID()\n // Reconnect means \"resume/retry\" — a stale error must not linger next to a fresh 'streaming' status.\n this.#error = undefined\n this.#status = 'streaming'\n this.#emit()\n const context = this.#contextResolver?.()\n void this.#drive(\n () =>\n this.#transport.reconnectToStream({\n chatId: this.#chatId,\n headers: context?.headers,\n metadata: context?.metadata,\n }),\n controller,\n )\n }\n\n /** Abort an in-flight stream (not an error — leaves messages as-is). */\n abort = (): void => {\n this.#controller?.abort()\n this.#controller = null\n // Finalize the status when the USER aborts an in-flight turn: the aborted `#drive` early-returns\n // without touching status (so a stale drive can't clobber a newer turn), which would otherwise leave\n // `status` stuck on 'streaming' — a lingering spinner + an unusable surface. A caller that aborts to\n // start a NEW turn (`send`/`sendMessages`) sets 'streaming' again immediately after, so this is safe.\n if (this.#status === 'streaming') {\n this.#status = this.#committed.length > 0 || this.#messages.length > 0 ? 'done' : 'idle'\n this.#emit()\n }\n }\n\n /** Clear messages + error, back to idle. */\n reset = (): void => {\n this.abort()\n this.#messages = []\n // M46 — reset means a NEW conversation: clear committed history + the current turn's user too.\n this.#committed = []\n // O outro ponto de escrita. Sem isto o `reset()` serviria o prefixo velho — e comprimento igual\n // com conteúdo diferente é exatamente o caso que uma memoização por tamanho não pegaria.\n this.#prefixo = this.#committed\n this.#currentUser = undefined\n this.#error = undefined\n this.#status = 'idle'\n this.#emit()\n }\n\n /** Settle a paused HITL approval via the transport's HITL path (HTTP POST or inline callback). */\n approve = async (approvalId: string, decision: ApprovalDecision): Promise<void> => {\n await this.#transport.approve?.(approvalId, decision)\n }\n}\n","import { ChannelTransport, type ChannelPushSource } from './channel-transport.js'\nimport { InProcessTransport, type InProcessRunner } from './in-process-transport.js'\n\n/**\n * M47 (ADR-M47-2) — a typed, client-safe handle for an exposed agent.\n *\n * It carries ONLY the HTTP `path` at runtime plus phantom `input`/`toolNames` types (never populated) — so\n * `useAgent(chat)` / `createAgentClient(chat…)` bind with NO magic string (the path is generated from the\n * `@Expose` exposure, not hand-typed) and NO duplicated input type (the input type flows through the phantom\n * generic, inferred from the agent's `.input()`). This mirrors tRPC/Hono's type-only handle: the client\n * pulls the agent's TYPE via `import type`, never its server runtime. The generated `@theo/agents` module\n * emits one `export const <name> = agentHandle('/api/agents/<name>')` per agent, typed with the phantoms.\n */\nexport interface AgentHandle<TInput = unknown, TToolNames extends string = string> {\n /** The agent's HTTP endpoint path (e.g. `/api/agents/chat`). The only serializable/runtime-bearing field. */\n readonly path: string\n /**\n * M47 — bind this agent in-process (TUI / single-process): wraps the app's runner in an\n * {@link InProcessTransport}. `useAgent(chat.inProcess(run))` drives the SAME agent without HTTP.\n */\n inProcess(run: InProcessRunner): InProcessTransport\n /**\n * M47 — bind this agent over a push channel (Tauri desktop webview): wraps the source in a\n * {@link ChannelTransport}. `createAgentClient(chat.channel(source))` drives the SAME agent.\n */\n channel(source: ChannelPushSource): ChannelTransport\n /** Phantom — the agent's `input` type, carried for `useAgent(handle).send` inference. Never populated. */\n readonly __input?: TInput\n /** Phantom — the agent's tool-name union, carried end-to-end. Never populated. */\n readonly __toolNames?: TToolNames\n}\n\n/**\n * Build an {@link AgentHandle} from an agent's HTTP path. Types are supplied by the caller/codegen. The\n * `inProcess`/`channel` binders are methods (dropped by `JSON.stringify`, so the `{ path }` core stays\n * serializable + client-safe) that produce the M41 transports for the non-web surfaces.\n */\nexport function agentHandle<TInput = unknown, TToolNames extends string = string>(\n path: string,\n): AgentHandle<TInput, TToolNames> {\n return {\n path,\n inProcess: (run) => new InProcessTransport({ run }),\n channel: (source) => new ChannelTransport({ source }),\n }\n}\n\n/** Narrow an unknown binding to an {@link AgentHandle} (has a string `path`, is not a transport). */\nexport function isAgentHandle(value: unknown): value is AgentHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { path?: unknown }).path === 'string' &&\n typeof (value as { sendMessages?: unknown }).sendMessages !== 'function'\n )\n}\n"],"mappings":"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","names":["consumeUIMessageStream","response","onMessage","chunkStream","responseToChunkStream","consumeChunkStream","body","ReadableStream","start","controller","close","parseJsonEventStream","uiMessageChunkSchema","parsed","stream","schema","result","success","enqueue","value","readUIMessageStream","streamError","message","onError","err","Error","String","terminateOnError","undefined","toRecord","headers","undefined","Headers","Object","fromEntries","entries","HttpTransport","options","api","fetch","globalThis","bind","sendMessages","messages","abortSignal","body","chatId","extra","response","method","accept","JSON","stringify","id","signal","ok","Error","status","statusText","get","responseToChunkStream","reconnectToStream","approve","approvalId","decision","extractLastUserText","messages","i","length","message","role","text","parts","filter","part","type","map","join","generatorToStream","gen","ReadableStream","pull","controller","result","next","done","close","enqueue","value","err","error","cancel","return","undefined","ApprovalAbortedError","Error","approvalId","motivo","name","InProcessTransport","Map","options","run","turno","sinal","req","Promise","resolve","reject","aborted","has","set","id","entrada","delete","pendentes","size","sendMessages","messages","abortSignal","metadata","turnoAtual","addEventListener","once","generator","message","extractLastUserText","signal","awaitApproval","context","reconnectToStream","approve","decision","get","ChannelTransport","options","source","sendMessages","messages","abortSignal","metadata","message","extractLastUserText","closed","teardown","undefined","detachAbort","stream","ReadableStream","start","controller","finish","settle","context","onLine","line","parsed","JSON","parse","type","enqueue","onClose","close","onError","err","error","onAbort","aborted","addEventListener","removeEventListener","cancel","Promise","resolve","reconnectToStream","approve","approvalId","decision","Error","inputToText","input","message","JSON","stringify","buildUserMessage","id","crypto","randomUUID","role","parts","type","text","AgentClient","Set","messages","thread","status","error","undefined","transport","contextResolver","options","emitIntervalMs","subscribe","listener","add","delete","getSnapshot","clearTimeout","cauda","concat","setTimeout","next","idx","findIndex","existing","push","open","controller","aborted","signal","stream","length","consumeChunkStream","stamped","err","Error","String","send","abort","AbortController","userMsg","context","sendMessages","trigger","chatId","messageId","abortSignal","body","headers","metadata","reconnect","reconnectToStream","reset","approve","approvalId","decision","agentHandle","path","inProcess","run","InProcessTransport","channel","source","ChannelTransport","isAgentHandle","value","sendMessages"]}
@@ -1,5 +1,5 @@
1
1
  import { UIMessage } from 'ai';
2
- import { R as RequestContext, U as UseAgentStatus, A as ApprovalDecision, a as AgentHandle, b as AgentTransport } from './agent-handle-DNbFlkrw.js';
2
+ import { R as RequestContext, U as UseAgentStatus, A as ApprovalDecision, a as AgentHandle, b as AgentTransport } from './agent-handle-C6q7iA4u.js';
3
3
 
4
4
  interface UseAgentReturn<TInput = unknown, TToolNames extends string = string> {
5
5
  /** The CURRENT turn's assistant messages (per-turn; reset each `send`). Back-compat since M41. */
@@ -2,7 +2,7 @@ import {
2
2
  AgentClient,
3
3
  HttpTransport,
4
4
  isAgentHandle
5
- } from "./chunk-M2JFE6IM.js";
5
+ } from "./chunk-NTDOKNSU.js";
6
6
  import {
7
7
  __name
8
8
  } from "./chunk-7QVYU63E.js";
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as AgentTransport, A as ApprovalDecision } from './agent-handle-DNbFlkrw.js';
2
- export { c as AgentClient, d as AgentClientState, a as AgentHandle, C as ChannelPushSource, e as ChannelTransport, f as ChannelTransportOptions, g as ChannelTurnHandlers, I as InProcessApprovalRequestLike, h as InProcessAwaitApproval, i as InProcessRunInput, j as InProcessRunner, k as InProcessTransport, l as InProcessTransportOptions, R as RequestContext, U as UseAgentStatus, m as agentHandle, n as isAgentHandle } from './agent-handle-DNbFlkrw.js';
1
+ import { b as AgentTransport, A as ApprovalDecision } from './agent-handle-C6q7iA4u.js';
2
+ export { c as AgentClient, d as AgentClientOptions, e as AgentClientState, a as AgentHandle, f as ApprovalAbortedError, C as ChannelPushSource, g as ChannelTransport, h as ChannelTransportOptions, i as ChannelTurnHandlers, I as InProcessApprovalRequestLike, j as InProcessAwaitApproval, k as InProcessRunInput, l as InProcessRunner, m as InProcessTransport, n as InProcessTransportOptions, R as RequestContext, U as UseAgentStatus, o as agentHandle, p as isAgentHandle } from './agent-handle-C6q7iA4u.js';
3
3
  import { ChatTransport, UIMessage, UIMessageChunk } from 'ai';
4
4
 
5
5
  /** Extra request headers — a static record OR a resolver called per request (for dynamic auth). */
package/dist/client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AgentClient,
3
+ ApprovalAbortedError,
3
4
  ChannelTransport,
4
5
  HttpTransport,
5
6
  InProcessTransport,
@@ -9,10 +10,11 @@ import {
9
10
  extractLastUserText,
10
11
  isAgentHandle,
11
12
  responseToChunkStream
12
- } from "./chunk-M2JFE6IM.js";
13
+ } from "./chunk-NTDOKNSU.js";
13
14
  import "./chunk-7QVYU63E.js";
14
15
  export {
15
16
  AgentClient,
17
+ ApprovalAbortedError,
16
18
  ChannelTransport,
17
19
  HttpTransport,
18
20
  InProcessTransport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theokit/agents",
3
- "version": "4.26.2",
3
+ "version": "4.27.1",
4
4
  "description": "AI agents as first-class citizens of the TheoKit pipeline. The fluent agent()/tool() builders compile to SDK Agent.create() (M31 builder-only authoring API).",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/client/consume-ui-message-stream.ts","../src/client/http-transport.ts","../src/client/last-user-text.ts","../src/client/in-process-transport.ts","../src/client/channel-transport.ts","../src/client/agent-client.ts","../src/client/agent-handle.ts"],"sourcesContent":["import type { UIMessage, UIMessageChunk } from 'ai'\n\n/**\n * M2 (theokit-ai-first) — read a TheoKit agent endpoint's `UIMessageStream` SSE `Response`\n * into reconstructed assistant `UIMessage`s, reusing the `ai` package's own consumer\n * primitives (`parseJsonEventStream` + `readUIMessageStream`) — the exact path\n * `@ai-sdk/react`'s `useChat` runs internally. No reinvented wire parser (Rule 9).\n *\n * `ai` is an OPTIONAL peer dependency, so it is imported dynamically: an app that never\n * calls an agent never pays for it, and importing `theokit/client` does not hard-require\n * `ai` (mirrors how the agent runtime dynamically imports `@theokit/sdk`). An agent app\n * always has `ai` installed (it is the UIMessageStream consumer).\n *\n * `onMessage` is invoked on every reconstruction step with the latest snapshot of the\n * assistant message, so a caller (the `useAgent` hook) can render streaming updates.\n */\nexport async function consumeUIMessageStream(\n response: Response,\n onMessage: (message: UIMessage) => void,\n): Promise<void> {\n const chunkStream = await responseToChunkStream(response)\n await consumeChunkStream(chunkStream, onMessage)\n}\n\n/**\n * M41 (ADR-0050 D3) — the reusable middle piece: a UIMessageStream SSE `Response` →\n * `ReadableStream<UIMessageChunk>`, reusing `ai`'s own `parseJsonEventStream` (the exact primitive\n * `useChat` runs). This is precisely what a `ChatTransport.sendMessages` returns, so `HttpTransport`\n * builds on it directly (no reinvented wire parser — Rule 9). A body-less response yields an empty stream.\n */\nexport async function responseToChunkStream(\n response: Response,\n): Promise<ReadableStream<UIMessageChunk>> {\n if (response.body === null) {\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n controller.close()\n },\n })\n }\n\n const { parseJsonEventStream, uiMessageChunkSchema } = await import('ai')\n\n // ai validates each SSE JSON frame against its own strict chunk schema (the exact gate `useChat`\n // runs), then yields `{ success, value }`; forward the valid chunks.\n const parsed = parseJsonEventStream({ stream: response.body, schema: uiMessageChunkSchema })\n return new ReadableStream<UIMessageChunk>({\n async start(controller) {\n for await (const result of parsed) {\n if (result.success) controller.enqueue(result.value)\n }\n controller.close()\n },\n })\n}\n\n/**\n * M41 (ADR-0050 D6) — read a `ReadableStream<UIMessageChunk>` into reconstructed assistant\n * `UIMessage`s via `ai`'s `readUIMessageStream`. Shared by `consumeUIMessageStream` (Response path)\n * and the framework-agnostic `AgentClient` store (transport path). `onMessage` fires on every\n * reconstruction step so a caller can render streaming updates.\n */\nexport async function consumeChunkStream(\n stream: ReadableStream<UIMessageChunk>,\n onMessage: (message: UIMessage) => void,\n): Promise<void> {\n const { readUIMessageStream } = await import('ai')\n // #136 — a provider failure (401/429/5xx) arrives as a `{ type: 'error', errorText }` chunk, NOT a\n // thrown rejection (the in-process runner and the SSE path both emit it as data). With the default\n // `readUIMessageStream({ stream })` (no `onError`, `terminateOnError` off) that chunk is silently\n // swallowed — the stream ends \"clean\" and the store settles to 'done' instead of 'error'.\n // `onError` captures the error; `terminateOnError` stops reconstructing partial messages after it AND\n // (under ai@7.0.14) errors the underlying iterator — so the `for await` below rejects and\n // `AgentClient.#drive`'s existing catch surfaces it (status='error', error set). The post-loop\n // `throw` is a defensive fallback that still surfaces the captured error if a future `ai` version\n // stops rejecting under `terminateOnError`; it is dead code under ai@7.0.14 but cheap version-robustness.\n let streamError: Error | undefined\n for await (const message of readUIMessageStream({\n stream,\n onError: (err) => {\n streamError = err instanceof Error ? err : new Error(String(err))\n },\n terminateOnError: true,\n })) {\n onMessage(message)\n }\n if (streamError !== undefined) throw streamError\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { responseToChunkStream } from './consume-ui-message-stream.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** Extra request headers — a static record OR a resolver called per request (for dynamic auth). */\nexport type HeadersResolver = Record<string, string> | (() => Record<string, string> | undefined)\n\nexport interface HttpTransportOptions {\n /** Agent endpoint path or URL, e.g. `/api/agents/support`. */\n api: string\n /**\n * Extra request headers (e.g. auth). Static record OR a resolver evaluated on EVERY request — pass a\n * resolver when the value is dynamic (a rotating JWT), so a stale header is never sent. Merged UNDER\n * per-request headers.\n */\n headers?: HeadersResolver\n /** Override fetch (primarily for tests / non-browser hosts) — static; resolved once at construction. */\n fetch?: typeof fetch\n}\n\n/** Normalize `ai`'s `Record | Headers | undefined` header option into a plain record. */\nfunction toRecord(headers: Record<string, string> | Headers | undefined): Record<string, string> {\n if (headers === undefined) return {}\n if (headers instanceof Headers) return Object.fromEntries(headers.entries())\n return headers\n}\n\n/**\n * M41 (ADR-0050 D3) — `ChatTransport` over the web agent path.\n *\n * - `sendMessages`: `POST <api>` with the UIMessageStream `accept` + the `X-Theo-Action` CSRF header\n * (HTTP method + headers are identical to the pre-M41 `useAgent` fetch; the body shape is a superset —\n * `{ ...input, messages: [UIMessage] }` — which the server's dual-path parser accepts, so no\n * regression), captures the server-minted `x-theokit-run-id`, and returns `ReadableStream<UIMessageChunk>`\n * via `ai`'s own SSE parser (`responseToChunkStream`).\n * - `reconnectToStream`: `GET <api>/runs/<runId>/stream` (M37 durable transport); 404 → `null` (the run\n * completed / was evicted). A caller may pass a `Last-Event-ID` header to resume only the tail; by\n * default the server replays the run from the start and the client upserts by message id (idempotent).\n * - `approve`: `POST <api>/approve/<id>` (out-of-band HITL settle).\n *\n * Implemented directly (not by subclassing `DefaultChatTransport`) because reconnect keys on our\n * server-minted `runId` captured from a response header, which the base class does not expose — see\n * ADR-0050 D3.\n */\nexport class HttpTransport implements AgentTransport {\n readonly #api: string\n readonly #headers: HeadersResolver\n readonly #fetch: typeof fetch\n /** Server-minted id of the last run (from `x-theokit-run-id`) — the reconnect key. */\n #lastRunId: string | undefined\n\n constructor(options: HttpTransportOptions) {\n this.#api = options.api\n this.#headers = options.headers ?? {}\n // BIND the default fetch to globalThis — the native `fetch` throws `TypeError: Illegal invocation` when\n // invoked as a method (`this.#fetch(...)` would set `this` to this transport instance, not the window).\n // An injected fetch (tests / non-browser hosts) is a plain function and is used as-is.\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n /** Resolve the configured headers per request (a resolver evaluates now — dynamic auth is never stale). */\n #resolveHeaders(): Record<string, string> {\n return (typeof this.#headers === 'function' ? this.#headers() : this.#headers) ?? {}\n }\n\n async sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, headers, body, chatId } = options\n // Only spread an object body (never a primitive — that would emit char-indexed keys). `body` is typed\n // `object | undefined` (ai's `ChatRequestOptions`), so no cast is needed — the guard narrows to\n // `object`. (A runtime-`null` body, which the type forbids, spreads to `{}` — harmless.) The server\n // accepts `{ messages }` (ai shape) AND `{ ...input }` (typed input), preferring the turn text.\n const extra = typeof body === 'object' ? body : undefined\n const response = await this.#fetch(this.#api, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'X-Theo-Action': '1',\n ...this.#resolveHeaders(),\n ...toRecord(headers),\n },\n // Send the stable `chatId` as the top-level `id` — the server reads it as the sessionId, so ONE\n // conversation (SDK history + session-scoped tools like `todolist`) persists across turns instead of\n // resetting on a fresh random session each request. Placed last so a session is never shadowed by an\n // `id` field inside the typed input. Undefined chatId ⇒ key omitted ⇒ server falls back (unchanged).\n body: JSON.stringify({ ...extra, messages, id: chatId }),\n signal: abortSignal,\n })\n if (!response.ok) {\n throw new Error(\n `Agent request to ${this.#api} failed: ${response.status} ${response.statusText}`,\n )\n }\n this.#lastRunId = response.headers.get('x-theokit-run-id') ?? undefined\n return responseToChunkStream(response)\n }\n\n async reconnectToStream(\n options: Parameters<ChatTransport<UIMessage>['reconnectToStream']>[0],\n ): Promise<ReadableStream<UIMessageChunk> | null> {\n if (this.#lastRunId === undefined) return null\n const response = await this.#fetch(`${this.#api}/runs/${this.#lastRunId}/stream`, {\n method: 'GET',\n headers: { ...this.#resolveHeaders(), ...toRecord(options.headers) },\n })\n if (response.status === 404) return null\n if (!response.ok) {\n throw new Error(\n `Agent reconnect to run ${this.#lastRunId} failed: ${response.status} ${response.statusText}`,\n )\n }\n return responseToChunkStream(response)\n }\n\n async approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n const response = await this.#fetch(`${this.#api}/approve/${approvalId}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'X-Theo-Action': '1',\n ...this.#resolveHeaders(),\n },\n body: JSON.stringify(decision),\n })\n if (!response.ok) {\n throw new Error(`Approve ${approvalId} failed: ${response.status} ${response.statusText}`)\n }\n }\n}\n","import type { UIMessage } from 'ai'\n\n/**\n * M41/M42 — extract the turn text from the last user message's text parts. Shared by the transports\n * that hand a plain `message` string to an in-process/push runner (`InProcessTransport`,\n * `ChannelTransport`) rather than POSTing the `messages[]` array (`HttpTransport`). One definition (G12).\n */\nexport function extractLastUserText(messages: readonly UIMessage[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]\n if (message.role !== 'user') continue\n const text = message.parts\n .filter((part): part is { type: 'text'; text: string } => part.type === 'text')\n .map((part) => part.text)\n .join('')\n if (text.length > 0) return text\n }\n return ''\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { extractLastUserText } from './last-user-text.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** An inline approval request handed to the transport's resolver (structural — no server import). */\nexport interface InProcessApprovalRequestLike {\n approvalId: string\n toolName: string\n opts: unknown\n}\n\n/** Resolve one gated-tool approval inline (mirrors the SDK's `boolean | HitlDecision` return). */\nexport type InProcessAwaitApproval = (\n req: InProcessApprovalRequestLike,\n) => Promise<boolean | ApprovalDecision>\n\n/** The input the injected runner receives (structurally compatible with `StreamAgentTurnInProcessInput`). */\nexport interface InProcessRunInput {\n message: string\n sessionId?: string\n signal?: AbortSignal\n awaitApproval?: InProcessAwaitApproval\n /** M43 — per-request context (from `sendMessages`'s `metadata`) — tenant / provider / auth for the runner. */\n context?: unknown\n}\n\n/**\n * The in-process turn runner. The consumer binds `streamAgentTurnInProcess(mod, apiKey, …)`:\n * `new InProcessTransport({ run: (input) => streamAgentTurnInProcess(mod, apiKey, input) })`.\n * Injecting it keeps this client module decoupled from `server/` and makes the transport testable.\n */\nexport type InProcessRunner = (input: InProcessRunInput) => AsyncGenerator<UIMessageChunk>\n\nexport interface InProcessTransportOptions {\n run: InProcessRunner\n}\n\n/** Bridge an `AsyncGenerator<UIMessageChunk>` into a pull-based `ReadableStream<UIMessageChunk>`. */\nfunction generatorToStream(gen: AsyncGenerator<UIMessageChunk>): ReadableStream<UIMessageChunk> {\n return new ReadableStream<UIMessageChunk>({\n async pull(controller) {\n try {\n const result = await gen.next()\n if (result.done === true) {\n controller.close()\n return\n }\n // After the done-guard, `result` is an IteratorYieldResult<UIMessageChunk> — value is typed.\n controller.enqueue(result.value)\n } catch (err) {\n controller.error(err)\n }\n },\n async cancel() {\n await gen.return(undefined)\n },\n })\n}\n\n/**\n * M41 (ADR-0050 D4) — `ChatTransport` over the in-process seam (`streamAgentTurnInProcess`), for the\n * terminal/desktop surfaces that run client + server in ONE process (no HTTP loopback).\n *\n * - `sendMessages`: bridge the injected runner's `AsyncGenerator<UIMessageChunk>` into a\n * `ReadableStream<UIMessageChunk>` (honoring `abortSignal`, which the runner forwards to the SDK).\n * - `reconnectToStream`: always `null` — a single process has no dropped server-side stream to resume\n * (mirrors `ai`'s `DirectChatTransport`).\n * - `approve`: resolve the pending inline approval by id (the run parks on `awaitApproval`). An unknown\n * id rejects (fail-fast, Rule 8 — never a silent resolve).\n *\n * Error asymmetry vs `HttpTransport` (by design, matching the `ChatTransport` contract): a runner that\n * throws SYNCHRONOUSLY surfaces the error when the stream is READ (via `controller.error`), not from the\n * `sendMessages` promise — whereas `HttpTransport` throws from `sendMessages` on a non-2xx response.\n */\nexport class InProcessTransport implements AgentTransport {\n readonly #run: InProcessRunner\n /** Pending inline approvals: approvalId → resolver of the parked `awaitApproval` promise. */\n readonly #pending = new Map<string, (decision: boolean | ApprovalDecision) => void>()\n\n constructor(options: InProcessTransportOptions) {\n this.#run = options.run\n }\n\n #awaitApproval: InProcessAwaitApproval = (req) =>\n new Promise<boolean | ApprovalDecision>((resolve, reject) => {\n // Approval ids are server-minted UUIDs — a collision means a real bug (two turns reusing an id).\n // Fail fast rather than silently overwrite the earlier turn's parked resolver (Rule 8).\n if (this.#pending.has(req.approvalId)) {\n reject(new Error(`Duplicate pending approval id '${req.approvalId}' — ids must be unique.`))\n return\n }\n this.#pending.set(req.approvalId, resolve)\n })\n\n sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, metadata } = options\n const generator = this.#run({\n message: extractLastUserText(messages),\n signal: abortSignal ?? undefined,\n awaitApproval: this.#awaitApproval,\n // M43 — forward per-request context (the seam's `metadata`) to the runner.\n context: metadata,\n })\n return Promise.resolve(generatorToStream(generator))\n }\n\n reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null> {\n return Promise.resolve(null)\n }\n\n approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n const resolve = this.#pending.get(approvalId)\n if (resolve === undefined) {\n return Promise.reject(\n new Error(`No pending approval '${approvalId}' (unknown or already settled).`),\n )\n }\n this.#pending.delete(approvalId)\n resolve(decision)\n return Promise.resolve()\n }\n}\n","import type { ChatTransport, UIMessage, UIMessageChunk } from 'ai'\n\nimport { extractLastUserText } from './last-user-text.js'\nimport type { AgentTransport, ApprovalDecision } from './transport.js'\n\n/** Handlers the transport hands to the injected push source for one turn. */\nexport interface ChannelTurnHandlers {\n /** One pushed JSONL line (a serialized `UIMessageChunk`). */\n onLine: (line: string) => void\n /** The turn ended — no more lines. */\n onClose: () => void\n /** The push source failed. */\n onError?: (err: unknown) => void\n}\n\n/**\n * The injected push source — a Tauri `Channel`/`invoke` bridge, kept STRUCTURAL so core adds no\n * `@tauri-apps/*` dependency and the transport is testable with a fake (ADR-0051 D2). The Tauri app\n * wires it: `new Channel()`, `channel.onmessage = onLine`, `invoke('run_agent', { message, channel })`,\n * returning a teardown that aborts the sidecar turn.\n */\nexport interface ChannelPushSource {\n /**\n * Start a turn; deliver each JSONL `UIMessageChunk` line to `onLine`, then `onClose`. Return a teardown.\n * `turn.context` (M43) is the per-request context (from the seam's `metadata`) — the Tauri `invoke`\n * forwards it to the sidecar. When no context is set it is present as `context: undefined` (the key is\n * NOT absent) — a sidecar that checks `'context' in turn` should treat `undefined` as \"no context\".\n */\n start(turn: { message: string; context?: unknown }, handlers: ChannelTurnHandlers): () => void\n /** Optional HITL settle (another Tauri `invoke`). */\n settle?(approvalId: string, decision: ApprovalDecision): Promise<void>\n}\n\nexport interface ChannelTransportOptions {\n source: ChannelPushSource\n}\n\n/**\n * M42 (ADR-0051) — `ChatTransport` over a Tauri-`Channel`-shaped push source, for the desktop webview.\n *\n * - `sendMessages`: start the turn via the injected source and bridge its pushed JSONL frames into a\n * `ReadableStream<UIMessageChunk>` (built in `start` — a Channel is push, so the stream's queue buffers\n * frames; ADR-0051 D3). A malformed JSONL line is SKIPPED, never fatal (ADR-0051 D4, Rule 8). `abortSignal`\n * tears down the source and closes the stream.\n * - `reconnectToStream`: always `null` — the M36 sidecar runs the turn directly (no durable server stream);\n * this is the honest parity for a single-process push surface (ADR-0051 D5; mirrors `InProcessTransport`).\n * - `approve`: routes to the injected `settle` (another Tauri `invoke`); absent `settle` → a typed error.\n *\n * The push source is INJECTED — core stays Tauri-agnostic and this transport is unit-tested with a fake.\n */\nexport class ChannelTransport implements AgentTransport {\n readonly #source: ChannelPushSource\n\n constructor(options: ChannelTransportOptions) {\n this.#source = options.source\n }\n\n sendMessages(\n options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0],\n ): Promise<ReadableStream<UIMessageChunk>> {\n const { messages, abortSignal, metadata } = options\n const message = extractLastUserText(messages)\n const source = this.#source\n\n // Per-stream teardown/abort-detach, hoisted so `cancel` (consumer stops reading) can also tear the\n // source down. `closed` makes every terminal transition idempotent (no enqueue/close after close).\n let closed = false\n let teardown: () => void = () => undefined\n let detachAbort: () => void = () => undefined\n\n const stream = new ReadableStream<UIMessageChunk>({\n start(controller) {\n const finish = (settle: () => void): void => {\n if (closed) return\n closed = true\n detachAbort()\n settle()\n }\n teardown = source.start(\n { message, context: metadata },\n {\n onLine: (line) => {\n if (closed) return\n // Skip a malformed pushed line — one bad frame must never crash the webview (ADR-0051 D4).\n let parsed: unknown\n try {\n parsed = JSON.parse(line)\n } catch {\n return\n }\n // Discriminant guard: a `UIMessageChunk` is an object with a string `type`. The trust\n // boundary here is the LOCAL sidecar (not the network), so a discriminant check — not the\n // full `ai` schema (which isn't exposed standalone) — is proportionate: it rejects\n // structureless / wrong-shape payloads before they reach `readUIMessageStream`. Same\n // skip-not-crash policy as a parse error.\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof (parsed as { type?: unknown }).type !== 'string'\n ) {\n return\n }\n controller.enqueue(parsed as UIMessageChunk)\n },\n onClose: () => {\n finish(() => {\n controller.close()\n })\n },\n onError: (err) => {\n finish(() => {\n controller.error(err)\n })\n },\n },\n )\n if (abortSignal !== undefined) {\n const onAbort = (): void => {\n finish(() => {\n teardown()\n controller.close()\n })\n }\n if (abortSignal.aborted) onAbort()\n else {\n abortSignal.addEventListener('abort', onAbort)\n detachAbort = () => {\n abortSignal.removeEventListener('abort', onAbort)\n }\n }\n }\n },\n cancel() {\n // The consumer stopped reading (reader.cancel) — abort the source turn + drop the abort listener.\n if (closed) return\n closed = true\n detachAbort()\n teardown()\n },\n })\n return Promise.resolve(stream)\n }\n\n reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null> {\n return Promise.resolve(null)\n }\n\n async approve(approvalId: string, decision: ApprovalDecision): Promise<void> {\n if (this.#source.settle === undefined) {\n throw new Error(`This channel source has no HITL settle — cannot approve '${approvalId}'.`)\n }\n await this.#source.settle(approvalId, decision)\n }\n}\n","import type { UIMessage, UIMessageChunk } from 'ai'\n\nimport { consumeChunkStream } from './consume-ui-message-stream.js'\nimport type { AgentTransport, ApprovalDecision, RequestContext } from './transport.js'\n\nexport type UseAgentStatus = 'idle' | 'streaming' | 'done' | 'error'\n\n/** The observable state the store exposes (stable reference between emits — `useSyncExternalStore` contract). */\nexport interface AgentClientState {\n /** The CURRENT turn's assistant messages (per-turn; reset each `send`). Back-compat — unchanged since M41. */\n messages: UIMessage[]\n /**\n * M46 — the full conversation: committed turns + the current turn's user message + in-flight assistant.\n * Accumulated across sends (never reset except by `reset()`), with stable ids committed exactly once.\n * Render this instead of hand-rolling a transcript from `messages`.\n */\n thread: UIMessage[]\n status: UseAgentStatus\n error: Error | undefined\n}\n\n/** Derive the turn text from a typed input: `input.message` when present, else the serialized input. */\nfunction inputToText(input: unknown): string {\n if (\n typeof input === 'object' &&\n input !== null &&\n typeof (input as { message?: unknown }).message === 'string'\n ) {\n return (input as { message: string }).message\n }\n if (typeof input === 'string') return input\n return JSON.stringify(input)\n}\n\n/** Build a user `UIMessage` from a typed input (text from `{ message }`, else the serialized input). */\nfunction buildUserMessage(input: unknown): UIMessage {\n return {\n id: crypto.randomUUID(),\n role: 'user',\n parts: [{ type: 'text', text: inputToText(input) }],\n }\n}\n\n/**\n * M41 (ADR-0050 D6) — the framework-agnostic agent client store.\n *\n * Holds `messages`/`status`/`error`, drives an {@link AgentTransport}, and notifies subscribers on\n * change. It is the SINGLE consolidation point: web (`HttpTransport`) and terminal/desktop\n * (`InProcessTransport`) run the SAME store. `useAgent` is a thin React binding over it via\n * `useSyncExternalStore`; a standalone (no-React) client (M44) can subscribe directly. Being\n * framework-agnostic, it is unit-tested without a DOM.\n */\nexport class AgentClient<TInput = unknown> {\n readonly #transport: AgentTransport\n readonly #chatId = crypto.randomUUID()\n readonly #listeners = new Set<() => void>()\n /** M43 — resolves per-request context (evaluated on every send/reconnect — dynamic, never stale). */\n readonly #contextResolver: (() => RequestContext | undefined) | undefined\n\n #messages: UIMessage[] = []\n #status: UseAgentStatus = 'idle'\n #error: Error | undefined\n #controller: AbortController | null = null\n // M46 — conversation accumulation (React-free; all surfaces inherit it via the snapshot).\n /** Committed (finished) turns — user + assistant, with stable fabricated ids. */\n #committed: UIMessage[] = []\n /** The current turn's user message (in `thread` but never in `messages` — back-compat). */\n #currentUser: UIMessage | undefined\n /** A stable id for the current turn's assistant (the SDK leaves it empty — we fabricate one). */\n #currentAssistantId = ''\n #snapshot: AgentClientState = { messages: [], thread: [], status: 'idle', error: undefined }\n\n constructor(transport: AgentTransport, contextResolver?: () => RequestContext | undefined) {\n this.#transport = transport\n this.#contextResolver = contextResolver\n }\n\n /** Subscribe to state changes; returns an unsubscribe fn. */\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n /** The current immutable snapshot (stable reference until the next emit). */\n getSnapshot = (): AgentClientState => this.#snapshot\n\n #emit(): void {\n // thread = committed turns + this turn's user + the in-flight assistant (`messages`). New array per\n // emit is fine — the SNAPSHOT reference only changes here, satisfying useSyncExternalStore.\n const thread = this.#currentUser\n ? [...this.#committed, this.#currentUser, ...this.#messages]\n : [...this.#committed, ...this.#messages]\n this.#snapshot = { messages: this.#messages, thread, status: this.#status, error: this.#error }\n for (const listener of this.#listeners) listener()\n }\n\n #upsert(message: UIMessage): void {\n const next = [...this.#messages]\n const idx = next.findIndex((existing) => existing.id === message.id)\n if (idx >= 0) next[idx] = message\n else next.push(message)\n this.#messages = next\n }\n\n async #drive(\n open: () => Promise<ReadableStream<UIMessageChunk> | null>,\n controller: AbortController,\n ): Promise<void> {\n // Read via a function (not a narrowed local) — `aborted` flips ASYNC across the awaits below, so the\n // control-flow narrowing of a direct `signal.aborted` read would be wrong. A stale drive (its\n // controller aborted because a newer send/abort took over) MUST NOT clobber the newer status.\n const aborted = (): boolean => controller.signal.aborted\n try {\n const stream = await open()\n if (aborted()) return\n if (stream === null) {\n // Nothing to resume (e.g. reconnect after the run completed). Settle without error.\n this.#status = this.#messages.length > 0 ? 'done' : 'idle'\n this.#emit()\n return\n }\n await consumeChunkStream(stream, (message) => {\n if (aborted()) return\n // The SDK leaves the assistant message id empty — fabricate a stable per-turn id so every chunk\n // upserts into the SAME message and the committed copy has a collision-free key (M46).\n const stamped = message.id ? message : { ...message, id: this.#currentAssistantId }\n this.#upsert(stamped)\n this.#emit()\n })\n if (aborted()) return\n this.#status = 'done'\n this.#emit()\n } catch (err) {\n if (aborted()) return\n this.#error = err instanceof Error ? err : new Error(String(err))\n this.#status = 'error'\n this.#emit()\n }\n }\n\n /** Send a typed input; opens a fresh stream (replaces prior messages). */\n send = (input: TInput): void => {\n // M46 — commit the PRIOR turn into history exactly once, but ONLY if it finished cleanly (`done`).\n // An errored or aborted turn (status !== 'done') is dropped, keeping committed history uncorrupted.\n if (this.#status === 'done' && this.#currentUser) {\n this.#committed = [...this.#committed, this.#currentUser, ...this.#messages]\n }\n this.abort()\n const controller = new AbortController()\n this.#controller = controller\n const userMsg = buildUserMessage(input)\n this.#currentUser = userMsg\n this.#currentAssistantId = crypto.randomUUID()\n this.#messages = []\n this.#error = undefined\n this.#status = 'streaming'\n this.#emit()\n const context = this.#contextResolver?.()\n void this.#drive(\n () =>\n this.#transport.sendMessages({\n trigger: 'submit-message',\n chatId: this.#chatId,\n messageId: undefined,\n messages: [userMsg],\n abortSignal: controller.signal,\n // Only object inputs flow as the request `body` (the turn text is always in `messages`);\n // a primitive input is carried by the user message, never spread into the body.\n body: typeof input === 'object' && input !== null ? input : undefined,\n // M43 — per-request context reaches every transport (headers → HTTP, metadata → in-process/channel).\n headers: context?.headers,\n metadata: context?.metadata,\n }),\n controller,\n )\n }\n\n /** Resume an interrupted stream via the transport's `reconnectToStream` (no-op when unavailable). */\n reconnect = (): void => {\n const controller = new AbortController()\n this.#controller = controller\n // Reconnecting before any send() (or after reset()) leaves #currentAssistantId empty — fabricate one\n // so a replayed assistant never lands in `thread` with an empty, non-unique id (M46 invariant).\n if (!this.#currentAssistantId) this.#currentAssistantId = crypto.randomUUID()\n // Reconnect means \"resume/retry\" — a stale error must not linger next to a fresh 'streaming' status.\n this.#error = undefined\n this.#status = 'streaming'\n this.#emit()\n const context = this.#contextResolver?.()\n void this.#drive(\n () =>\n this.#transport.reconnectToStream({\n chatId: this.#chatId,\n headers: context?.headers,\n metadata: context?.metadata,\n }),\n controller,\n )\n }\n\n /** Abort an in-flight stream (not an error — leaves messages as-is). */\n abort = (): void => {\n this.#controller?.abort()\n this.#controller = null\n // Finalize the status when the USER aborts an in-flight turn: the aborted `#drive` early-returns\n // without touching status (so a stale drive can't clobber a newer turn), which would otherwise leave\n // `status` stuck on 'streaming' — a lingering spinner + an unusable surface. A caller that aborts to\n // start a NEW turn (`send`/`sendMessages`) sets 'streaming' again immediately after, so this is safe.\n if (this.#status === 'streaming') {\n this.#status = this.#committed.length > 0 || this.#messages.length > 0 ? 'done' : 'idle'\n this.#emit()\n }\n }\n\n /** Clear messages + error, back to idle. */\n reset = (): void => {\n this.abort()\n this.#messages = []\n // M46 — reset means a NEW conversation: clear committed history + the current turn's user too.\n this.#committed = []\n this.#currentUser = undefined\n this.#error = undefined\n this.#status = 'idle'\n this.#emit()\n }\n\n /** Settle a paused HITL approval via the transport's HITL path (HTTP POST or inline callback). */\n approve = async (approvalId: string, decision: ApprovalDecision): Promise<void> => {\n await this.#transport.approve?.(approvalId, decision)\n }\n}\n","import { ChannelTransport, type ChannelPushSource } from './channel-transport.js'\nimport { InProcessTransport, type InProcessRunner } from './in-process-transport.js'\n\n/**\n * M47 (ADR-M47-2) — a typed, client-safe handle for an exposed agent.\n *\n * It carries ONLY the HTTP `path` at runtime plus phantom `input`/`toolNames` types (never populated) — so\n * `useAgent(chat)` / `createAgentClient(chat…)` bind with NO magic string (the path is generated from the\n * `@Expose` exposure, not hand-typed) and NO duplicated input type (the input type flows through the phantom\n * generic, inferred from the agent's `.input()`). This mirrors tRPC/Hono's type-only handle: the client\n * pulls the agent's TYPE via `import type`, never its server runtime. The generated `@theo/agents` module\n * emits one `export const <name> = agentHandle('/api/agents/<name>')` per agent, typed with the phantoms.\n */\nexport interface AgentHandle<TInput = unknown, TToolNames extends string = string> {\n /** The agent's HTTP endpoint path (e.g. `/api/agents/chat`). The only serializable/runtime-bearing field. */\n readonly path: string\n /**\n * M47 — bind this agent in-process (TUI / single-process): wraps the app's runner in an\n * {@link InProcessTransport}. `useAgent(chat.inProcess(run))` drives the SAME agent without HTTP.\n */\n inProcess(run: InProcessRunner): InProcessTransport\n /**\n * M47 — bind this agent over a push channel (Tauri desktop webview): wraps the source in a\n * {@link ChannelTransport}. `createAgentClient(chat.channel(source))` drives the SAME agent.\n */\n channel(source: ChannelPushSource): ChannelTransport\n /** Phantom — the agent's `input` type, carried for `useAgent(handle).send` inference. Never populated. */\n readonly __input?: TInput\n /** Phantom — the agent's tool-name union, carried end-to-end. Never populated. */\n readonly __toolNames?: TToolNames\n}\n\n/**\n * Build an {@link AgentHandle} from an agent's HTTP path. Types are supplied by the caller/codegen. The\n * `inProcess`/`channel` binders are methods (dropped by `JSON.stringify`, so the `{ path }` core stays\n * serializable + client-safe) that produce the M41 transports for the non-web surfaces.\n */\nexport function agentHandle<TInput = unknown, TToolNames extends string = string>(\n path: string,\n): AgentHandle<TInput, TToolNames> {\n return {\n path,\n inProcess: (run) => new InProcessTransport({ run }),\n channel: (source) => new ChannelTransport({ source }),\n }\n}\n\n/** Narrow an unknown binding to an {@link AgentHandle} (has a string `path`, is not a transport). */\nexport function isAgentHandle(value: unknown): value is AgentHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { path?: unknown }).path === 'string' &&\n typeof (value as { sendMessages?: unknown }).sendMessages !== 'function'\n )\n}\n"],"mappings":"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","names":["consumeUIMessageStream","response","onMessage","chunkStream","responseToChunkStream","consumeChunkStream","body","ReadableStream","start","controller","close","parseJsonEventStream","uiMessageChunkSchema","parsed","stream","schema","result","success","enqueue","value","readUIMessageStream","streamError","message","onError","err","Error","String","terminateOnError","undefined","toRecord","headers","undefined","Headers","Object","fromEntries","entries","HttpTransport","options","api","fetch","globalThis","bind","sendMessages","messages","abortSignal","body","chatId","extra","response","method","accept","JSON","stringify","id","signal","ok","Error","status","statusText","get","responseToChunkStream","reconnectToStream","approve","approvalId","decision","extractLastUserText","messages","i","length","message","role","text","parts","filter","part","type","map","join","generatorToStream","gen","ReadableStream","pull","controller","result","next","done","close","enqueue","value","err","error","cancel","return","undefined","InProcessTransport","Map","options","run","req","Promise","resolve","reject","has","approvalId","Error","set","sendMessages","messages","abortSignal","metadata","generator","message","extractLastUserText","signal","awaitApproval","context","reconnectToStream","approve","decision","get","delete","ChannelTransport","options","source","sendMessages","messages","abortSignal","metadata","message","extractLastUserText","closed","teardown","undefined","detachAbort","stream","ReadableStream","start","controller","finish","settle","context","onLine","line","parsed","JSON","parse","type","enqueue","onClose","close","onError","err","error","onAbort","aborted","addEventListener","removeEventListener","cancel","Promise","resolve","reconnectToStream","approve","approvalId","decision","Error","inputToText","input","message","JSON","stringify","buildUserMessage","id","crypto","randomUUID","role","parts","type","text","AgentClient","Set","messages","thread","status","error","undefined","transport","contextResolver","subscribe","listener","add","delete","getSnapshot","next","idx","findIndex","existing","push","open","controller","aborted","signal","stream","length","consumeChunkStream","stamped","err","Error","String","send","abort","AbortController","userMsg","context","sendMessages","trigger","chatId","messageId","abortSignal","body","headers","metadata","reconnect","reconnectToStream","reset","approve","approvalId","decision","agentHandle","path","inProcess","run","InProcessTransport","channel","source","ChannelTransport","isAgentHandle","value","sendMessages"]}