@convex-dev/agent 0.6.0-alpha.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/package.json +1 -1
  2. package/src/UIMessages.ts +135 -0
  3. package/src/client/index.ts +56 -14
  4. package/src/client/search.test.ts +4 -5
  5. package/src/client/search.ts +45 -4
  6. package/src/client/streaming.integration.test.ts +1206 -0
  7. package/src/client/streaming.ts +9 -2
  8. package/src/mapping.test.ts +136 -71
  9. package/src/mapping.ts +81 -28
  10. package/dist/UIMessages.d.ts +0 -46
  11. package/dist/UIMessages.d.ts.map +0 -1
  12. package/dist/UIMessages.js +0 -546
  13. package/dist/UIMessages.js.map +0 -1
  14. package/dist/client/createTool.d.ts +0 -167
  15. package/dist/client/createTool.d.ts.map +0 -1
  16. package/dist/client/createTool.js +0 -116
  17. package/dist/client/createTool.js.map +0 -1
  18. package/dist/client/defaultComponent.d.ts +0 -11
  19. package/dist/client/defaultComponent.d.ts.map +0 -1
  20. package/dist/client/defaultComponent.js +0 -7
  21. package/dist/client/defaultComponent.js.map +0 -1
  22. package/dist/client/definePlaygroundAPI.d.ts +0 -1725
  23. package/dist/client/definePlaygroundAPI.d.ts.map +0 -1
  24. package/dist/client/definePlaygroundAPI.js +0 -271
  25. package/dist/client/definePlaygroundAPI.js.map +0 -1
  26. package/dist/client/files.d.ts +0 -69
  27. package/dist/client/files.d.ts.map +0 -1
  28. package/dist/client/files.js +0 -181
  29. package/dist/client/files.js.map +0 -1
  30. package/dist/client/index.d.ts +0 -2091
  31. package/dist/client/index.d.ts.map +0 -1
  32. package/dist/client/index.js +0 -895
  33. package/dist/client/index.js.map +0 -1
  34. package/dist/client/messages.d.ts +0 -461
  35. package/dist/client/messages.d.ts.map +0 -1
  36. package/dist/client/messages.js +0 -106
  37. package/dist/client/messages.js.map +0 -1
  38. package/dist/client/mockModel.d.ts +0 -42
  39. package/dist/client/mockModel.d.ts.map +0 -1
  40. package/dist/client/mockModel.js +0 -182
  41. package/dist/client/mockModel.js.map +0 -1
  42. package/dist/client/saveInputMessages.d.ts +0 -20
  43. package/dist/client/saveInputMessages.d.ts.map +0 -1
  44. package/dist/client/saveInputMessages.js +0 -58
  45. package/dist/client/saveInputMessages.js.map +0 -1
  46. package/dist/client/search.d.ts +0 -493
  47. package/dist/client/search.d.ts.map +0 -1
  48. package/dist/client/search.js +0 -425
  49. package/dist/client/search.js.map +0 -1
  50. package/dist/client/start.d.ts +0 -84
  51. package/dist/client/start.d.ts.map +0 -1
  52. package/dist/client/start.js +0 -185
  53. package/dist/client/start.js.map +0 -1
  54. package/dist/client/streamText.d.ts +0 -46
  55. package/dist/client/streamText.d.ts.map +0 -1
  56. package/dist/client/streamText.js +0 -117
  57. package/dist/client/streamText.js.map +0 -1
  58. package/dist/client/streaming.d.ts +0 -3778
  59. package/dist/client/streaming.d.ts.map +0 -1
  60. package/dist/client/streaming.js +0 -314
  61. package/dist/client/streaming.js.map +0 -1
  62. package/dist/client/threads.d.ts +0 -46
  63. package/dist/client/threads.d.ts.map +0 -1
  64. package/dist/client/threads.js +0 -49
  65. package/dist/client/threads.js.map +0 -1
  66. package/dist/client/types.d.ts +0 -461
  67. package/dist/client/types.d.ts.map +0 -1
  68. package/dist/client/types.js +0 -2
  69. package/dist/client/types.js.map +0 -1
  70. package/dist/client/utils.d.ts +0 -4
  71. package/dist/client/utils.d.ts.map +0 -1
  72. package/dist/client/utils.js +0 -21
  73. package/dist/client/utils.js.map +0 -1
  74. package/dist/component/_generated/api.d.ts +0 -48
  75. package/dist/component/_generated/api.d.ts.map +0 -1
  76. package/dist/component/_generated/api.js +0 -31
  77. package/dist/component/_generated/api.js.map +0 -1
  78. package/dist/component/_generated/component.d.ts +0 -3120
  79. package/dist/component/_generated/component.d.ts.map +0 -1
  80. package/dist/component/_generated/component.js +0 -11
  81. package/dist/component/_generated/component.js.map +0 -1
  82. package/dist/component/_generated/dataModel.d.ts +0 -46
  83. package/dist/component/_generated/dataModel.d.ts.map +0 -1
  84. package/dist/component/_generated/dataModel.js +0 -11
  85. package/dist/component/_generated/dataModel.js.map +0 -1
  86. package/dist/component/_generated/server.d.ts +0 -121
  87. package/dist/component/_generated/server.d.ts.map +0 -1
  88. package/dist/component/_generated/server.js +0 -78
  89. package/dist/component/_generated/server.js.map +0 -1
  90. package/dist/component/apiKeys.d.ts +0 -11
  91. package/dist/component/apiKeys.d.ts.map +0 -1
  92. package/dist/component/apiKeys.js +0 -69
  93. package/dist/component/apiKeys.js.map +0 -1
  94. package/dist/component/convex.config.d.ts +0 -3
  95. package/dist/component/convex.config.d.ts.map +0 -1
  96. package/dist/component/convex.config.js +0 -3
  97. package/dist/component/convex.config.js.map +0 -1
  98. package/dist/component/files.d.ts +0 -97
  99. package/dist/component/files.d.ts.map +0 -1
  100. package/dist/component/files.js +0 -190
  101. package/dist/component/files.js.map +0 -1
  102. package/dist/component/messages.d.ts +0 -3851
  103. package/dist/component/messages.d.ts.map +0 -1
  104. package/dist/component/messages.js +0 -757
  105. package/dist/component/messages.js.map +0 -1
  106. package/dist/component/schema.d.ts +0 -8026
  107. package/dist/component/schema.d.ts.map +0 -1
  108. package/dist/component/schema.js +0 -147
  109. package/dist/component/schema.js.map +0 -1
  110. package/dist/component/streams.d.ts +0 -128
  111. package/dist/component/streams.d.ts.map +0 -1
  112. package/dist/component/streams.js +0 -413
  113. package/dist/component/streams.js.map +0 -1
  114. package/dist/component/threads.d.ts +0 -115
  115. package/dist/component/threads.d.ts.map +0 -1
  116. package/dist/component/threads.js +0 -208
  117. package/dist/component/threads.js.map +0 -1
  118. package/dist/component/users.d.ts +0 -52
  119. package/dist/component/users.d.ts.map +0 -1
  120. package/dist/component/users.js +0 -229
  121. package/dist/component/users.js.map +0 -1
  122. package/dist/component/vector/index.d.ts +0 -61
  123. package/dist/component/vector/index.d.ts.map +0 -1
  124. package/dist/component/vector/index.js +0 -146
  125. package/dist/component/vector/index.js.map +0 -1
  126. package/dist/component/vector/tables.d.ts +0 -58
  127. package/dist/component/vector/tables.d.ts.map +0 -1
  128. package/dist/component/vector/tables.js +0 -56
  129. package/dist/component/vector/tables.js.map +0 -1
  130. package/dist/deltas.d.ts +0 -43
  131. package/dist/deltas.d.ts.map +0 -1
  132. package/dist/deltas.js +0 -446
  133. package/dist/deltas.js.map +0 -1
  134. package/dist/mapping.d.ts +0 -72
  135. package/dist/mapping.d.ts.map +0 -1
  136. package/dist/mapping.js +0 -677
  137. package/dist/mapping.js.map +0 -1
  138. package/dist/react/SmoothText.d.ts +0 -5
  139. package/dist/react/SmoothText.d.ts.map +0 -1
  140. package/dist/react/SmoothText.js +0 -6
  141. package/dist/react/SmoothText.js.map +0 -1
  142. package/dist/react/index.d.ts +0 -25
  143. package/dist/react/index.d.ts.map +0 -1
  144. package/dist/react/index.js +0 -70
  145. package/dist/react/index.js.map +0 -1
  146. package/dist/react/optimisticallySendMessage.d.ts +0 -42
  147. package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
  148. package/dist/react/optimisticallySendMessage.js +0 -74
  149. package/dist/react/optimisticallySendMessage.js.map +0 -1
  150. package/dist/react/types.d.ts +0 -12
  151. package/dist/react/types.d.ts.map +0 -1
  152. package/dist/react/types.js +0 -2
  153. package/dist/react/types.js.map +0 -1
  154. package/dist/react/useDeltaStreams.d.ts +0 -10
  155. package/dist/react/useDeltaStreams.d.ts.map +0 -1
  156. package/dist/react/useDeltaStreams.js +0 -106
  157. package/dist/react/useDeltaStreams.js.map +0 -1
  158. package/dist/react/useSmoothText.d.ts +0 -27
  159. package/dist/react/useSmoothText.d.ts.map +0 -1
  160. package/dist/react/useSmoothText.js +0 -68
  161. package/dist/react/useSmoothText.js.map +0 -1
  162. package/dist/react/useStreamingUIMessages.d.ts +0 -22
  163. package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
  164. package/dist/react/useStreamingUIMessages.js +0 -92
  165. package/dist/react/useStreamingUIMessages.js.map +0 -1
  166. package/dist/react/useThreadMessages.d.ts +0 -104
  167. package/dist/react/useThreadMessages.d.ts.map +0 -1
  168. package/dist/react/useThreadMessages.js +0 -148
  169. package/dist/react/useThreadMessages.js.map +0 -1
  170. package/dist/react/useUIMessages.d.ts +0 -96
  171. package/dist/react/useUIMessages.d.ts.map +0 -1
  172. package/dist/react/useUIMessages.js +0 -108
  173. package/dist/react/useUIMessages.js.map +0 -1
  174. package/dist/shared.d.ts +0 -26
  175. package/dist/shared.d.ts.map +0 -1
  176. package/dist/shared.js +0 -67
  177. package/dist/shared.js.map +0 -1
  178. package/dist/validators.d.ts +0 -24516
  179. package/dist/validators.d.ts.map +0 -1
  180. package/dist/validators.js +0 -475
  181. package/dist/validators.js.map +0 -1
