agents 0.11.5 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { n as SEARCH_DESCRIPTION, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-BovR6hRc.js";
1
+ import { n as SEARCH_DESCRIPTION, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-mfBbxjS1.js";
2
2
  import { z } from "zod";
3
3
  import { tool } from "ai";
4
4
  //#region src/browser/ai.ts
@@ -1,2 +1,2 @@
1
- import { a as connectBrowser, i as CdpSession, n as SEARCH_DESCRIPTION, o as connectUrl, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-BovR6hRc.js";
1
+ import { a as connectBrowser, i as CdpSession, n as SEARCH_DESCRIPTION, o as connectUrl, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-mfBbxjS1.js";
2
2
  export { CdpSession, EXECUTE_DESCRIPTION, SEARCH_DESCRIPTION, connectBrowser, connectUrl, createBrowserToolHandlers };
@@ -1,4 +1,4 @@
1
- import { n as SEARCH_DESCRIPTION, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-BovR6hRc.js";
1
+ import { n as SEARCH_DESCRIPTION, r as createBrowserToolHandlers, t as EXECUTE_DESCRIPTION } from "../shared-mfBbxjS1.js";
2
2
  import { z } from "zod";
3
3
  import { toolDefinition } from "@tanstack/ai";
4
4
  //#region src/browser/tanstack-ai.ts
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-ai.js","names":[],"sources":["../../src/browser/tanstack-ai.ts"],"sourcesContent":["import { toolDefinition } from \"@tanstack/ai\";\nimport type { ServerTool } from \"@tanstack/ai\";\nimport { z } from \"zod\";\nimport {\n createBrowserToolHandlers,\n SEARCH_DESCRIPTION,\n EXECUTE_DESCRIPTION,\n type BrowserToolsOptions\n} from \"./shared\";\n\nexport type { BrowserToolsOptions } from \"./shared\";\n\n/**\n * Create TanStack AI tools for browser automation via CDP code mode.\n *\n * Returns an array of `ServerTool`s: `browser_search` (query the CDP spec)\n * and `browser_execute` (run CDP commands against a live browser).\n *\n * @example\n * ```ts\n * import { createBrowserTools } from \"agents/browser/tanstack-ai\";\n * import { chat } from \"@tanstack/ai\";\n *\n * const browserTools = createBrowserTools({\n * browser: env.BROWSER,\n * loader: env.LOADER,\n * });\n *\n * const stream = chat({\n * adapter: openaiText(\"gpt-4o\"),\n * tools: [...browserTools, ...otherTools],\n * messages,\n * });\n * ```\n */\nexport function createBrowserTools(options: BrowserToolsOptions): ServerTool[] {\n const handlers = createBrowserToolHandlers(options);\n\n const search = toolDefinition({\n name: \"browser_search\" as const,\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n code: z.string().meta({\n description: \"JavaScript async arrow function that queries the CDP spec\"\n })\n })\n }).server(async ({ code }) => {\n const result = await handlers.search(code);\n if (result.isError) {\n throw new Error(result.text);\n }\n return { text: result.text };\n });\n\n const execute = toolDefinition({\n name: \"browser_execute\" as const,\n description: EXECUTE_DESCRIPTION,\n inputSchema: z.object({\n code: z.string().meta({\n description: \"JavaScript async arrow function that uses the cdp helper\"\n })\n })\n }).server(async ({ code }) => {\n const result = await handlers.execute(code);\n if (result.isError) {\n throw new Error(result.text);\n }\n return { text: result.text };\n });\n\n return [search, execute];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,mBAAmB,SAA4C;CAC7E,MAAM,WAAW,0BAA0B,QAAQ;AAkCnD,QAAO,CAhCQ,eAAe;EAC5B,MAAM;EACN,aAAa;EACb,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,6DACd,CAAC,EACH,CAAC;EACH,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,MAAM,SAAS,MAAM,SAAS,OAAO,KAAK;AAC1C,MAAI,OAAO,QACT,OAAM,IAAI,MAAM,OAAO,KAAK;AAE9B,SAAO,EAAE,MAAM,OAAO,MAAM;GAC5B,EAEc,eAAe;EAC7B,MAAM;EACN,aAAa;EACb,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,4DACd,CAAC,EACH,CAAC;EACH,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,MAAM,SAAS,MAAM,SAAS,QAAQ,KAAK;AAC3C,MAAI,OAAO,QACT,OAAM,IAAI,MAAM,OAAO,KAAK;AAE9B,SAAO,EAAE,MAAM,OAAO,MAAM;GAC5B,CAEsB"}
1
+ {"version":3,"file":"tanstack-ai.js","names":[],"sources":["../../src/browser/tanstack-ai.ts"],"sourcesContent":["import { toolDefinition } from \"@tanstack/ai\";\nimport type { ServerTool } from \"@tanstack/ai\";\nimport { z } from \"zod\";\nimport {\n createBrowserToolHandlers,\n SEARCH_DESCRIPTION,\n EXECUTE_DESCRIPTION,\n type BrowserToolsOptions\n} from \"./shared\";\n\nexport type { BrowserToolsOptions } from \"./shared\";\n\n/**\n * Create TanStack AI tools for browser automation via CDP code mode.\n *\n * Returns an array of `ServerTool`s: `browser_search` (query the CDP spec)\n * and `browser_execute` (run CDP commands against a live browser).\n *\n * @example\n * ```ts\n * import { createBrowserTools } from \"agents/browser/tanstack-ai\";\n * import { chat } from \"@tanstack/ai\";\n *\n * const browserTools = createBrowserTools({\n * browser: env.BROWSER,\n * loader: env.LOADER,\n * });\n *\n * const stream = chat({\n * adapter: openaiText(\"gpt-4o\"),\n * tools: [...browserTools, ...otherTools],\n * messages,\n * });\n * ```\n */\nexport function createBrowserTools(options: BrowserToolsOptions): ServerTool[] {\n const handlers = createBrowserToolHandlers(options);\n\n const search = toolDefinition({\n name: \"browser_search\" as const,\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n code: z.string().meta({\n description: \"JavaScript async arrow function that queries the CDP spec\"\n })\n })\n }).server(async ({ code }) => {\n const result = await handlers.search(code);\n if (result.isError) {\n throw new Error(result.text);\n }\n return { text: result.text };\n });\n\n const execute = toolDefinition({\n name: \"browser_execute\" as const,\n description: EXECUTE_DESCRIPTION,\n inputSchema: z.object({\n code: z.string().meta({\n description: \"JavaScript async arrow function that uses the cdp helper\"\n })\n })\n }).server(async ({ code }) => {\n const result = await handlers.execute(code);\n if (result.isError) {\n throw new Error(result.text);\n }\n return { text: result.text };\n });\n\n return [search, execute];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,mBAAmB,SAA4C;CAC7E,MAAM,WAAW,0BAA0B,QAAQ;AAkCnD,QAAO,CAhCQ,eAAe;EAC5B,MAAM;EACN,aAAa;EACb,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,6DACd,CAAC,EACH,CAAC;EACH,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,MAAM,SAAS,MAAM,SAAS,OAAO,KAAK;AAC1C,MAAI,OAAO,QACT,OAAM,IAAI,MAAM,OAAO,KAAK;AAE9B,SAAO,EAAE,MAAM,OAAO,MAAM;GAmBhB,EAhBE,eAAe;EAC7B,MAAM;EACN,aAAa;EACb,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,4DACd,CAAC,EACH,CAAC;EACH,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,MAAM,SAAS,MAAM,SAAS,QAAQ,KAAK;AAC3C,MAAI,OAAO,QACT,OAAM,IAAI,MAAM,OAAO,KAAK;AAE9B,SAAO,EAAE,MAAM,OAAO,MAAM;GAGP,CAAC"}
@@ -188,6 +188,147 @@ declare class TurnQueue {
188
188
  private _decrementCount;
189
189
  }
190
190
  //#endregion
191
+ //#region src/chat/client-tools.d.ts
192
+ /**
193
+ * Wire-format tool schema sent from the client.
194
+ * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`
195
+ * because Zod schemas cannot be serialized over the wire.
196
+ */
197
+ type ClientToolSchema = {
198
+ /** Unique name for the tool */name: string; /** Human-readable description of what the tool does */
199
+ description?: Tool["description"]; /** JSON Schema defining the tool's input parameters */
200
+ parameters?: JSONSchema7;
201
+ };
202
+ /**
203
+ * Converts client tool schemas to AI SDK tool format.
204
+ *
205
+ * These tools have no `execute` function — when the AI model calls them,
206
+ * the tool call is sent back to the client for execution.
207
+ *
208
+ * @param clientTools - Array of tool schemas from the client
209
+ * @returns Record of AI SDK tools that can be spread into your tools object
210
+ */
211
+ declare function createToolsFromClientSchemas(clientTools?: ClientToolSchema[]): ToolSet;
212
+ //#endregion
213
+ //#region src/chat/lifecycle.d.ts
214
+ /**
215
+ * Result passed to the `onChatResponse` lifecycle hook after a chat
216
+ * turn completes.
217
+ */
218
+ type ChatResponseResult = {
219
+ /** The finalized assistant message from this turn. */message: UIMessage; /** The request ID associated with this turn. */
220
+ requestId: string; /** Whether this turn was a continuation of a previous assistant turn. */
221
+ continuation: boolean; /** How the turn ended. */
222
+ status: "completed" | "error" | "aborted"; /** Error message when `status` is `"error"`. */
223
+ error?: string;
224
+ };
225
+ /**
226
+ * Result returned by programmatic entry points that may skip execution
227
+ * when the chat state has been invalidated mid-flight (e.g. by a
228
+ * `CHAT_CLEAR` protocol message).
229
+ */
230
+ type SaveMessagesResult = {
231
+ /** Server-generated request ID for the chat turn. */requestId: string; /** Whether the turn ran or was skipped (e.g. because the chat was cleared). */
232
+ status: "completed" | "skipped";
233
+ };
234
+ /**
235
+ * Context passed to the `onChatRecovery` hook when an interrupted chat
236
+ * stream is detected after DO restart.
237
+ */
238
+ type ChatRecoveryContext = {
239
+ /** Stream ID from the interrupted stream. */streamId: string; /** Request ID from the interrupted stream. */
240
+ requestId: string; /** Partial text extracted from stored chunks. */
241
+ partialText: string; /** Partial message parts reconstructed from chunks. */
242
+ partialParts: MessagePart[]; /** Checkpoint data from `this.stash()` during the interrupted stream. */
243
+ recoveryData: unknown | null; /** Current persisted messages. */
244
+ messages: UIMessage[]; /** Custom body from the last chat request. */
245
+ lastBody?: Record<string, unknown>; /** Client tool schemas from the last chat request. */
246
+ lastClientTools?: ClientToolSchema[];
247
+ /**
248
+ * Epoch milliseconds when the underlying fiber was started. Compare
249
+ * against `Date.now()` to suppress continuations for turns that have
250
+ * been orphaned too long to safely replay.
251
+ */
252
+ createdAt: number;
253
+ };
254
+ /**
255
+ * Options returned from `onChatRecovery` to control recovery behavior.
256
+ */
257
+ type ChatRecoveryOptions = {
258
+ /** Save the partial response from stored chunks. Default: true. */persist?: boolean; /** Schedule a continuation via `continueLastTurn()`. Default: true. */
259
+ continue?: boolean;
260
+ };
261
+ /**
262
+ * Controls how overlapping user submit requests behave while another
263
+ * chat turn is already active or queued.
264
+ *
265
+ * - `"queue"` (default) — queue every submit and process them in order.
266
+ * - `"latest"` — keep only the latest overlapping submit; superseded
267
+ * submits still persist their user messages, but do not start their
268
+ * own model turn.
269
+ * - `"merge"` — coalesce overlapping submits into one model turn while
270
+ * preserving the submitted user content. Exact persistence depends on
271
+ * the chat package's message model.
272
+ * - `"drop"` — ignore overlapping submits entirely (messages not
273
+ * persisted).
274
+ * - `{ strategy: "debounce", debounceMs? }` — trailing-edge latest with
275
+ * a quiet window.
276
+ *
277
+ * Only applies to `submit-message` requests. Regenerations, tool
278
+ * continuations, approvals, clears, programmatic `saveMessages`, and
279
+ * `continueLastTurn` keep their existing serialized behavior.
280
+ */
281
+ type MessageConcurrency = "queue" | "latest" | "merge" | "drop" | {
282
+ strategy: "debounce";
283
+ debounceMs?: number;
284
+ };
285
+ //#endregion
286
+ //#region src/chat/submit-concurrency.d.ts
287
+ type NormalizedMessageConcurrency = "queue" | "latest" | "merge" | "drop" | {
288
+ strategy: "debounce";
289
+ debounceMs: number;
290
+ };
291
+ type SubmitConcurrencyDecision = {
292
+ action: "execute" | "drop";
293
+ strategy: NormalizedMessageConcurrency | null;
294
+ submitSequence: number | null;
295
+ debounceUntilMs: number | null;
296
+ };
297
+ declare class SubmitConcurrencyController {
298
+ private readonly options;
299
+ private _submitSequence;
300
+ private _latestOverlappingSubmitSequence;
301
+ private _pendingEnqueueCount;
302
+ private _resetEpoch;
303
+ private _activeDebounceTimers;
304
+ private _activeDebounceResolves;
305
+ constructor(options: {
306
+ defaultDebounceMs: number;
307
+ });
308
+ get pendingEnqueueCount(): number;
309
+ get overlappingSubmitCount(): number;
310
+ decide(options: {
311
+ concurrency: MessageConcurrency;
312
+ isSubmitMessage: boolean;
313
+ queuedTurns: number;
314
+ }): SubmitConcurrencyDecision;
315
+ /**
316
+ * Mark a submit as accepted and in-flight between admission and turn
317
+ * queue registration. Returns an idempotent `release()` function that
318
+ * must be called when the submit either reaches the turn queue or is
319
+ * abandoned. The returned function is bound to the controller's reset
320
+ * epoch — releases from before the most recent `reset()` are no-ops,
321
+ * so post-reset submits keep an accurate count.
322
+ */
323
+ beginEnqueue(): () => void;
324
+ isSuperseded(submitSequence: number | null): boolean;
325
+ waitForTimestamp(timestampMs: number): Promise<void>;
326
+ cancelActiveDebounce(): void;
327
+ reset(): void;
328
+ waitForIdle(waitForQueueIdle: () => Promise<void>): Promise<void>;
329
+ private normalize;
330
+ }
331
+ //#endregion
191
332
  //#region src/chat/broadcast-state.d.ts
192
333
  type BroadcastStreamState = {
193
334
  status: "idle";
@@ -341,28 +482,6 @@ declare class ResumableStream {
341
482
  insertStaleStream(streamId: string, requestId: string, ageMs: number): void;
342
483
  }
343
484
  //#endregion
344
- //#region src/chat/client-tools.d.ts
345
- /**
346
- * Wire-format tool schema sent from the client.
347
- * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`
348
- * because Zod schemas cannot be serialized over the wire.
349
- */
350
- type ClientToolSchema = {
351
- /** Unique name for the tool */name: string; /** Human-readable description of what the tool does */
352
- description?: Tool["description"]; /** JSON Schema defining the tool's input parameters */
353
- parameters?: JSONSchema7;
354
- };
355
- /**
356
- * Converts client tool schemas to AI SDK tool format.
357
- *
358
- * These tools have no `execute` function — when the AI model calls them,
359
- * the tool call is sent back to the client for execution.
360
- *
361
- * @param clientTools - Array of tool schemas from the client
362
- * @returns Record of AI SDK tools that can be spread into your tools object
363
- */
364
- declare function createToolsFromClientSchemas(clientTools?: ClientToolSchema[]): ToolSet;
365
- //#endregion
366
485
  //#region src/chat/protocol.d.ts
367
486
  /**
368
487
  * Wire protocol message type constants for the cf_agent_chat_* protocol.
@@ -599,77 +718,32 @@ type ChatProtocolEvent = {
599
718
  */
600
719
  declare function parseProtocolMessage(raw: string): ChatProtocolEvent | null;
601
720
  //#endregion
602
- //#region src/chat/lifecycle.d.ts
721
+ //#region src/chat/message-reconciler.d.ts
603
722
  /**
604
- * Result passed to the `onChatResponse` lifecycle hook after a chat
605
- * turn completes.
606
- */
607
- type ChatResponseResult = {
608
- /** The finalized assistant message from this turn. */message: UIMessage; /** The request ID associated with this turn. */
609
- requestId: string; /** Whether this turn was a continuation of a previous assistant turn. */
610
- continuation: boolean; /** How the turn ended. */
611
- status: "completed" | "error" | "aborted"; /** Error message when `status` is `"error"`. */
612
- error?: string;
613
- };
614
- /**
615
- * Result returned by programmatic entry points that may skip execution
616
- * when the chat state has been invalidated mid-flight (e.g. by a
617
- * `CHAT_CLEAR` protocol message).
618
- */
619
- type SaveMessagesResult = {
620
- /** Server-generated request ID for the chat turn. */requestId: string; /** Whether the turn ran or was skipped (e.g. because the chat was cleared). */
621
- status: "completed" | "skipped";
622
- };
623
- /**
624
- * Context passed to the `onChatRecovery` hook when an interrupted chat
625
- * stream is detected after DO restart.
723
+ * Reconcile incoming client messages against server state.
724
+ *
725
+ * 1. Merges server-known tool outputs into incoming messages that still
726
+ * show stale states (input-available, approval-requested, approval-responded)
727
+ * 2. Reconciles assistant IDs: exact match content-key match toolCallId match
728
+ *
729
+ * @param incoming - Messages from the client
730
+ * @param serverMessages - Current server-side messages (source of truth)
731
+ * @param sanitizeForContentKey - Function to sanitize a message before computing
732
+ * its content key (typically strips ephemeral provider metadata)
733
+ * @returns Reconciled messages ready for persistence
626
734
  */
627
- type ChatRecoveryContext = {
628
- /** Stream ID from the interrupted stream. */streamId: string; /** Request ID from the interrupted stream. */
629
- requestId: string; /** Partial text extracted from stored chunks. */
630
- partialText: string; /** Partial message parts reconstructed from chunks. */
631
- partialParts: MessagePart[]; /** Checkpoint data from `this.stash()` during the interrupted stream. */
632
- recoveryData: unknown | null; /** Current persisted messages. */
633
- messages: UIMessage[]; /** Custom body from the last chat request. */
634
- lastBody?: Record<string, unknown>; /** Client tool schemas from the last chat request. */
635
- lastClientTools?: ClientToolSchema[];
636
- /**
637
- * Epoch milliseconds when the underlying fiber was started. Compare
638
- * against `Date.now()` to suppress continuations for turns that have
639
- * been orphaned too long to safely replay.
640
- */
641
- createdAt: number;
642
- };
735
+ declare function reconcileMessages(incoming: UIMessage[], serverMessages: readonly UIMessage[], sanitizeForContentKey?: (message: UIMessage) => UIMessage): UIMessage[];
643
736
  /**
644
- * Options returned from `onChatRecovery` to control recovery behavior.
737
+ * For a single message, resolve its ID by matching toolCallId against server state.
738
+ * Prevents duplicate DB rows when client IDs differ from server IDs.
739
+ * Tool call IDs are unique per conversation, so matching is safe regardless of state.
645
740
  */
646
- type ChatRecoveryOptions = {
647
- /** Save the partial response from stored chunks. Default: true. */persist?: boolean; /** Schedule a continuation via `continueLastTurn()`. Default: true. */
648
- continue?: boolean;
649
- };
741
+ declare function resolveToolMergeId(message: UIMessage, serverMessages: readonly UIMessage[]): UIMessage;
650
742
  /**
651
- * Controls how overlapping user submit requests behave while another
652
- * chat turn is already active or queued.
653
- *
654
- * - `"queue"` (default) — queue every submit and process them in order.
655
- * - `"latest"` — keep only the latest overlapping submit; superseded
656
- * submits still persist their user messages, but do not start their
657
- * own model turn.
658
- * - `"merge"` — like `latest`, but all overlapping user messages remain
659
- * in the conversation history. The model sees them all in one turn.
660
- * - `"drop"` — ignore overlapping submits entirely (messages not
661
- * persisted).
662
- * - `{ strategy: "debounce", debounceMs? }` — trailing-edge latest with
663
- * a quiet window.
664
- *
665
- * Only applies to `submit-message` requests. Regenerations, tool
666
- * continuations, approvals, clears, programmatic `saveMessages`, and
667
- * `continueLastTurn` keep their existing serialized behavior.
743
+ * Content key for assistant messages used for dedup of identical short replies.
744
+ * Returns JSON of sanitized parts, or undefined for non-assistant messages.
668
745
  */
669
- type MessageConcurrency = "queue" | "latest" | "merge" | "drop" | {
670
- strategy: "debounce";
671
- debounceMs?: number;
672
- };
746
+ declare function assistantContentKey(message: UIMessage, sanitize?: (message: UIMessage) => UIMessage): string | undefined;
673
747
  //#endregion
674
- export { AbortRegistry, type BroadcastStreamEvent, type BroadcastStreamState, type TransitionResult as BroadcastTransitionResult, CHAT_MESSAGE_TYPES, type ChatProtocolEvent, type ChatRecoveryContext, type ChatRecoveryOptions, type ChatResponseResult, type ChunkAction, type ChunkResult, type ClientToolSchema, type ContinuationConnection, type ContinuationDeferred, type ContinuationPending, ContinuationState, type EnqueueOptions, type MessageConcurrency, type MessagePart, type MessageParts, ROW_MAX_BYTES, ResumableStream, type SaveMessagesResult, type SqlTaggedTemplate, StreamAccumulator, type StreamAccumulatorOptions, type StreamChunkData, type ToolPartUpdate, TurnQueue, type TurnResult, applyChunkToParts, applyToolUpdate, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
748
+ export { AbortRegistry, type BroadcastStreamEvent, type BroadcastStreamState, type TransitionResult as BroadcastTransitionResult, CHAT_MESSAGE_TYPES, type ChatProtocolEvent, type ChatRecoveryContext, type ChatRecoveryOptions, type ChatResponseResult, type ChunkAction, type ChunkResult, type ClientToolSchema, type ContinuationConnection, type ContinuationDeferred, type ContinuationPending, ContinuationState, type EnqueueOptions, type MessageConcurrency, type MessagePart, type MessageParts, type NormalizedMessageConcurrency, ROW_MAX_BYTES, ResumableStream, type SaveMessagesResult, type SqlTaggedTemplate, StreamAccumulator, type StreamAccumulatorOptions, type StreamChunkData, SubmitConcurrencyController, type SubmitConcurrencyDecision, type ToolPartUpdate, TurnQueue, type TurnResult, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
675
749
  //# sourceMappingURL=index.d.ts.map
@@ -574,6 +574,126 @@ var TurnQueue = class {
574
574
  }
575
575
  };
576
576
  //#endregion
577
+ //#region src/chat/submit-concurrency.ts
578
+ var SubmitConcurrencyController = class {
579
+ constructor(options) {
580
+ this.options = options;
581
+ this._submitSequence = 0;
582
+ this._latestOverlappingSubmitSequence = 0;
583
+ this._pendingEnqueueCount = 0;
584
+ this._resetEpoch = 0;
585
+ this._activeDebounceTimers = /* @__PURE__ */ new Set();
586
+ this._activeDebounceResolves = /* @__PURE__ */ new Set();
587
+ }
588
+ get pendingEnqueueCount() {
589
+ return this._pendingEnqueueCount;
590
+ }
591
+ get overlappingSubmitCount() {
592
+ return this._latestOverlappingSubmitSequence;
593
+ }
594
+ decide(options) {
595
+ const queuedTurnsInCurrentEpoch = options.queuedTurns + this._pendingEnqueueCount;
596
+ if (!options.isSubmitMessage || queuedTurnsInCurrentEpoch === 0) return {
597
+ action: "execute",
598
+ strategy: null,
599
+ submitSequence: null,
600
+ debounceUntilMs: null
601
+ };
602
+ const concurrency = this.normalize(options.concurrency);
603
+ if (concurrency === "drop") return {
604
+ action: "drop",
605
+ strategy: concurrency,
606
+ submitSequence: null,
607
+ debounceUntilMs: null
608
+ };
609
+ if (concurrency === "queue") return {
610
+ action: "execute",
611
+ strategy: concurrency,
612
+ submitSequence: null,
613
+ debounceUntilMs: null
614
+ };
615
+ const submitSequence = ++this._submitSequence;
616
+ this._latestOverlappingSubmitSequence = submitSequence;
617
+ if (concurrency === "latest" || concurrency === "merge") return {
618
+ action: "execute",
619
+ strategy: concurrency,
620
+ submitSequence,
621
+ debounceUntilMs: null
622
+ };
623
+ return {
624
+ action: "execute",
625
+ strategy: concurrency,
626
+ submitSequence,
627
+ debounceUntilMs: Date.now() + concurrency.debounceMs
628
+ };
629
+ }
630
+ /**
631
+ * Mark a submit as accepted and in-flight between admission and turn
632
+ * queue registration. Returns an idempotent `release()` function that
633
+ * must be called when the submit either reaches the turn queue or is
634
+ * abandoned. The returned function is bound to the controller's reset
635
+ * epoch — releases from before the most recent `reset()` are no-ops,
636
+ * so post-reset submits keep an accurate count.
637
+ */
638
+ beginEnqueue() {
639
+ this._pendingEnqueueCount++;
640
+ const epoch = this._resetEpoch;
641
+ let released = false;
642
+ return () => {
643
+ if (released) return;
644
+ released = true;
645
+ if (this._resetEpoch !== epoch) return;
646
+ this._pendingEnqueueCount = Math.max(0, this._pendingEnqueueCount - 1);
647
+ };
648
+ }
649
+ isSuperseded(submitSequence) {
650
+ return submitSequence !== null && submitSequence < this._latestOverlappingSubmitSequence;
651
+ }
652
+ async waitForTimestamp(timestampMs) {
653
+ const remainingMs = timestampMs - Date.now();
654
+ if (remainingMs <= 0) return;
655
+ await new Promise((resolve) => {
656
+ const wrappedResolve = () => {
657
+ this._activeDebounceResolves.delete(wrappedResolve);
658
+ resolve();
659
+ };
660
+ const timer = setTimeout(() => {
661
+ this._activeDebounceTimers.delete(timer);
662
+ wrappedResolve();
663
+ }, remainingMs);
664
+ this._activeDebounceTimers.add(timer);
665
+ this._activeDebounceResolves.add(wrappedResolve);
666
+ });
667
+ }
668
+ cancelActiveDebounce() {
669
+ for (const timer of this._activeDebounceTimers) clearTimeout(timer);
670
+ this._activeDebounceTimers.clear();
671
+ const resolves = [...this._activeDebounceResolves];
672
+ this._activeDebounceResolves.clear();
673
+ for (const resolve of resolves) resolve();
674
+ }
675
+ reset() {
676
+ this._resetEpoch++;
677
+ this._pendingEnqueueCount = 0;
678
+ this.cancelActiveDebounce();
679
+ }
680
+ async waitForIdle(waitForQueueIdle) {
681
+ while (true) {
682
+ await waitForQueueIdle();
683
+ if (this._pendingEnqueueCount === 0) return;
684
+ await new Promise((resolve) => setTimeout(resolve, 5));
685
+ }
686
+ }
687
+ normalize(concurrency) {
688
+ if (typeof concurrency === "string") return concurrency;
689
+ const debounceMs = concurrency.debounceMs;
690
+ return {
691
+ strategy: "debounce",
692
+ debounceMs: typeof debounceMs === "number" && Number.isFinite(debounceMs) && debounceMs >= 0 ? debounceMs : this.options.defaultDebounceMs
693
+ };
694
+ }
695
+ };
696
+ //#endregion
577
697
  //#region src/chat/broadcast-state.ts
578
698
  function transition(state, event) {
579
699
  switch (event.type) {
@@ -1300,6 +1420,117 @@ function parseProtocolMessage(raw) {
1300
1420
  }
1301
1421
  }
1302
1422
  //#endregion
1303
- export { AbortRegistry, CHAT_MESSAGE_TYPES, ContinuationState, ROW_MAX_BYTES, ResumableStream, StreamAccumulator, TurnQueue, applyChunkToParts, applyToolUpdate, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
1423
+ //#region src/chat/message-reconciler.ts
1424
+ /**
1425
+ * Reconcile incoming client messages against server state.
1426
+ *
1427
+ * 1. Merges server-known tool outputs into incoming messages that still
1428
+ * show stale states (input-available, approval-requested, approval-responded)
1429
+ * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match
1430
+ *
1431
+ * @param incoming - Messages from the client
1432
+ * @param serverMessages - Current server-side messages (source of truth)
1433
+ * @param sanitizeForContentKey - Function to sanitize a message before computing
1434
+ * its content key (typically strips ephemeral provider metadata)
1435
+ * @returns Reconciled messages ready for persistence
1436
+ */
1437
+ function reconcileMessages(incoming, serverMessages, sanitizeForContentKey) {
1438
+ return reconcileAssistantIds(mergeServerToolOutputs(incoming, serverMessages), serverMessages, sanitizeForContentKey);
1439
+ }
1440
+ /**
1441
+ * For a single message, resolve its ID by matching toolCallId against server state.
1442
+ * Prevents duplicate DB rows when client IDs differ from server IDs.
1443
+ * Tool call IDs are unique per conversation, so matching is safe regardless of state.
1444
+ */
1445
+ function resolveToolMergeId(message, serverMessages) {
1446
+ if (message.role !== "assistant") return message;
1447
+ for (const part of message.parts) if ("toolCallId" in part && part.toolCallId) {
1448
+ const toolCallId = part.toolCallId;
1449
+ const existing = findMessageByToolCallId(serverMessages, toolCallId);
1450
+ if (existing && existing.id !== message.id) return {
1451
+ ...message,
1452
+ id: existing.id
1453
+ };
1454
+ }
1455
+ return message;
1456
+ }
1457
+ /**
1458
+ * Content key for assistant messages used for dedup of identical short replies.
1459
+ * Returns JSON of sanitized parts, or undefined for non-assistant messages.
1460
+ */
1461
+ function assistantContentKey(message, sanitize) {
1462
+ if (message.role !== "assistant") return;
1463
+ const sanitized = sanitize ? sanitize(message) : message;
1464
+ return JSON.stringify(sanitized.parts);
1465
+ }
1466
+ function mergeServerToolOutputs(incoming, serverMessages) {
1467
+ const serverToolOutputs = /* @__PURE__ */ new Map();
1468
+ for (const msg of serverMessages) {
1469
+ if (msg.role !== "assistant") continue;
1470
+ for (const part of msg.parts) if ("toolCallId" in part && "state" in part && part.state === "output-available" && "output" in part) serverToolOutputs.set(part.toolCallId, part.output);
1471
+ }
1472
+ if (serverToolOutputs.size === 0) return incoming;
1473
+ return incoming.map((msg) => {
1474
+ if (msg.role !== "assistant") return msg;
1475
+ let hasChanges = false;
1476
+ const updatedParts = msg.parts.map((part) => {
1477
+ if ("toolCallId" in part && "state" in part && (part.state === "input-available" || part.state === "approval-requested" || part.state === "approval-responded") && serverToolOutputs.has(part.toolCallId)) {
1478
+ hasChanges = true;
1479
+ return {
1480
+ ...part,
1481
+ state: "output-available",
1482
+ output: serverToolOutputs.get(part.toolCallId)
1483
+ };
1484
+ }
1485
+ return part;
1486
+ });
1487
+ return hasChanges ? {
1488
+ ...msg,
1489
+ parts: updatedParts
1490
+ } : msg;
1491
+ });
1492
+ }
1493
+ function reconcileAssistantIds(incoming, serverMessages, sanitize) {
1494
+ if (serverMessages.length === 0) return incoming;
1495
+ const claimedServerIndices = /* @__PURE__ */ new Set();
1496
+ const exactMatchMap = /* @__PURE__ */ new Map();
1497
+ for (let i = 0; i < incoming.length; i++) {
1498
+ const serverIdx = serverMessages.findIndex((sm, si) => !claimedServerIndices.has(si) && sm.id === incoming[i].id);
1499
+ if (serverIdx !== -1) {
1500
+ claimedServerIndices.add(serverIdx);
1501
+ exactMatchMap.set(i, serverIdx);
1502
+ }
1503
+ }
1504
+ return incoming.map((incomingMessage, incomingIdx) => {
1505
+ if (exactMatchMap.has(incomingIdx)) return incomingMessage;
1506
+ if (incomingMessage.role !== "assistant" || hasToolCallPart(incomingMessage)) return incomingMessage;
1507
+ const incomingKey = assistantContentKey(incomingMessage, sanitize);
1508
+ if (!incomingKey) return incomingMessage;
1509
+ for (let i = 0; i < serverMessages.length; i++) {
1510
+ if (claimedServerIndices.has(i)) continue;
1511
+ const serverMessage = serverMessages[i];
1512
+ if (serverMessage.role !== "assistant" || hasToolCallPart(serverMessage)) continue;
1513
+ if (assistantContentKey(serverMessage, sanitize) === incomingKey) {
1514
+ claimedServerIndices.add(i);
1515
+ return {
1516
+ ...incomingMessage,
1517
+ id: serverMessage.id
1518
+ };
1519
+ }
1520
+ }
1521
+ return incomingMessage;
1522
+ });
1523
+ }
1524
+ function hasToolCallPart(message) {
1525
+ return message.parts.some((part) => "toolCallId" in part);
1526
+ }
1527
+ function findMessageByToolCallId(messages, toolCallId) {
1528
+ for (const msg of messages) {
1529
+ if (msg.role !== "assistant") continue;
1530
+ for (const part of msg.parts) if ("toolCallId" in part && part.toolCallId === toolCallId) return msg;
1531
+ }
1532
+ }
1533
+ //#endregion
1534
+ export { AbortRegistry, CHAT_MESSAGE_TYPES, ContinuationState, ROW_MAX_BYTES, ResumableStream, StreamAccumulator, SubmitConcurrencyController, TurnQueue, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
1304
1535
 
1305
1536
  //# sourceMappingURL=index.js.map