@convex-dev/agent 0.1.18-alpha.1 → 0.5.0-alpha.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.
Files changed (71) hide show
  1. package/dist/client/createTool.d.ts +30 -9
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -9
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +174 -199
  6. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  7. package/dist/client/definePlaygroundAPI.js +12 -19
  8. package/dist/client/definePlaygroundAPI.js.map +1 -1
  9. package/dist/client/files.js +4 -4
  10. package/dist/client/files.js.map +1 -1
  11. package/dist/client/index.d.ts +166 -63
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +75 -45
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/search.d.ts +27 -6
  16. package/dist/client/search.d.ts.map +1 -1
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/streaming.d.ts +3 -2
  19. package/dist/client/streaming.d.ts.map +1 -1
  20. package/dist/client/streaming.js.map +1 -1
  21. package/dist/client/types.d.ts +27 -41
  22. package/dist/client/types.d.ts.map +1 -1
  23. package/dist/component/_generated/api.d.ts +436 -73
  24. package/dist/component/messages.d.ts +246 -43
  25. package/dist/component/messages.d.ts.map +1 -1
  26. package/dist/component/messages.js +2 -2
  27. package/dist/component/messages.js.map +1 -1
  28. package/dist/component/schema.d.ts +1453 -152
  29. package/dist/component/schema.d.ts.map +1 -1
  30. package/dist/component/schema.js +2 -2
  31. package/dist/component/schema.js.map +1 -1
  32. package/dist/component/streams.d.ts +180 -6
  33. package/dist/component/streams.d.ts.map +1 -1
  34. package/dist/mapping.d.ts +12 -14
  35. package/dist/mapping.d.ts.map +1 -1
  36. package/dist/mapping.js +187 -47
  37. package/dist/mapping.js.map +1 -1
  38. package/dist/react/deltas.d.ts +0 -3
  39. package/dist/react/deltas.d.ts.map +1 -1
  40. package/dist/react/deltas.js +140 -44
  41. package/dist/react/deltas.js.map +1 -1
  42. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  43. package/dist/react/optimisticallySendMessage.js +2 -1
  44. package/dist/react/optimisticallySendMessage.js.map +1 -1
  45. package/dist/react/toUIMessages.d.ts +5 -4
  46. package/dist/react/toUIMessages.d.ts.map +1 -1
  47. package/dist/react/toUIMessages.js +103 -40
  48. package/dist/react/toUIMessages.js.map +1 -1
  49. package/dist/validators.d.ts +1798 -259
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +79 -15
  52. package/dist/validators.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/client/createTool.ts +68 -37
  55. package/src/client/definePlaygroundAPI.ts +24 -25
  56. package/src/client/files.ts +4 -4
  57. package/src/client/index.test.ts +14 -12
  58. package/src/client/index.ts +134 -94
  59. package/src/client/search.ts +3 -2
  60. package/src/client/streaming.ts +4 -3
  61. package/src/client/types.ts +34 -70
  62. package/src/component/_generated/api.d.ts +436 -73
  63. package/src/component/messages.ts +2 -2
  64. package/src/component/schema.ts +2 -2
  65. package/src/mapping.ts +228 -75
  66. package/src/react/deltas.ts +165 -52
  67. package/src/react/optimisticallySendMessage.ts +4 -1
  68. package/src/react/toUIMessages.test.ts +154 -36
  69. package/src/react/toUIMessages.ts +136 -57
  70. package/src/validators.test.ts +2 -99
  71. package/src/validators.ts +95 -17
@@ -1,4 +1,3 @@
1
- import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
2
1
  import type { MessageDoc } from "../client/index.js";