@@ -293,7 +293,8 @@ export class DeltaStreamer<T> {
293
293
  await this.addParts([chunk]);
294
294
  }
295
295
  // Skip finish if it will be handled externally (atomically with message save)
296
- if (!this.#finishedExternally) {
296
+ // or if the stream was aborted (e.g., due to a failed delta write)
297
+ if (!this.#finishedExternally && !this.abortController.signal.aborted) {
297
298
  await this.finish();
298
299
  }
299
300
  }
@@ -339,7 +340,7 @@ export class DeltaStreamer<T> {
339
340
  e instanceof Error ? e.message : "unknown error",
340
341
  );
341
342
  this.abortController.abort();
342
- throw e;
343
+ return;
343
344
  }
344
345
  // Now that we've sent the delta, check if we need to send another one.
345
346
  if (
@@ -375,7 +376,13 @@ export class DeltaStreamer<T> {
375
376
  return;
376
377
  }
377
378
  await this.#ongoingWrite;
379
+ if (this.abortController.signal.aborted) {
380
+ return;
381
+ }
378
382
  await this.#sendDelta();
383
+ if (this.abortController.signal.aborted) {
384
+ return;
385
+ }
379
386
  await this.ctx.runMutation(this.component.streams.finish, {
380
387
  streamId: this.streamId,
381
388
  });
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "vitest";
1
+ import { describe, test, expect, vi } from "vitest";
2
2
  import {
3
3
  guessMimeType,
4
4
  serializeDataOrUrl,
@@ -7,7 +7,7 @@ import {
7
7
  toModelMessage,
8
8
  serializeContent,
9
9
  toModelMessageContent,
10
- mergeApprovalResponseMessages,
10
+ autoDenyUnresolvedApprovals,
11
11
  } from "./mapping.js";
12
12
  import { api } from "./component/_generated/api.js";
13
13
  import type { AgentComponent, ActionCtx } from "./client/types.js";
@@ -259,79 +259,144 @@ describe("mapping", () => {
259
259
  expect((content as unknown[])[0]).toMatchObject(approvalResponse);
260
260
  });
261
261
 
262
- test("mergeApprovalResponseMessages merges consecutive tool approval messages", () => {
263
- const messages = [
264
- { role: "user" as const, content: "hello" },
265
- {
266
- role: "assistant" as const,
267
- content: [
268
- { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
269
- { type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
270
- { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
271
- { type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
272
- ],
273
- },
274
- {
275
- role: "tool" as const,
276
- content: [
277
- { type: "tool-approval-response", approvalId: "ap1", approved: true },
278
- ],
279
- },
280
- {
281
- role: "tool" as const,
282
- content: [
283
- { type: "tool-approval-response", approvalId: "ap2", approved: false, reason: "denied" },
284
- ],
285
- },
286
- ] as any;
262
+ describe("autoDenyUnresolvedApprovals", () => {
263
+ test("returns messages unchanged when no unresolved approvals", () => {
264
+ const messages = [
265
+ { role: "user" as const, content: "hello" },
266
+ {
267
+ role: "assistant" as const,
268
+ content: [
269
+ { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
270
+ { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
271
+ ],
272
+ },
273
+ {
274
+ role: "tool" as const,
275
+ content: [
276
+ { type: "tool-approval-response", approvalId: "ap1", approved: true },
277
+ ],
278
+ },
279
+ ] as any;
287
280
 
288
- const merged = mergeApprovalResponseMessages(messages);
289
- expect(merged).toHaveLength(3); // user, assistant, single tool
290
- expect(merged[2].role).toBe("tool");
291
- const toolContent = merged[2].content as Array<{ type: string; approvalId: string }>;
292
- expect(toolContent).toHaveLength(2);
293
- expect(toolContent[0].approvalId).toBe("ap1");
294
- expect(toolContent[1].approvalId).toBe("ap2");
295
- });
281
+ const result = autoDenyUnresolvedApprovals(messages);
282
+ expect(result).toBe(messages); // same reference, no changes
283
+ });
296
284
 
297
- test("mergeApprovalResponseMessages does not mutate original message content arrays", () => {
298
- const msg1Content = [
299
- { type: "tool-approval-response", approvalId: "ap1", approved: true },
300
- ];
301
- const msg2Content = [
302
- { type: "tool-approval-response", approvalId: "ap2", approved: false, reason: "denied" },
303
- ];
304
- const messages = [
305
- { role: "tool" as const, content: msg1Content },
306
- { role: "tool" as const, content: msg2Content },
307
- ] as any;
285
+ test("injects synthetic denial for a single unresolved approval", () => {
286
+ const messages = [
287
+ { role: "user" as const, content: "hello" },
288
+ {
289
+ role: "assistant" as const,
290
+ content: [
291
+ { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
292
+ { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
293
+ ],
294
+ },
295
+ { role: "user" as const, content: "new message" },
296
+ ] as any;
308
297
 
309
- const merged = mergeApprovalResponseMessages(messages);
310
- // Merged result should combine both
311
- expect(merged).toHaveLength(1);
312
- expect((merged[0].content as any[]).length).toBe(2);
313
- // Original arrays must be untouched
314
- expect(msg1Content).toHaveLength(1);
315
- expect(msg2Content).toHaveLength(1);
316
- });
298
+ const result = autoDenyUnresolvedApprovals(messages);
299
+ expect(result).toHaveLength(4); // original 3 + 1 synthetic tool message
300
+ // Synthetic denial should be inserted right after the assistant message (index 1)
301
+ expect(result[2].role).toBe("tool");
302
+ const denialContent = result[2].content as any[];
303
+ expect(denialContent).toHaveLength(1);
304
+ expect(denialContent[0].type).toBe("tool-approval-response");
305
+ expect(denialContent[0].approvalId).toBe("ap1");
306
+ expect(denialContent[0].approved).toBe(false);
307
+ expect(denialContent[0].reason).toBe("auto-denied: new generation started");
308
+ // The new user message should follow
309
+ expect(result[3].role).toBe("user");
310
+ expect(result[3].content).toBe("new message");
311
+ });
317
312
 
318
- test("mergeApprovalResponseMessages does not merge non-approval tool messages", () => {
319
- const messages = [
320
- {
321
- role: "tool" as const,
322
- content: [
323
- { type: "tool-result", toolCallId: "tc1", toolName: "a", output: { type: "text", value: "ok" } },
324
- ],
325
- },
326
- {
327
- role: "tool" as const,
328
- content: [
329
- { type: "tool-approval-response", approvalId: "ap1", approved: true },
330
- ],
331
- },
332
- ] as any;
313
+ test("groups multiple unresolved approvals from the same step into a single synthetic message", () => {
314
+ const messages = [
315
+ {
316
+ role: "assistant" as const,
317
+ content: [
318
+ { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
319
+ { type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
320
+ { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
321
+ { type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
322
+ ],
323
+ },
324
+ ] as any;
325
+
326
+ const result = autoDenyUnresolvedApprovals(messages);
327
+ expect(result).toHaveLength(2); // assistant + 1 synthetic tool message
328
+ expect(result[1].role).toBe("tool");
329
+ const denialContent = result[1].content as any[];
330
+ expect(denialContent).toHaveLength(2);
331
+ expect(denialContent[0].approvalId).toBe("ap1");
332
+ expect(denialContent[0].approved).toBe(false);
333
+ expect(denialContent[1].approvalId).toBe("ap2");
334
+ expect(denialContent[1].approved).toBe(false);
335
+ });
336
+
337
+ test("only auto-denies unresolved approvals, leaves resolved ones alone", () => {
338
+ const messages = [
339
+ {
340
+ role: "assistant" as const,
341
+ content: [
342
+ { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
343
+ { type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
344
+ { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
345
+ { type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
346
+ ],
347
+ },
348
+ {
349
+ role: "tool" as const,
350
+ content: [
351
+ { type: "tool-approval-response", approvalId: "ap1", approved: true },
352
+ ],
353
+ },
354
+ { role: "user" as const, content: "next question" },
355
+ ] as any;
356
+
357
+ const result = autoDenyUnresolvedApprovals(messages);
358
+ // Should inject a denial for ap2 (unresolved) after the assistant message
359
+ expect(result).toHaveLength(4); // assistant + existing tool + synthetic denial + user
360
+ // The synthetic denial is inserted after the assistant (index 0)
361
+ expect(result[0].role).toBe("assistant");
362
+ expect(result[1].role).toBe("tool"); // synthetic denial for ap2
363
+ const denialContent = result[1].content as any[];
364
+ expect(denialContent).toHaveLength(1);
365
+ expect(denialContent[0].approvalId).toBe("ap2");
366
+ expect(denialContent[0].approved).toBe(false);
367
+ // Original tool message (ap1 response) follows
368
+ expect(result[2].role).toBe("tool");
369
+ const originalToolContent = result[2].content as any[];
370
+ expect(originalToolContent[0].approvalId).toBe("ap1");
371
+ expect(originalToolContent[0].approved).toBe(true);
372
+ // User message last
373
+ expect(result[3].role).toBe("user");
374
+ });
375
+
376
+ test("emits console.warn for each auto-denied approval", () => {
377
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
378
+ const messages = [
379
+ {
380
+ role: "assistant" as const,
381
+ content: [
382
+ { type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
383
+ { type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
384
+ { type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
385
+ { type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
386
+ ],
387
+ },
388
+ ] as any;
389
+
390
+ autoDenyUnresolvedApprovals(messages);
333
391
 
334
- const merged = mergeApprovalResponseMessages(messages);
335
- expect(merged).toHaveLength(2); // not merged since first has tool-result
392
+ expect(warnSpy).toHaveBeenCalledTimes(2);
393
+ expect(warnSpy).toHaveBeenCalledWith(
394
+ expect.stringContaining("ap1"),
395
+ );
396
+ expect(warnSpy).toHaveBeenCalledWith(
397
+ expect.stringContaining("ap2"),
398
+ );
399
+ warnSpy.mockRestore();
400
+ });
336
401
  });
337
402
  });
package/src/mapping.ts CHANGED
@@ -140,41 +140,94 @@ export function docsToModelMessages(messages: MessageDoc[]): ModelMessage[] {
140
140
  }
141
141
 
142
142
  /**
143
- * Merge consecutive tool messages that contain `tool-approval-response` parts
144
- * into a single tool message. The AI SDK's `collectToolApprovals` only examines
145
- * the last tool message, so when multiple approvals are saved as separate
146
- * messages (e.g. approve tool A, then deny tool B), they must be combined
147
- * for the SDK to process them all.
143
+ * Scan messages for unresolved `tool-approval-request` parts and inject
144
+ * synthetic `tool-approval-response` denials so that the AI SDK receives
145
+ * a complete history (every tool-call has a corresponding result or denial).
146
+ *
147
+ * This handles the case where a user sends a new message instead of
148
+ * resolving pending approvals — the old approvals are auto-denied rather
149
+ * than silently dropped.
148
150
  */
149
- export function mergeApprovalResponseMessages(
151
+ export function autoDenyUnresolvedApprovals(
150
152
  messages: ModelMessage[],
151
153
  ): ModelMessage[] {
154
+ // Collect all approval requests: approvalId → { toolCallId, messageIndex }
155
+ const requests = new Map<
156
+ string,
157
+ { toolCallId: string; messageIndex: number }
158
+ >();
159
+ // Collect all resolved approval IDs
160
+ const resolvedIds = new Set<string>();
161
+
162
+ for (let i = 0; i < messages.length; i++) {
163
+ const msg = messages[i];
164
+ if (!Array.isArray(msg.content)) continue;
165
+ for (const part of msg.content as any[]) {
166
+ if (part.type === "tool-approval-request") {
167
+ requests.set(part.approvalId, {
168
+ toolCallId: part.toolCallId,
169
+ messageIndex: i,
170
+ });
171
+ } else if (part.type === "tool-approval-response") {
172
+ resolvedIds.add(part.approvalId);
173
+ }
174
+ }
175
+ }
176
+
177
+ // Find unresolved approvals
178
+ const unresolved: Array<{
179
+ approvalId: string;
180
+ toolCallId: string;
181
+ messageIndex: number;
182
+ }> = [];
183
+ for (const [approvalId, info] of requests) {
184
+ if (!resolvedIds.has(approvalId)) {
185
+ unresolved.push({ approvalId, ...info });
186
+ }
187
+ }
188
+
189
+ if (unresolved.length === 0) {
190
+ return messages;
191
+ }
192
+
193
+ // Group unresolved approvals by the assistant message index they came from
194
+ const byMessageIndex = new Map<
195
+ number,
196
+ Array<{ approvalId: string; toolCallId: string }>
197
+ >();
198
+ for (const entry of unresolved) {
199
+ console.warn(
200
+ `Auto-denying unresolved tool approval ${entry.approvalId} ` +
201
+ `(toolCallId: ${entry.toolCallId}): new generation started`,
202
+ );
203
+ let group = byMessageIndex.get(entry.messageIndex);
204
+ if (!group) {
205
+ group = [];
206
+ byMessageIndex.set(entry.messageIndex, group);
207
+ }
208
+ group.push(entry);
209
+ }
210
+
211
+ // Build result by inserting synthetic denial messages after each relevant
212
+ // assistant message
152
213
  const result: ModelMessage[] = [];
153
- for (const msg of messages) {
154
- const prev = result.at(-1);
155
- if (
156
- msg.role === "tool" &&
157
- prev?.role === "tool" &&
158
- Array.isArray(msg.content) &&
159
- Array.isArray(prev.content) &&
160
- hasApprovalResponse(msg.content) &&
161
- hasApprovalResponse(prev.content)
162
- ) {
163
- // Clone before merging to avoid mutating the original message's content array
164
- const cloned = { ...prev, content: [...(prev.content as any[])] };
165
- result[result.length - 1] = cloned;
166
- (cloned.content as any[]).push(...(msg.content as any[]));
167
- } else {
168
- result.push(msg);
214
+ for (let i = 0; i < messages.length; i++) {
215
+ result.push(messages[i]);
216
+ const group = byMessageIndex.get(i);
217
+ if (group) {
218
+ result.push({
219
+ role: "tool",
220
+ content: group.map((entry) => ({
221
+ type: "tool-approval-response" as const,
222
+ approvalId: entry.approvalId,
223
+ approved: false,
224
+ reason: "auto-denied: new generation started",
225
+ })),
226
+ });
169
227
  }
170
228
  }
171
- return result;
172
- }
173
229
 
174
- function hasApprovalResponse(content: any[]): boolean {
175
- return content.some(
176
- (p: any) => p.type === "tool-approval-response",
177
- );
230
+ return result;
178
231
  }
179
232
 
180
233
  export function serializeUsage(usage: LanguageModelUsage): Usage {
@@ -1,46 +0,0 @@
1
- import { type UIMessage as AIUIMessage, type UIDataTypes, type UITools } from "ai";
2
- import type { MessageDoc, MessageStatus, ProviderOptions } from "./validators.js";
3
- export type UIStatus = "streaming" | MessageStatus;
4
- export type UIMessage<METADATA = unknown, DATA_PARTS extends UIDataTypes = UIDataTypes, TOOLS extends UITools = UITools> = AIUIMessage<METADATA, DATA_PARTS, TOOLS> & {
5
- key: string;
6
- order: number;
7
- stepOrder: number;
8
- status: UIStatus;
9
- agentName?: string;
10
- userId?: string;
11
- text: string;
12
- _creationTime: number;
13
- };
14
- /**
15
- * Converts a list of UIMessages to MessageDocs, along with extra metadata that
16
- * may be available to associate with the MessageDocs.
17
- * @param messages - The UIMessages to convert to MessageDocs.
18
- * @param meta - The metadata to add to the MessageDocs.
19
- * @returns
20
- */
21
- export declare function fromUIMessages<METADATA = unknown>(messages: UIMessage<METADATA>[], meta: {
22
- threadId: string;
23
- userId?: string;
24
- model?: string;
25
- provider?: string;
26
- providerOptions?: ProviderOptions;
27
- metadata?: METADATA;
28
- }): Promise<(MessageDoc & {
29
- streaming: boolean;
30
- metadata?: METADATA;
31
- })[]>;
32
- type ExtraFields<METADATA = unknown> = {
33
- streaming?: boolean;
34
- metadata?: METADATA;
35
- };
36
- /**
37
- * Converts a list of MessageDocs to UIMessages.
38
- * This is somewhat lossy, as many fields are not supported by UIMessages, e.g.
39
- * the model, provider, userId, etc.
40
- * The UIMessage type is the augmented type that includes more fields such as
41
- * key, order, stepOrder, status, agentName, text, etc.
42
- */
43
- export declare function toUIMessages<METADATA = unknown, DATA_PARTS extends UIDataTypes = UIDataTypes, TOOLS extends UITools = UITools>(messages: (MessageDoc & ExtraFields<METADATA>)[]): UIMessage<METADATA, DATA_PARTS, TOOLS>[];
44
- export declare function combineUIMessages(messages: UIMessage[]): UIMessage[];
45
- export {};
46
- //# sourceMappingURL=UIMessages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"UIMessages.d.ts","sourceRoot":"","sources":["../src/UIMessages.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,IAAI,WAAW,EAU7B,KAAK,WAAW,EAChB,KAAK,OAAO,EACb,MAAM,IAAI,CAAC;AAUZ,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,iBAAiB,CAAC;AAGzB,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAEnD,MAAM,MAAM,SAAS,CACnB,QAAQ,GAAG,OAAO,EAClB,UAAU,SAAS,WAAW,GAAG,WAAW,EAC5C,KAAK,SAAS,OAAO,GAAG,OAAO,IAC7B,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,QAAQ,GAAG,OAAO,EACrD,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,GACA,OAAO,CAAC,CAAC,UAAU,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC,EAAE,CAAC,CAiEvE;AA8BD,KAAK,WAAW,CAAC,QAAQ,GAAG,OAAO,IAAI;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,GAAG,OAAO,EAClB,UAAU,SAAS,WAAW,GAAG,WAAW,EAC5C,KAAK,SAAS,OAAO,GAAG,OAAO,EAE/B,QAAQ,EAAE,CAAC,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,GAC/C,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,CAiB1C;AA+dD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA2CpE"}