@convex-dev/agent 0.2.8-alpha.6 → 0.2.9
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.
- package/dist/UIMessages.d.ts.map +1 -1
- package/dist/UIMessages.js +4 -4
- package/dist/UIMessages.js.map +1 -1
- package/dist/client/definePlaygroundAPI.js +3 -3
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/index.d.ts +4 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +13 -12
- package/dist/client/index.js.map +1 -1
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +15 -13
- package/dist/client/search.js.map +1 -1
- package/dist/client/start.d.ts +1 -1
- package/dist/client/start.d.ts.map +1 -1
- package/dist/client/start.js +1 -1
- package/dist/client/start.js.map +1 -1
- package/dist/client/streaming.d.ts +1 -0
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +13 -3
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/types.d.ts +4 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/mapping.d.ts +5 -5
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +22 -22
- package/dist/mapping.js.map +1 -1
- package/package.json +2 -2
- package/src/UIMessages.ts +4 -8
- package/src/client/definePlaygroundAPI.ts +3 -3
- package/src/client/index.ts +14 -11
- package/src/client/search.ts +15 -13
- package/src/client/start.ts +1 -1
- package/src/client/streaming.ts +18 -7
- package/src/client/types.ts +4 -0
- package/src/deltas.test.ts +262 -0
- package/src/mapping.test.ts +8 -8
- package/src/mapping.ts +28 -28
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/client/mock.json +0 -68
- package/src/client/stream.json +0 -48
package/src/client/index.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { convexToJson, v, type Value } from "convex/values";
|
|
|
36
36
|
import type { threadFieldsSupportingPatch } from "../component/threads.js";
|
|
37
37
|
import { type VectorDimension } from "../component/vector/tables.js";
|
|
38
38
|
import {
|
|
39
|
-
|
|
39
|
+
toModelMessage,
|
|
40
40
|
serializeMessage,
|
|
41
41
|
serializeNewMessagesInStep,
|
|
42
42
|
serializeObjectResult,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
fetchContextMessages,
|
|
68
68
|
generateAndSaveEmbeddings,
|
|
69
69
|
} from "./search.js";
|
|
70
|
-
import {
|
|
70
|
+
import { startGeneration } from "./start.js";
|
|
71
71
|
import {
|
|
72
72
|
compressUIMessageChunks,
|
|
73
73
|
DeltaStreamer,
|
|
@@ -104,7 +104,9 @@ import type {
|
|
|
104
104
|
|
|
105
105
|
export { stepCountIs } from "ai";
|
|
106
106
|
export {
|
|
107
|
-
|
|
107
|
+
toModelMessage,
|
|
108
|
+
//** @deprecated use toModelMessage instead */
|
|
109
|
+
toModelMessage as deserializeMessage,
|
|
108
110
|
guessMimeType,
|
|
109
111
|
serializeDataOrUrl,
|
|
110
112
|
serializeMessage,
|
|
@@ -159,10 +161,12 @@ export {
|
|
|
159
161
|
embedMessages,
|
|
160
162
|
embedMany,
|
|
161
163
|
} from "./search.js";
|
|
164
|
+
export { startGeneration } from "./start.js";
|
|
162
165
|
export {
|
|
163
166
|
DEFAULT_STREAMING_OPTIONS,
|
|
164
167
|
DeltaStreamer,
|
|
165
168
|
abortStream,
|
|
169
|
+
compressUIMessageChunks,
|
|
166
170
|
listStreams,
|
|
167
171
|
syncStreams,
|
|
168
172
|
vStreamMessagesReturnValue,
|
|
@@ -173,6 +177,7 @@ export {
|
|
|
173
177
|
searchThreadTitles,
|
|
174
178
|
updateThreadMetadata,
|
|
175
179
|
} from "./threads.js";
|
|
180
|
+
export type { ContextHandler } from "./types.js";
|
|
176
181
|
export { toUIMessages, fromUIMessages, type UIMessage } from "../UIMessages.js";
|
|
177
182
|
|
|
178
183
|
export type {
|
|
@@ -449,7 +454,7 @@ export class Agent<
|
|
|
449
454
|
getSavedMessages: () => MessageDoc[];
|
|
450
455
|
}> {
|
|
451
456
|
type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
|
|
452
|
-
return
|
|
457
|
+
return startGeneration<T, Tools, CustomCtx>(
|
|
453
458
|
ctx,
|
|
454
459
|
this.component,
|
|
455
460
|
{
|
|
@@ -1496,10 +1501,8 @@ export class Agent<
|
|
|
1496
1501
|
stopWhen: spec?.stopWhen,
|
|
1497
1502
|
...overrides,
|
|
1498
1503
|
...omit(rest, ["storageOptions", "contextOptions", "stream"]),
|
|
1499
|
-
messages: messages?.map(
|
|
1500
|
-
prompt: Array.isArray(prompt)
|
|
1501
|
-
? prompt.map(deserializeMessage)
|
|
1502
|
-
: prompt,
|
|
1504
|
+
messages: messages?.map(toModelMessage),
|
|
1505
|
+
prompt: Array.isArray(prompt) ? prompt.map(toModelMessage) : prompt,
|
|
1503
1506
|
toolChoice: args.toolChoice as ToolChoice<AgentTools>,
|
|
1504
1507
|
} satisfies StreamingTextArgs<AgentTools>;
|
|
1505
1508
|
if (maxSteps) {
|
|
@@ -1573,9 +1576,9 @@ export class Agent<
|
|
|
1573
1576
|
...objectArgs,
|
|
1574
1577
|
...callSettings,
|
|
1575
1578
|
...omit(rest, ["storageOptions", "contextOptions"]),
|
|
1576
|
-
messages: args.messages?.map(
|
|
1579
|
+
messages: args.messages?.map(toModelMessage),
|
|
1577
1580
|
prompt: Array.isArray(args.prompt)
|
|
1578
|
-
? args.prompt.map(
|
|
1581
|
+
? args.prompt.map(toModelMessage)
|
|
1579
1582
|
: args.prompt,
|
|
1580
1583
|
} as GenerateObjectArgs<FlexibleSchema<T>>;
|
|
1581
1584
|
const ctx = (
|
|
@@ -1633,7 +1636,7 @@ export class Agent<
|
|
|
1633
1636
|
handler: async (ctx, args) => {
|
|
1634
1637
|
const { messages } = await this.saveMessages(ctx, {
|
|
1635
1638
|
...args,
|
|
1636
|
-
messages: args.messages.map((m) =>
|
|
1639
|
+
messages: args.messages.map((m) => toModelMessage(m.message)),
|
|
1637
1640
|
metadata: args.messages.map(({ message: _, ...m }) => m),
|
|
1638
1641
|
skipEmbeddings: true,
|
|
1639
1642
|
});
|
package/src/client/search.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
RunQueryCtx,
|
|
29
29
|
} from "./types.js";
|
|
30
30
|
import { inlineMessagesFiles } from "./files.js";
|
|
31
|
-
import {
|
|
31
|
+
import { toModelMessage } from "../mapping.js";
|
|
32
32
|
|
|
33
33
|
const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
|
|
34
34
|
// 10k characters should be more than enough for most cases, and stays under
|
|
@@ -525,20 +525,28 @@ export async function fetchContextWithPrompt(
|
|
|
525
525
|
const search = searchMessages
|
|
526
526
|
.map((m) => m.message)
|
|
527
527
|
.filter((m) => !!m)
|
|
528
|
-
.map(
|
|
528
|
+
.map(toModelMessage);
|
|
529
529
|
const recent = prePromptDocs
|
|
530
530
|
.map((m) => m.message)
|
|
531
531
|
.filter((m) => !!m)
|
|
532
|
-
.map(
|
|
533
|
-
const inputMessages = messages.map(
|
|
534
|
-
const inputPrompt = promptArray.map(
|
|
532
|
+
.map(toModelMessage);
|
|
533
|
+
const inputMessages = messages.map(toModelMessage);
|
|
534
|
+
const inputPrompt = promptArray.map(toModelMessage);
|
|
535
535
|
const existingResponses = existingResponseDocs
|
|
536
536
|
.map((m) => m.message)
|
|
537
537
|
.filter((m) => !!m)
|
|
538
|
-
.map(
|
|
538
|
+
.map(toModelMessage);
|
|
539
539
|
|
|
540
|
+
const allMessages = [
|
|
541
|
+
...search,
|
|
542
|
+
...recent,
|
|
543
|
+
...inputMessages,
|
|
544
|
+
...inputPrompt,
|
|
545
|
+
...existingResponses,
|
|
546
|
+
];
|
|
540
547
|
let processedMessages = args.contextHandler
|
|
541
548
|
? await args.contextHandler(ctx, {
|
|
549
|
+
allMessages,
|
|
542
550
|
search,
|
|
543
551
|
recent,
|
|
544
552
|
inputMessages,
|
|
@@ -547,13 +555,7 @@ export async function fetchContextWithPrompt(
|
|
|
547
555
|
userId,
|
|
548
556
|
threadId,
|
|
549
557
|
})
|
|
550
|
-
:
|
|
551
|
-
...search,
|
|
552
|
-
...recent,
|
|
553
|
-
...inputMessages,
|
|
554
|
-
...inputPrompt,
|
|
555
|
-
...existingResponses,
|
|
556
|
-
];
|
|
558
|
+
: allMessages;
|
|
557
559
|
|
|
558
560
|
// Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
|
|
559
561
|
if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
|
package/src/client/start.ts
CHANGED
|
@@ -32,7 +32,7 @@ import type { Agent } from "./index.js";
|
|
|
32
32
|
import { omit } from "convex-helpers";
|
|
33
33
|
import { saveInputMessages } from "./saveInputMessages.js";
|
|
34
34
|
|
|
35
|
-
export async function
|
|
35
|
+
export async function startGeneration<
|
|
36
36
|
T,
|
|
37
37
|
Tools extends ToolSet = ToolSet,
|
|
38
38
|
CustomCtx extends object = object,
|
package/src/client/streaming.ts
CHANGED
|
@@ -198,7 +198,7 @@ export function mergeTransforms<TOOLS extends ToolSet>(
|
|
|
198
198
|
* optimize the data in transit.
|
|
199
199
|
*/
|
|
200
200
|
export class DeltaStreamer<T> {
|
|
201
|
-
|
|
201
|
+
streamId: string | undefined;
|
|
202
202
|
public readonly config: {
|
|
203
203
|
throttleMs: number;
|
|
204
204
|
onAsyncAbort: (reason: string) => Promise<void>;
|
|
@@ -255,16 +255,27 @@ export class DeltaStreamer<T> {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
// Avoid race conditions by only creating once
|
|
259
|
+
#creatingStreamIdPromise: Promise<string> | undefined;
|
|
260
|
+
public async getStreamId() {
|
|
261
|
+
if (this.streamId) {
|
|
262
|
+
return this.streamId;
|
|
263
|
+
}
|
|
264
|
+
if (this.#creatingStreamIdPromise) {
|
|
265
|
+
return this.#creatingStreamIdPromise;
|
|
266
|
+
}
|
|
267
|
+
this.#creatingStreamIdPromise = this.ctx.runMutation(
|
|
268
|
+
this.component.streams.create,
|
|
269
|
+
this.metadata,
|
|
270
|
+
);
|
|
271
|
+
this.streamId = await this.#creatingStreamIdPromise;
|
|
272
|
+
}
|
|
273
|
+
|
|
258
274
|
public async addParts(parts: T[]) {
|
|
259
275
|
if (this.abortController.signal.aborted) {
|
|
260
276
|
return;
|
|
261
277
|
}
|
|
262
|
-
|
|
263
|
-
this.streamId = await this.ctx.runMutation(
|
|
264
|
-
this.component.streams.create,
|
|
265
|
-
this.metadata,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
278
|
+
await this.getStreamId();
|
|
268
279
|
this.#nextParts.push(...parts);
|
|
269
280
|
if (
|
|
270
281
|
!this.#ongoingWrite &&
|
package/src/client/types.ts
CHANGED
package/src/deltas.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
blankUIMessage,
|
|
4
|
+
combineUIMessages,
|
|
4
5
|
deriveUIMessagesFromTextStreamParts,
|
|
5
6
|
updateFromTextStreamParts,
|
|
6
7
|
updateFromUIMessageChunks,
|
|
@@ -534,3 +535,264 @@ describe("mergeDeltas", () => {
|
|
|
534
535
|
]);
|
|
535
536
|
});
|
|
536
537
|
});
|
|
538
|
+
|
|
539
|
+
describe("combineUIMessages", () => {
|
|
540
|
+
it("combines messages spanning two pages correctly", () => {
|
|
541
|
+
const message1 = {
|
|
542
|
+
id: "msg1",
|
|
543
|
+
key: "msg1-key",
|
|
544
|
+
_creationTime: Date.now(),
|
|
545
|
+
order: 1,
|
|
546
|
+
stepOrder: 1,
|
|
547
|
+
status: "success" as const,
|
|
548
|
+
role: "assistant" as const,
|
|
549
|
+
text: "",
|
|
550
|
+
parts: [
|
|
551
|
+
{
|
|
552
|
+
type: "dynamic-tool" as const,
|
|
553
|
+
state: "input-available" as const,
|
|
554
|
+
toolCallId: "call_123",
|
|
555
|
+
toolName: "calculator",
|
|
556
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const message2 = {
|
|
562
|
+
id: "msg2",
|
|
563
|
+
key: "msg2-key",
|
|
564
|
+
_creationTime: Date.now() + 1,
|
|
565
|
+
order: 1,
|
|
566
|
+
stepOrder: 2,
|
|
567
|
+
status: "success" as const,
|
|
568
|
+
role: "assistant" as const,
|
|
569
|
+
text: "The result is 5.",
|
|
570
|
+
parts: [
|
|
571
|
+
{
|
|
572
|
+
type: "tool-calculator" as const,
|
|
573
|
+
state: "output-available" as const,
|
|
574
|
+
toolCallId: "call_123",
|
|
575
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
576
|
+
output: { result: 5 },
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
type: "text" as const,
|
|
580
|
+
text: "The result is 5.",
|
|
581
|
+
state: "done" as const,
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const combined = combineUIMessages([message1, message2]);
|
|
587
|
+
|
|
588
|
+
expect(combined).toHaveLength(1);
|
|
589
|
+
expect(combined[0].role).toBe("assistant");
|
|
590
|
+
expect(combined[0].text).toBe("The result is 5.");
|
|
591
|
+
expect(combined[0].parts).toHaveLength(2);
|
|
592
|
+
|
|
593
|
+
const toolPart = combined[0].parts.find(p => p.type === "tool-calculator");
|
|
594
|
+
expect(toolPart).toMatchObject({
|
|
595
|
+
type: "tool-calculator",
|
|
596
|
+
state: "output-available",
|
|
597
|
+
toolCallId: "call_123",
|
|
598
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
599
|
+
output: { result: 5 },
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const textPart = combined[0].parts.find(p => p.type === "text");
|
|
603
|
+
expect(textPart).toMatchObject({
|
|
604
|
+
type: "text",
|
|
605
|
+
text: "The result is 5.",
|
|
606
|
+
state: "done",
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("preserves separate messages with different roles", () => {
|
|
611
|
+
const userMessage = {
|
|
612
|
+
id: "user1",
|
|
613
|
+
key: "user1-key",
|
|
614
|
+
_creationTime: Date.now(),
|
|
615
|
+
order: 1,
|
|
616
|
+
stepOrder: 0,
|
|
617
|
+
status: "success" as const,
|
|
618
|
+
role: "user" as const,
|
|
619
|
+
text: "Calculate 2 + 3",
|
|
620
|
+
parts: [{ type: "text" as const, text: "Calculate 2 + 3" }],
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const assistantMessage = {
|
|
624
|
+
id: "assistant1",
|
|
625
|
+
key: "assistant1-key",
|
|
626
|
+
_creationTime: Date.now() + 1,
|
|
627
|
+
order: 2,
|
|
628
|
+
stepOrder: 0,
|
|
629
|
+
status: "success" as const,
|
|
630
|
+
role: "assistant" as const,
|
|
631
|
+
text: "The result is 5.",
|
|
632
|
+
parts: [{ type: "text" as const, text: "The result is 5.", state: "done" as const }],
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const combined = combineUIMessages([userMessage, assistantMessage]);
|
|
636
|
+
|
|
637
|
+
expect(combined).toHaveLength(2);
|
|
638
|
+
expect(combined[0]).toEqual(userMessage);
|
|
639
|
+
expect(combined[1]).toEqual(assistantMessage);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("combines multiple tool calls across pages", () => {
|
|
643
|
+
const message1 = {
|
|
644
|
+
id: "msg1",
|
|
645
|
+
key: "msg1-key",
|
|
646
|
+
_creationTime: Date.now(),
|
|
647
|
+
order: 1,
|
|
648
|
+
stepOrder: 1,
|
|
649
|
+
status: "success" as const,
|
|
650
|
+
role: "assistant" as const,
|
|
651
|
+
text: "",
|
|
652
|
+
parts: [
|
|
653
|
+
{
|
|
654
|
+
type: "dynamic-tool" as const,
|
|
655
|
+
state: "input-available" as const,
|
|
656
|
+
toolCallId: "call_1",
|
|
657
|
+
toolName: "calculator",
|
|
658
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
type: "dynamic-tool" as const,
|
|
662
|
+
state: "input-available" as const,
|
|
663
|
+
toolCallId: "call_2",
|
|
664
|
+
toolName: "formatter",
|
|
665
|
+
input: { text: "result" },
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const message2 = {
|
|
671
|
+
id: "msg2",
|
|
672
|
+
key: "msg2-key",
|
|
673
|
+
_creationTime: Date.now() + 1,
|
|
674
|
+
order: 1,
|
|
675
|
+
stepOrder: 2,
|
|
676
|
+
status: "success" as const,
|
|
677
|
+
role: "assistant" as const,
|
|
678
|
+
text: "The formatted result is: 5",
|
|
679
|
+
parts: [
|
|
680
|
+
{
|
|
681
|
+
type: "tool-calculator" as const,
|
|
682
|
+
state: "output-available" as const,
|
|
683
|
+
toolCallId: "call_1",
|
|
684
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
685
|
+
output: { result: 5 },
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
type: "tool-formatter" as const,
|
|
689
|
+
state: "output-available" as const,
|
|
690
|
+
toolCallId: "call_2",
|
|
691
|
+
input: { text: "result" },
|
|
692
|
+
output: { formatted: "The formatted result is: 5" },
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
type: "text" as const,
|
|
696
|
+
text: "The formatted result is: 5",
|
|
697
|
+
state: "done" as const,
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const combined = combineUIMessages([message1, message2]);
|
|
703
|
+
|
|
704
|
+
expect(combined).toHaveLength(1);
|
|
705
|
+
expect(combined[0].role).toBe("assistant");
|
|
706
|
+
expect(combined[0].text).toBe("The formatted result is: 5");
|
|
707
|
+
expect(combined[0].parts).toHaveLength(3);
|
|
708
|
+
|
|
709
|
+
const calculatorPart = combined[0].parts.find(p =>
|
|
710
|
+
p.type === "tool-calculator" && "toolCallId" in p && p.toolCallId === "call_1"
|
|
711
|
+
);
|
|
712
|
+
expect(calculatorPart).toMatchObject({
|
|
713
|
+
type: "tool-calculator",
|
|
714
|
+
state: "output-available",
|
|
715
|
+
toolCallId: "call_1",
|
|
716
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
717
|
+
output: { result: 5 },
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const formatterPart = combined[0].parts.find(p =>
|
|
721
|
+
p.type === "tool-formatter" && "toolCallId" in p && p.toolCallId === "call_2"
|
|
722
|
+
);
|
|
723
|
+
expect(formatterPart).toMatchObject({
|
|
724
|
+
type: "tool-formatter",
|
|
725
|
+
state: "output-available",
|
|
726
|
+
toolCallId: "call_2",
|
|
727
|
+
input: { text: "result" },
|
|
728
|
+
output: { formatted: "The formatted result is: 5" },
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("handles tool call without corresponding output", () => {
|
|
733
|
+
const message1 = {
|
|
734
|
+
id: "msg1",
|
|
735
|
+
key: "msg1-key",
|
|
736
|
+
_creationTime: Date.now(),
|
|
737
|
+
order: 1,
|
|
738
|
+
stepOrder: 1,
|
|
739
|
+
status: "success" as const,
|
|
740
|
+
role: "assistant" as const,
|
|
741
|
+
text: "",
|
|
742
|
+
parts: [
|
|
743
|
+
{
|
|
744
|
+
type: "dynamic-tool" as const,
|
|
745
|
+
state: "input-available" as const,
|
|
746
|
+
toolCallId: "call_orphan",
|
|
747
|
+
toolName: "calculator",
|
|
748
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const message2 = {
|
|
754
|
+
id: "msg2",
|
|
755
|
+
key: "msg2-key",
|
|
756
|
+
_creationTime: Date.now() + 1,
|
|
757
|
+
order: 1,
|
|
758
|
+
stepOrder: 2,
|
|
759
|
+
status: "success" as const,
|
|
760
|
+
role: "assistant" as const,
|
|
761
|
+
text: "Still processing...",
|
|
762
|
+
parts: [
|
|
763
|
+
{
|
|
764
|
+
type: "text" as const,
|
|
765
|
+
text: "Still processing...",
|
|
766
|
+
state: "done" as const,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const combined = combineUIMessages([message1, message2]);
|
|
772
|
+
|
|
773
|
+
expect(combined).toHaveLength(1);
|
|
774
|
+
expect(combined[0].role).toBe("assistant");
|
|
775
|
+
expect(combined[0].text).toBe("Still processing...");
|
|
776
|
+
expect(combined[0].parts).toHaveLength(2);
|
|
777
|
+
|
|
778
|
+
const toolPart = combined[0].parts.find(p =>
|
|
779
|
+
p.type === "dynamic-tool"
|
|
780
|
+
);
|
|
781
|
+
expect(toolPart).toMatchObject({
|
|
782
|
+
type: "dynamic-tool",
|
|
783
|
+
state: "input-available",
|
|
784
|
+
toolCallId: "call_orphan",
|
|
785
|
+
toolName: "calculator",
|
|
786
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const textPart = combined[0].parts.find(p =>
|
|
790
|
+
p.type === "text"
|
|
791
|
+
);
|
|
792
|
+
expect(textPart).toMatchObject({
|
|
793
|
+
type: "text",
|
|
794
|
+
text: "Still processing...",
|
|
795
|
+
state: "done",
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
});
|
package/src/mapping.test.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { describe, test, expect } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
guessMimeType,
|
|
4
4
|
serializeDataOrUrl,
|
|
5
|
-
|
|
5
|
+
toModelMessageDataOrUrl,
|
|
6
6
|
serializeMessage,
|
|
7
|
-
|
|
7
|
+
toModelMessage,
|
|
8
8
|
serializeContent,
|
|
9
|
-
|
|
9
|
+
toModelMessageContent,
|
|
10
10
|
} from "./mapping.js";
|
|
11
11
|
import { api } from "./component/_generated/api.js";
|
|
12
12
|
import type { AgentComponent, ActionCtx } from "./client/types.js";
|
|
@@ -57,8 +57,8 @@ describe("mapping", () => {
|
|
|
57
57
|
const ser = serializeDataOrUrl(arr);
|
|
58
58
|
expect(ser).toBeInstanceOf(ArrayBuffer);
|
|
59
59
|
expect(new Uint8Array(ser as ArrayBuffer)).toEqual(arr);
|
|
60
|
-
//
|
|
61
|
-
const deser =
|
|
60
|
+
// toModelMessageDataOrUrl should return the same ArrayBuffer
|
|
61
|
+
const deser = toModelMessageDataOrUrl(ser);
|
|
62
62
|
expect(deser).toBeInstanceOf(ArrayBuffer);
|
|
63
63
|
expect(new Uint8Array(deser as ArrayBuffer)).toEqual(arr);
|
|
64
64
|
});
|
|
@@ -83,7 +83,7 @@ describe("mapping", () => {
|
|
|
83
83
|
const { message: ser } = await serializeMessage(ctx, component, message);
|
|
84
84
|
// Use is for type validation
|
|
85
85
|
expect(validate(vMessage, ser)).toBeTruthy();
|
|
86
|
-
const round =
|
|
86
|
+
const round = toModelMessage(ser);
|
|
87
87
|
expect(round).toEqual(message);
|
|
88
88
|
});
|
|
89
89
|
|
|
@@ -97,7 +97,7 @@ describe("mapping", () => {
|
|
|
97
97
|
value: "hello world",
|
|
98
98
|
},
|
|
99
99
|
} satisfies ToolResultPart;
|
|
100
|
-
const [result] =
|
|
100
|
+
const [result] = toModelMessageContent([toolResult]);
|
|
101
101
|
expect(result).toMatchObject(toolResult);
|
|
102
102
|
const {
|
|
103
103
|
content: [roundtrip],
|
|
@@ -123,7 +123,7 @@ describe("mapping", () => {
|
|
|
123
123
|
value: "hello world",
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
|
-
const [deserialized] =
|
|
126
|
+
const [deserialized] = toModelMessageContent([toolResult]);
|
|
127
127
|
expect(deserialized).toMatchObject(expected);
|
|
128
128
|
const {
|
|
129
129
|
content: [serialized],
|
package/src/mapping.ts
CHANGED
|
@@ -125,12 +125,12 @@ export async function serializeOrThrow(
|
|
|
125
125
|
} as SerializedMessage;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export function
|
|
128
|
+
export function toModelMessage(
|
|
129
129
|
message: SerializedMessage | ModelMessage,
|
|
130
130
|
): ModelMessage {
|
|
131
131
|
return {
|
|
132
132
|
...message,
|
|
133
|
-
content:
|
|
133
|
+
content: toModelMessageContent(message.content),
|
|
134
134
|
} as ModelMessage;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -144,7 +144,7 @@ export function serializeUsage(usage: LanguageModelUsage): Usage {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
export function
|
|
147
|
+
export function toModelMessageUsage(usage: Usage): LanguageModelUsage {
|
|
148
148
|
return {
|
|
149
149
|
inputTokens: usage.promptTokens,
|
|
150
150
|
outputTokens: usage.completionTokens,
|
|
@@ -168,7 +168,7 @@ export function serializeWarnings(
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
export function
|
|
171
|
+
export function toModelMessageWarnings(
|
|
172
172
|
warnings: MessageWithMetadata["warnings"],
|
|
173
173
|
): CallWarning[] | undefined {
|
|
174
174
|
// We don't need to do anythign here for now
|
|
@@ -262,18 +262,18 @@ export async function serializeContent(
|
|
|
262
262
|
return { content };
|
|
263
263
|
}
|
|
264
264
|
const fileIds: string[] = [];
|
|
265
|
-
const metadata: {
|
|
266
|
-
providerOptions?: ProviderOptions;
|
|
267
|
-
providerMetadata?: ProviderMetadata;
|
|
268
|
-
} = {};
|
|
269
|
-
if ("providerOptions" in content) {
|
|
270
|
-
metadata.providerOptions = content.providerOptions as ProviderOptions;
|
|
271
|
-
}
|
|
272
|
-
if ("providerMetadata" in content) {
|
|
273
|
-
metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
|
|
274
|
-
}
|
|
275
265
|
const serialized = await Promise.all(
|
|
276
266
|
content.map(async (part) => {
|
|
267
|
+
const metadata: {
|
|
268
|
+
providerOptions?: ProviderOptions;
|
|
269
|
+
providerMetadata?: ProviderMetadata;
|
|
270
|
+
} = {};
|
|
271
|
+
if ("providerOptions" in part) {
|
|
272
|
+
metadata.providerOptions = part.providerOptions as ProviderOptions;
|
|
273
|
+
}
|
|
274
|
+
if ("providerMetadata" in part) {
|
|
275
|
+
metadata.providerMetadata = part.providerMetadata as ProviderMetadata;
|
|
276
|
+
}
|
|
277
277
|
switch (part.type) {
|
|
278
278
|
case "text": {
|
|
279
279
|
return {
|
|
@@ -371,17 +371,17 @@ export function fromModelMessageContent(content: Content): Message["content"] {
|
|
|
371
371
|
if (typeof content === "string") {
|
|
372
372
|
return content;
|
|
373
373
|
}
|
|
374
|
-
const metadata: {
|
|
375
|
-
providerOptions?: ProviderOptions;
|
|
376
|
-
providerMetadata?: ProviderMetadata;
|
|
377
|
-
} = {};
|
|
378
|
-
if ("providerOptions" in content) {
|
|
379
|
-
metadata.providerOptions = content.providerOptions as ProviderOptions;
|
|
380
|
-
}
|
|
381
|
-
if ("providerMetadata" in content) {
|
|
382
|
-
metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
|
|
383
|
-
}
|
|
384
374
|
return content.map((part) => {
|
|
375
|
+
const metadata: {
|
|
376
|
+
providerOptions?: ProviderOptions;
|
|
377
|
+
providerMetadata?: ProviderMetadata;
|
|
378
|
+
} = {};
|
|
379
|
+
if ("providerOptions" in part) {
|
|
380
|
+
metadata.providerOptions = part.providerOptions as ProviderOptions;
|
|
381
|
+
}
|
|
382
|
+
if ("providerMetadata" in part) {
|
|
383
|
+
metadata.providerMetadata = part.providerMetadata as ProviderMetadata;
|
|
384
|
+
}
|
|
385
385
|
switch (part.type) {
|
|
386
386
|
case "text":
|
|
387
387
|
return part satisfies Infer<typeof vTextPart>;
|
|
@@ -424,7 +424,7 @@ export function fromModelMessageContent(content: Content): Message["content"] {
|
|
|
424
424
|
}) as Message["content"];
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
export function
|
|
427
|
+
export function toModelMessageContent(
|
|
428
428
|
content: SerializedContent | ModelMessage["content"],
|
|
429
429
|
): Content {
|
|
430
430
|
if (typeof content === "string") {
|
|
@@ -451,14 +451,14 @@ export function deserializeContent(
|
|
|
451
451
|
case "image":
|
|
452
452
|
return {
|
|
453
453
|
type: part.type,
|
|
454
|
-
image:
|
|
454
|
+
image: toModelMessageDataOrUrl(part.image),
|
|
455
455
|
mediaType: getMimeOrMediaType(part),
|
|
456
456
|
...metadata,
|
|
457
457
|
} satisfies ImagePart;
|
|
458
458
|
case "file":
|
|
459
459
|
return {
|
|
460
460
|
type: part.type,
|
|
461
|
-
data:
|
|
461
|
+
data: toModelMessageDataOrUrl(part.data),
|
|
462
462
|
filename: part.filename,
|
|
463
463
|
mediaType: getMimeOrMediaType(part)!,
|
|
464
464
|
...metadata,
|
|
@@ -636,7 +636,7 @@ export function serializeDataOrUrl(
|
|
|
636
636
|
) as ArrayBuffer;
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
export function
|
|
639
|
+
export function toModelMessageDataOrUrl(
|
|
640
640
|
urlOrString: string | ArrayBuffer | URL | DataContent,
|
|
641
641
|
): URL | DataContent {
|
|
642
642
|
if (urlOrString instanceof URL) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":client/index.test.ts",{"duration":129.9694999999997,"failed":false}],[":component/messages.test.ts",{"duration":191.73391600000014,"failed":false}],[":component/users.test.ts",{"duration":545.7272500000001,"failed":false}],[":mapping.test.ts",{"duration":
|
|
1
|
+
{"version":"3.2.4","results":[[":client/index.test.ts",{"duration":129.9694999999997,"failed":false}],[":component/messages.test.ts",{"duration":191.73391600000014,"failed":false}],[":component/users.test.ts",{"duration":545.7272500000001,"failed":false}],[":mapping.test.ts",{"duration":28.127082999999402,"failed":false}],[":react/deltas.test.ts",{"duration":5.8497500000000855,"failed":false}],[":react/toUIMessages.test.ts",{"duration":6.495208000000048,"failed":false}],[":validators.test.ts",{"duration":1.0037499999999682,"failed":false}],[":deltas.test.ts",{"duration":0,"failed":false}],[":client/streaming.test.ts",{"duration":177.87866699695587,"failed":false}]]}
|