3
2
  import type {
4
3
  Message,
@@ -6,11 +5,13 @@ import type {
6
5
  StreamDelta,
7
6
  StreamMessage,
8
7
  TextStreamPart,
8
+ vReasoningPart,
9
+ vSource,
10
+ vTextPart,
11
+ vToolCallPart,
12
+ vToolResultPart,
9
13
  } from "../validators.js";
10
- import type { UIMessage } from "./toUIMessages.js";
11
- import { toUIMessages } from "./toUIMessages.js";
12
-
13
- export { toUIMessages, type UIMessage };
14
+ import type { Infer } from "convex/values";
14
15
 
15
16
  export function mergeDeltas(
16
17
  threadId: string,
@@ -142,10 +143,10 @@ export function applyDeltasToStreamMessage(
142
143
  let lastContent = getLastContent(currentMessage);
143
144
  for (const part of parts) {
144
145
  let contentToAdd:
145
- | TextPart
146
- | ToolCallPart
147
- | { type: "reasoning"; text: string }
148
- | ToolResultPart
146
+ | Infer<typeof vTextPart>
147
+ | Infer<typeof vToolCallPart>
148
+ | Infer<typeof vToolResultPart>
149
+ | Infer<typeof vReasoningPart>
149
150
  | undefined;
150
151
  const isToolRole = part.type === "source" || part.type === "tool-result";
151
152
  if (isToolRole !== (currentMessage.message!.role === "tool")) {
@@ -160,27 +161,36 @@ export function applyDeltasToStreamMessage(
160
161
  continue;
161
162
  }
162
163
  switch (part.type) {
163
- case "text-delta":
164
- currentMessage.text = (currentMessage.text ?? "") + part.textDelta;
164
+ case "text-delta": {
165
+ const text = "text" in part ? part.text : part.textDelta;
166
+ currentMessage.text = (currentMessage.text ?? "") + text;
165
167
  if (lastContent?.type === "text") {
166
- lastContent.text = (lastContent.text ?? "") + part.textDelta;
168
+ lastContent.text = (lastContent.text ?? "") + text;
167
169
  } else {
168
- contentToAdd = {
169
- type: "text",
170
- text: part.textDelta,
171
- };
170
+ contentToAdd = { type: "text", text } satisfies Infer<
171
+ typeof vTextPart
172
+ >;
172
173
  }
173
174
  break;
174
- case "tool-call-streaming-start":
175
+ }
176
+ case "tool-input-start":
177
+ case "tool-call-streaming-start": {
178
+ const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
175
179
  currentMessage.tool = true;
176
180
  contentToAdd = {
177
181
  type: "tool-call",
178
- toolCallId: part.toolCallId,
182
+ toolCallId,
179
183
  toolName: part.toolName,
180
184
  args: "",
181
- };
185
+ providerExecuted:
186
+ "providerExecuted" in part ? part.providerExecuted : undefined,
187
+ providerOptions:
188
+ "providerMetadata" in part ? part.providerMetadata : undefined,
189
+ } satisfies Infer<typeof vToolCallPart>;
182
190
  break;
191
+ }
183
192
  case "tool-call-delta":
193
+ case "tool-input-delta":
184
194
  {
185
195
  currentMessage.tool = true;
186
196
  if (lastContent?.type !== "tool-call") {
@@ -189,13 +199,20 @@ export function applyDeltasToStreamMessage(
189
199
  if (typeof lastContent.args !== "string") {
190
200
  throw new Error("Expected args to be a string");
191
201
  }
192
- lastContent.args = (lastContent.args ?? "") + part.argsTextDelta;
202
+ const delta =
203
+ "argsTextDelta" in part ? part.argsTextDelta : part.delta;
204
+ lastContent.args = (lastContent.args ?? "") + delta;
193
205
  }
194
206
  break;
195
- case "tool-call":
207
+ case "tool-call": {
196
208
  currentMessage.tool = true;
197
- contentToAdd = part;
209
+ contentToAdd = toolCallContent(part);
210
+ break;
211
+ }
212
+ case "tool-result": {
213
+ contentToAdd = toolResultContent(part);
198
214
  break;
215
+ }
199
216
  case "reasoning":
200
217
  currentMessage.reasoning =
201
218
  (currentMessage.reasoning ?? "") + part.textDelta;
@@ -208,14 +225,36 @@ export function applyDeltasToStreamMessage(
208
225
  };
209
226
  }
210
227
  break;
228
+ case "reasoning-delta": {
229
+ currentMessage.reasoning = (currentMessage.reasoning ?? "") + part.text;
230
+ if (lastContent?.type === "reasoning") {
231
+ lastContent.text = (lastContent.text ?? "") + part.text;
232
+ } else {
233
+ contentToAdd = {
234
+ type: "reasoning",
235
+ text: part.text,
236
+ providerOptions:
237
+ "providerMetadata" in part ? part.providerMetadata : undefined,
238
+ state: "streaming",
239
+ } satisfies Infer<typeof vReasoningPart>;
240
+ }
241
+ break;
242
+ }
211
243
  case "source":
212
244
  if (!currentMessage.sources) {
213
245
  currentMessage.sources = [];
214
246
  }
215
- currentMessage.sources.push(part.source);
247
+ if ("source" in part) {
248
+ currentMessage.sources.push({
249
+ type: "source",
250
+ ...part.source,
251
+ } satisfies Infer<typeof vSource>);
252
+ } else {
253
+ currentMessage.sources.push(part);
254
+ }
216
255
  break;
217
- case "tool-result":
218
- contentToAdd = part;
256
+ case "raw":
257
+ contentToAdd = part.rawValue;
219
258
  break;
220
259
  default:
221
260
  console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
@@ -236,6 +275,35 @@ export function applyDeltasToStreamMessage(
236
275
  return [newStream, true];
237
276
  }
238
277
 
278
+ function toolCallContent(
279
+ part: Extract<TextStreamPart, { type: "tool-call" }>,
280
+ ): Infer<typeof vToolCallPart> {
281
+ const args = "args" in part ? part.args : part.input;
282
+ return {
283
+ type: "tool-call",
284
+ toolCallId: part.toolCallId,
285
+ toolName: part.toolName,
286
+ args,
287
+ providerExecuted: part.providerExecuted,
288
+ } satisfies Infer<typeof vToolCallPart>;
289
+ }
290
+
291
+ function toolResultContent(
292
+ part: Extract<TextStreamPart, { type: "tool-result" }>,
293
+ ): Infer<typeof vToolResultPart> {
294
+ const result =
295
+ "output" in part ? part.output : "result" in part ? part.result : undefined;
296
+ const args =
297
+ "input" in part ? part.input : "args" in part ? part.args : undefined;
298
+ return {
299
+ type: "tool-result",
300
+ toolCallId: part.toolCallId,
301
+ toolName: part.toolName,
302
+ result,
303
+ args,
304
+ providerExecuted: part.providerExecuted,
305
+ } satisfies Infer<typeof vToolResultPart>;
306
+ }
239
307
  function cloneMessageAndContent(
240
308
  message: Message | undefined,
241
309
  ): Message | undefined {
@@ -272,6 +340,7 @@ function statusFromStreamStatus(
272
340
  }
273
341
  }
274
342
 
343
+ // TODO: share more code with applyDeltasToStreamMessage
275
344
  export function createStreamingMessage(
276
345
  threadId: string,
277
346
  message: StreamMessage,
@@ -288,16 +357,20 @@ export function createStreamingMessage(
288
357
  tool: false,
289
358
  };
290
359
  switch (part.type) {
291
- case "text-delta":
360
+ case "text-delta": {
361
+ const text = "text" in part ? part.text : part.textDelta;
292
362
  return {
293
363
  ...metadata,
294
364
  message: {
295
365
  role: "assistant",
296
- content: [{ type: "text", text: part.textDelta }],
366
+ content: [{ type: "text", text }],
297
367
  },
298
- text: part.textDelta,
368
+ text,
299
369
  };
300
- case "tool-call-streaming-start":
370
+ }
371
+ case "tool-input-start":
372
+ case "tool-call-streaming-start": {
373
+ const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
301
374
  return {
302
375
  ...metadata,
303
376
  tool: true,
@@ -307,58 +380,98 @@ export function createStreamingMessage(
307
380
  {
308
381
  type: "tool-call",
309
382
  toolName: part.toolName,
310
- toolCallId: part.toolCallId,
383
+ toolCallId,
311
384
  args: "", // when it's a string, it's a partial call
385
+ providerExecuted:
386
+ "providerExecuted" in part ? part.providerExecuted : undefined,
387
+ providerOptions:
388
+ "providerMetadata" in part ? part.providerMetadata : undefined,
312
389
  },
313
390
  ],
314
391
  },
315
392
  };
316
- case "reasoning":
393
+ }
394
+ case "tool-input-delta":
395
+ case "tool-call-delta": {
396
+ console.warn("Received tool call delta part first??");
397
+ const delta = "argsTextDelta" in part ? part.argsTextDelta : part.delta;
398
+ const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
399
+ const toolName =
400
+ "toolName" in part ? part.toolName : part.type.slice("tool-".length);
317
401
  return {
318
402
  ...metadata,
403
+ tool: true,
319
404
  message: {
320
405
  role: "assistant",
321
- content: [{ type: "reasoning", text: part.textDelta }],
406
+ content: [
407
+ {
408
+ type: "tool-call",
409
+ toolCallId,
410
+ toolName,
411
+ args: delta,
412
+ },
413
+ ],
322
414
  },
323
- reasoning: part.textDelta,
324
415
  };
325
- case "source":
326
- console.warn("Received source part first??");
416
+ }
417
+ case "tool-call": {
327
418
  return {
328
419
  ...metadata,
329
420
  tool: true,
330
- message: { role: "tool", content: [] },
331
- sources: [part.source],
421
+ message: {
422
+ role: "assistant",
423
+ content: [toolCallContent(part)],
424
+ },
332
425
  };
333
- case "tool-call":
426
+ }
427
+ case "tool-result":
334
428
  return {
335
429
  ...metadata,
336
430
  tool: true,
337
- message: { role: "assistant", content: [part] },
431
+ message: {
432
+ role: "tool",
433
+ content: [toolResultContent(part)],
434
+ },
338
435
  };
339
- case "tool-call-delta":
340
- console.warn("Received tool call delta part first??");
436
+ case "reasoning":
341
437
  return {
342
438
  ...metadata,
343
- tool: true,
344
439
  message: {
345
440
  role: "assistant",
346
- content: [
347
- {
348
- type: "tool-call",
349
- toolCallId: part.toolCallId,
350
- toolName: part.toolName,
351
- args: part.argsTextDelta,
352
- },
353
- ],
441
+ content: [{ type: "reasoning", text: part.textDelta }],
354
442
  },
443
+ reasoning: part.textDelta,
355
444
  };
356
- case "tool-result":
445
+ case "reasoning-delta": {
446
+ return {
447
+ ...metadata,
448
+ message: {
449
+ role: "assistant",
450
+ content: [{ type: "reasoning", text: part.text }],
451
+ },
452
+ reasoning: part.text,
453
+ };
454
+ }
455
+ case "source":
456
+ console.warn("Received source part first??");
357
457
  return {
358
458
  ...metadata,
359
459
  tool: true,
360
- message: { role: "tool", content: [part] },
460
+ message: { role: "tool", content: [] },
461
+ sources: [
462
+ "source" in part
463
+ ? {
464
+ ...part.source,
465
+ type: "source",
466
+ }
467
+ : part,
468
+ ],
361
469
  };
470
+ // case "raw":
471
+ // return {
472
+ // ...metadata,
473
+ // message: { role: "assistant", content: [part.rawValue] },
474
+ // };
362
475
  default:
363
476
  throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
364
477
  }
@@ -46,7 +46,10 @@ export function optimisticallySendMessage(
46
46
  }
47
47
 
48
48
  export function randomUUID() {
49
- if (typeof crypto !== "undefined") {
49
+ if (
50
+ typeof crypto !== "undefined" &&
51
+ typeof crypto.randomUUID === "function"
52
+ ) {
50
53
  return crypto.randomUUID();
51
54
  }
52
55
  return (
@@ -31,7 +31,7 @@ describe("toUIMessages", () => {
31
31
  const uiMessages = toUIMessages(messages);
32
32
  expect(uiMessages).toHaveLength(1);
33
33
  expect(uiMessages[0].role).toBe("user");
34
- expect(uiMessages[0].content).toBe("Hello!");
34
+ expect(uiMessages[0].text).toBe("Hello!");
35
35
  expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "Hello!" });
36
36
  });
37
37
 
@@ -48,10 +48,11 @@ describe("toUIMessages", () => {
48
48
  const uiMessages = toUIMessages(messages);
49
49
  expect(uiMessages).toHaveLength(1);
50
50
  expect(uiMessages[0].role).toBe("assistant");
51
- expect(uiMessages[0].content).toBe("Hi, how can I help?");
51
+ expect(uiMessages[0].text).toBe("Hi, how can I help?");
52
52
  expect(uiMessages[0].parts[0]).toEqual({
53
53
  type: "text",
54
54
  text: "Hi, how can I help?",
55
+ state: "done",
55
56
  });
56
57
  });
57
58
 
@@ -83,13 +84,13 @@ describe("toUIMessages", () => {
83
84
  {
84
85
  type: "file",
85
86
  mimeType: "text/plain",
86
- data: "asdfasdfasdf",
87
+ data: "https://example.com/file.txt",
87
88
  },
88
89
  {
89
90
  type: "tool-call",
90
91
  toolName: "myTool",
91
92
  toolCallId: "call1",
92
- args: "",
93
+ args: "an arg",
93
94
  },
94
95
  ],
95
96
  },
@@ -109,7 +110,6 @@ describe("toUIMessages", () => {
109
110
  },
110
111
  ],
111
112
  },
112
- text: "42",
113
113
  tool: true,
114
114
  }),
115
115
  ];
@@ -121,18 +121,15 @@ describe("toUIMessages", () => {
121
121
  );
122
122
  expect(uiMessages[1].role).toBe("assistant");
123
123
  expect(
124
- uiMessages[1].parts.filter((p) => p.type === "tool-invocation"),
124
+ uiMessages[1].parts.filter((p) => p.type === "tool-myTool"),
125
125
  ).toHaveLength(1);
126
126
  expect(
127
- uiMessages[1].parts.filter((p) => p.type === "tool-invocation")[0]
128
- .toolInvocation,
129
- ).toEqual({
130
- toolName: "myTool",
127
+ uiMessages[1].parts.filter((p) => p.type === "tool-myTool")[0],
128
+ ).toMatchObject({
129
+ type: "tool-myTool",
131
130
  toolCallId: "call1",
132
- args: "",
133
- state: "result",
134
- result: "42",
135
- step: 0,
131
+ state: "output-available",
132
+ output: "42",
136
133
  });
137
134
  });
138
135
 
@@ -167,21 +164,39 @@ describe("toUIMessages", () => {
167
164
  const uiMessages = toUIMessages(messages);
168
165
  expect(uiMessages).toHaveLength(1);
169
166
  expect(uiMessages[0].role).toBe("assistant");
170
- expect(uiMessages[0].content).toBe("Here's one idea. Here's another idea.");
171
- expect(
172
- uiMessages[0].parts.filter((p) => p.type === "reasoning"),
173
- ).toHaveLength(1);
167
+ expect(uiMessages[0].text).toBe("Here's one idea. Here's another idea.");
168
+ expect(uiMessages[0].parts.filter((p) => p.type === "reasoning")).toEqual([
169
+ {
170
+ providerMetadata: undefined,
171
+ state: undefined,
172
+ text: "I'm thinking...",
173
+ type: "reasoning",
174
+ },
175
+ {
176
+ providerMetadata: undefined,
177
+ state: undefined,
178
+ text: "I'm thinking...",
179
+ type: "reasoning",
180
+ },
181
+ ]);
174
182
  expect(uiMessages[0].parts[0].type).toBe("reasoning");
175
183
  assert(uiMessages[0].parts[0].type === "reasoning");
176
- expect(uiMessages[0].parts[0].reasoning).toBe(
177
- "I'm thinking...I'm thinking...",
178
- );
184
+ expect(uiMessages[0].parts[0].text).toBe("I'm thinking...");
185
+ expect(uiMessages[0].parts[1].type).toBe("text");
186
+ assert(uiMessages[0].parts[1].type === "text");
187
+ expect(uiMessages[0].parts[1].text).toBe("Here's one idea.");
188
+ expect(uiMessages[0].parts[2].type).toBe("reasoning");
189
+ assert(uiMessages[0].parts[2].type === "reasoning");
190
+ expect(uiMessages[0].parts[2].text).toBe("I'm thinking...");
179
191
 
180
192
  expect(uiMessages[0].parts.filter((p) => p.type === "text")).toHaveLength(
181
- 1,
193
+ 2,
182
194
  );
183
195
  expect(uiMessages[0].parts.filter((p) => p.type === "text")[0].text).toBe(
184
- "Here's one idea. Here's another idea.",
196
+ "Here's one idea.",
197
+ );
198
+ expect(uiMessages[0].parts.filter((p) => p.type === "text")[1].text).toBe(
199
+ "Here's another idea.",
185
200
  );
186
201
  });
187
202
 
@@ -198,10 +213,58 @@ describe("toUIMessages", () => {
198
213
  const uiMessages = toUIMessages(messages);
199
214
  expect(uiMessages).toHaveLength(1);
200
215
  expect(uiMessages[0].role).toBe("system");
201
- expect(uiMessages[0].content).toBe("System message here");
216
+ expect(uiMessages[0].text).toBe("System message here");
202
217
  expect(uiMessages[0].parts[0]).toEqual({
203
218
  type: "text",
204
219
  text: "System message here",
220
+ state: "done",
221
+ providerMetadata: undefined,
222
+ });
223
+ });
224
+
225
+ it("handles wrapped JSON tool output", () => {
226
+ const messages = [
227
+ baseMessageDoc({
228
+ message: {
229
+ role: "assistant",
230
+ content: [
231
+ {
232
+ type: "tool-call",
233
+ toolName: "myTool",
234
+ toolCallId: "call1",
235
+ args: { query: "test" },
236
+ },
237
+ ],
238
+ },
239
+ tool: true,
240
+ }),
241
+ baseMessageDoc({
242
+ message: {
243
+ role: "tool",
244
+ content: [
245
+ {
246
+ type: "tool-result",
247
+ toolName: "myTool",
248
+ toolCallId: "call1",
249
+ result: {
250
+ type: "json",
251
+ value: { data: "wrapped result", success: true },
252
+ },
253
+ },
254
+ ],
255
+ },
256
+ tool: true,
257
+ }),
258
+ ];
259
+ const uiMessages = toUIMessages(messages);
260
+ expect(uiMessages).toHaveLength(1);
261
+ const toolPart = uiMessages[0].parts.find((p) => p.type === "tool-myTool");
262
+ expect(toolPart).toMatchObject({
263
+ type: "tool-myTool",
264
+ toolCallId: "call1",
265
+ state: "output-available",
266
+ input: { query: "test" },
267
+ output: { data: "wrapped result", success: true }, // Should be unwrapped
205
268
  });
206
269
  });
207
270
 
@@ -215,7 +278,7 @@ describe("toUIMessages", () => {
215
278
  type: "tool-call",
216
279
  toolName: "myTool",
217
280
  toolCallId: "call1",
218
- args: "",
281
+ args: "hi",
219
282
  },
220
283
  ],
221
284
  },
@@ -226,17 +289,12 @@ describe("toUIMessages", () => {
226
289
  expect(uiMessages).toHaveLength(1);
227
290
  expect(uiMessages[0].role).toBe("assistant");
228
291
  expect(
229
- uiMessages[0].parts.filter((p) => p.type === "tool-invocation"),
230
- ).toHaveLength(1);
231
- expect(
232
- uiMessages[0].parts.filter((p) => p.type === "tool-invocation")[0]
233
- .toolInvocation,
234
- ).toEqual({
235
- toolName: "myTool",
292
+ uiMessages[0].parts.filter((p) => p.type === "tool-myTool")[0],
293
+ ).toMatchObject({
294
+ type: "tool-myTool",
236
295
  toolCallId: "call1",
237
- args: "",
238
- state: "call",
239
- step: 0,
296
+ input: "hi",
297
+ state: "input-available",
240
298
  });
241
299
  });
242
300
 
@@ -276,7 +334,7 @@ describe("toUIMessages", () => {
276
334
  expect(uiMessages).toHaveLength(1);
277
335
  expect(uiMessages[0].role).toBe("assistant");
278
336
  // Should have a tool-invocation part
279
- expect(uiMessages[0].parts.some((p) => p.type === "tool-invocation")).toBe(
337
+ expect(uiMessages[0].parts.some((p) => p.type === "tool-myTool")).toBe(
280
338
  true,
281
339
  );
282
340
  });
@@ -299,4 +357,64 @@ describe("toUIMessages", () => {
299
357
  });
300
358
 
301
359
  // Add more tests for array content, tool calls, etc. as needed
360
+
361
+ it("should update tool call state from input-available to output-available", () => {
362
+ const messages = [
363
+ baseMessageDoc({
364
+ message: {
365
+ role: "assistant",
366
+ content: [
367
+ {
368
+ type: "tool-call",
369
+ toolName: "calculator",
370
+ toolCallId: "call1",
371
+ args: { operation: "add", a: 1, b: 2 },
372
+ },
373
+ ],
374
+ },
375
+ tool: true,
376
+ }),
377
+ baseMessageDoc({
378
+ message: {
379
+ role: "tool",
380
+ content: [
381
+ {
382
+ type: "tool-result",
383
+ toolCallId: "call1",
384
+ toolName: "calculator",
385
+ result: { sum: 3 },
386
+ },
387
+ ],
388
+ },
389
+ tool: true,
390
+ }),
391
+ ];
392
+
393
+ const uiMessages = toUIMessages(messages);
394
+
395
+ // Should have one assistant message
396
+ expect(uiMessages).toHaveLength(1);
397
+ expect(uiMessages[0].role).toBe("assistant");
398
+
399
+ // Should have a single tool-calculator part (not separate tool-call and tool-result parts)
400
+ const toolParts = uiMessages[0].parts.filter(
401
+ (p) => p.type === "tool-calculator",
402
+ );
403
+ expect(toolParts).toHaveLength(1);
404
+
405
+ const toolPart = toolParts[0];
406
+ expect(toolPart).toMatchObject({
407
+ type: "tool-calculator",
408
+ toolCallId: "call1",
409
+ state: "output-available",
410
+ input: { operation: "add", a: 1, b: 2 },
411
+ output: { sum: 3 },
412
+ });
413
+
414
+ // Should NOT have a tool-call part (which is what currently happens)
415
+ const toolCallParts = uiMessages[0].parts.filter(
416
+ (p) => p.type === "tool-call",
417
+ );
418
+ expect(toolCallParts).toHaveLength(0);
419
+ });
302
420
  });