@convex-dev/agent 0.2.6 → 0.2.8-alpha.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.
- package/dist/UIMessages.d.ts +43 -0
- package/dist/UIMessages.d.ts.map +1 -0
- package/dist/{react/toUIMessages.js → UIMessages.js} +97 -4
- package/dist/UIMessages.js.map +1 -0
- package/dist/client/createTool.js +1 -1
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/index.d.ts +39 -19
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +22 -19
- package/dist/client/index.js.map +1 -1
- package/dist/client/mockModel.d.ts +2 -0
- package/dist/client/mockModel.d.ts.map +1 -1
- package/dist/client/mockModel.js +11 -7
- package/dist/client/mockModel.js.map +1 -1
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +2 -0
- package/dist/client/search.js.map +1 -1
- package/dist/client/start.d.ts +1 -0
- package/dist/client/start.d.ts.map +1 -1
- package/dist/client/start.js +0 -1
- package/dist/client/start.js.map +1 -1
- package/dist/client/streaming.d.ts +23 -4
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +32 -6
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/types.d.ts +19 -13
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/messages.js +1 -1
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +10 -4
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +4 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +2 -4
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +42 -43
- package/dist/component/streams.js.map +1 -1
- package/dist/deltas.d.ts +48 -0
- package/dist/deltas.d.ts.map +1 -0
- package/dist/deltas.js +468 -0
- package/dist/deltas.js.map +1 -0
- package/dist/react/index.d.ts +4 -77
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +4 -178
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts +36 -3
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +37 -5
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/types.d.ts +4 -18
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useStreamingUIMessages.d.ts +22 -0
- package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/react/useStreamingUIMessages.js +204 -0
- package/dist/react/useStreamingUIMessages.js.map +1 -0
- package/dist/react/useThreadMessages.d.ts +103 -0
- package/dist/react/useThreadMessages.d.ts.map +1 -0
- package/dist/react/useThreadMessages.js +139 -0
- package/dist/react/useThreadMessages.js.map +1 -0
- package/dist/react/useUIMessages.d.ts +91 -0
- package/dist/react/useUIMessages.d.ts.map +1 -0
- package/dist/react/useUIMessages.js +115 -0
- package/dist/react/useUIMessages.js.map +1 -0
- package/dist/validators.d.ts +3 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +1 -0
- package/dist/validators.js.map +1 -1
- package/package.json +4 -4
- package/src/{react/toUIMessages.ts → UIMessages.ts} +132 -15
- package/src/client/createTool.ts +1 -1
- package/src/client/index.ts +60 -41
- package/src/client/mockModel.ts +13 -8
- package/src/client/search.ts +2 -0
- package/src/client/start.ts +2 -1
- package/src/client/streaming.test.ts +186 -0
- package/src/client/streaming.ts +50 -9
- package/src/client/types.ts +19 -21
- package/src/component/_generated/api.d.ts +2 -0
- package/src/component/messages.ts +1 -1
- package/src/component/schema.ts +6 -0
- package/src/component/streams.ts +49 -66
- package/src/{react/deltas.test.ts → deltas.test.ts} +57 -46
- package/src/deltas.ts +589 -0
- package/src/{react/fromUIMessages.test.ts → fromUIMessages.test.ts} +108 -43
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/react/index.ts +8 -250
- package/src/react/optimisticallySendMessage.ts +57 -8
- package/src/react/types.ts +6 -39
- package/src/react/useStreamingUIMessages.ts +294 -0
- package/src/react/useThreadMessages.ts +251 -0
- package/src/react/useUIMessages.ts +206 -0
- package/src/{react/toUIMessages.test.ts → toUIMessages.test.ts} +3 -3
- package/src/validators.ts +3 -0
- package/dist/client/textStreamParts.d.ts +0 -5
- package/dist/client/textStreamParts.d.ts.map +0 -1
- package/dist/client/textStreamParts.js +0 -44
- package/dist/client/textStreamParts.js.map +0 -1
- package/dist/react/deltas.d.ts +0 -27
- package/dist/react/deltas.d.ts.map +0 -1
- package/dist/react/deltas.js +0 -627
- package/dist/react/deltas.js.map +0 -1
- package/dist/react/fromUIMessages.d.ts +0 -13
- package/dist/react/fromUIMessages.d.ts.map +0 -1
- package/dist/react/fromUIMessages.js +0 -70
- package/dist/react/fromUIMessages.js.map +0 -1
- package/dist/react/toUIMessages.d.ts +0 -19
- package/dist/react/toUIMessages.d.ts.map +0 -1
- package/dist/react/toUIMessages.js.map +0 -1
- package/src/client/textStreamParts.ts +0 -62
- package/src/react/deltas.ts +0 -774
- package/src/react/fromUIMessages.ts +0 -85
package/src/client/start.ts
CHANGED
|
@@ -21,7 +21,6 @@ import type {
|
|
|
21
21
|
Options,
|
|
22
22
|
UserActionCtx,
|
|
23
23
|
} from "./types.js";
|
|
24
|
-
import { saveMessages } from "./messages.js";
|
|
25
24
|
import type { Message, MessageDoc } from "../validators.js";
|
|
26
25
|
import {
|
|
27
26
|
getModelName,
|
|
@@ -105,6 +104,7 @@ export async function start<
|
|
|
105
104
|
system?: string;
|
|
106
105
|
model: LanguageModel;
|
|
107
106
|
messages: ModelMessage[];
|
|
107
|
+
prompt?: never;
|
|
108
108
|
tools?: Tools;
|
|
109
109
|
} & CallSettings;
|
|
110
110
|
order: number;
|
|
@@ -200,6 +200,7 @@ export async function start<
|
|
|
200
200
|
} as T & {
|
|
201
201
|
model: LanguageModel;
|
|
202
202
|
messages: ModelMessage[];
|
|
203
|
+
prompt?: never;
|
|
203
204
|
tools?: Tools;
|
|
204
205
|
_internal?: { generateId?: IdGenerator };
|
|
205
206
|
} & CallSettings;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { createThread } from "./index.js";
|
|
3
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
|
+
import { streamText } from "ai";
|
|
5
|
+
import { components, initConvexTest } from "./setup.test.js";
|
|
6
|
+
import { mockModel } from "./mockModel.js";
|
|
7
|
+
import { DeltaStreamer } from "./streaming.js";
|
|
8
|
+
import { compressUIMessageChunks, getParts } from "../deltas.js";
|
|
9
|
+
import type { TestConvex } from "convex-test";
|
|
10
|
+
|
|
11
|
+
const defaultTestOptions = {
|
|
12
|
+
throttleMs: 0,
|
|
13
|
+
abortSignal: undefined,
|
|
14
|
+
compress: null,
|
|
15
|
+
onAsyncAbort: async () => {
|
|
16
|
+
throw new Error("unexpected");
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const testMetadata = {
|
|
21
|
+
order: 0,
|
|
22
|
+
stepOrder: 0,
|
|
23
|
+
agentName: "test agent",
|
|
24
|
+
model: "test model",
|
|
25
|
+
provider: "test provider",
|
|
26
|
+
providerOptions: {},
|
|
27
|
+
format: "UIMessageChunk" as const,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("DeltaStreamer", () => {
|
|
31
|
+
let t: TestConvex<SchemaDefinition<GenericSchema, boolean>>;
|
|
32
|
+
let threadId: string;
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
t = initConvexTest();
|
|
35
|
+
await t.run(async (ctx) => {
|
|
36
|
+
threadId = await createThread(ctx, components.agent, {});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
test("should save chunks via DeltaStreamer", async () => {
|
|
40
|
+
await t.run(async (ctx) => {
|
|
41
|
+
const streamer = new DeltaStreamer(
|
|
42
|
+
components.agent,
|
|
43
|
+
ctx,
|
|
44
|
+
{ ...defaultTestOptions },
|
|
45
|
+
{ ...testMetadata, threadId },
|
|
46
|
+
);
|
|
47
|
+
const result = streamText({
|
|
48
|
+
model: mockModel(),
|
|
49
|
+
prompt: "Test prompt",
|
|
50
|
+
});
|
|
51
|
+
await streamer.consumeStream(result.toUIMessageStream());
|
|
52
|
+
const streamId = streamer.streamId!;
|
|
53
|
+
expect(streamId).toBeDefined();
|
|
54
|
+
const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
|
|
55
|
+
threadId,
|
|
56
|
+
cursors: [{ cursor: 0, streamId }],
|
|
57
|
+
});
|
|
58
|
+
const { parts } = getParts(deltas);
|
|
59
|
+
const stream = result.toUIMessageStream();
|
|
60
|
+
for await (const part of stream) {
|
|
61
|
+
const expected = parts.shift();
|
|
62
|
+
expect(part).toEqual(expected);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
test("should save all parts when throttleMs is 0", async () => {
|
|
67
|
+
await t.run(async (ctx) => {
|
|
68
|
+
const streamer = new DeltaStreamer(
|
|
69
|
+
components.agent,
|
|
70
|
+
ctx,
|
|
71
|
+
{ ...defaultTestOptions, throttleMs: 0 },
|
|
72
|
+
{ ...testMetadata, threadId },
|
|
73
|
+
);
|
|
74
|
+
const result = streamText({
|
|
75
|
+
model: mockModel({
|
|
76
|
+
content: [
|
|
77
|
+
// The mockModel splits these into deltas based on spaces
|
|
78
|
+
{ type: "text", text: "A B C" },
|
|
79
|
+
{ type: "reasoning", text: "D E F" },
|
|
80
|
+
],
|
|
81
|
+
}),
|
|
82
|
+
prompt: "Test prompt",
|
|
83
|
+
});
|
|
84
|
+
await streamer.consumeStream(result.toUIMessageStream());
|
|
85
|
+
const streamId = streamer.streamId!;
|
|
86
|
+
expect(streamId).toBeDefined();
|
|
87
|
+
const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
|
|
88
|
+
threadId,
|
|
89
|
+
cursors: [{ cursor: 0, streamId }],
|
|
90
|
+
});
|
|
91
|
+
const { parts } = getParts(deltas);
|
|
92
|
+
const expected = [
|
|
93
|
+
{ type: "start" },
|
|
94
|
+
{ type: "start-step" },
|
|
95
|
+
{ type: "text-start" },
|
|
96
|
+
{ type: "text-delta", delta: "A" },
|
|
97
|
+
{ type: "text-delta", delta: " B" },
|
|
98
|
+
{ type: "text-delta", delta: " C" },
|
|
99
|
+
{ type: "text-end" },
|
|
100
|
+
{ type: "reasoning-start" },
|
|
101
|
+
{ type: "reasoning-delta", delta: "D" },
|
|
102
|
+
{ type: "reasoning-delta", delta: " E" },
|
|
103
|
+
{ type: "reasoning-delta", delta: " F" },
|
|
104
|
+
{ type: "reasoning-end" },
|
|
105
|
+
{ type: "finish-step" },
|
|
106
|
+
{ type: "finish" },
|
|
107
|
+
];
|
|
108
|
+
for (const expectedPart of expected) {
|
|
109
|
+
const part = parts.shift();
|
|
110
|
+
expect(part).toBeDefined();
|
|
111
|
+
expect(part).toMatchObject(expectedPart);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("should save compressed parts via DeltaStreamer", async () => {
|
|
117
|
+
await t.run(async (ctx) => {
|
|
118
|
+
const streamer = new DeltaStreamer(
|
|
119
|
+
components.agent,
|
|
120
|
+
ctx,
|
|
121
|
+
{
|
|
122
|
+
throttleMs: 1000,
|
|
123
|
+
abortSignal: undefined,
|
|
124
|
+
compress: compressUIMessageChunks,
|
|
125
|
+
onAsyncAbort: async () => {
|
|
126
|
+
throw new Error("async abort");
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
...testMetadata,
|
|
131
|
+
threadId,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
const result = streamText({
|
|
135
|
+
model: mockModel({
|
|
136
|
+
content: [
|
|
137
|
+
// The mockModel splits these into deltas based on spaces
|
|
138
|
+
{ type: "text", text: "A B C" },
|
|
139
|
+
{ type: "text", text: "D E F" },
|
|
140
|
+
{ type: "reasoning", text: "J K L" },
|
|
141
|
+
{ type: "text", text: "M N O" },
|
|
142
|
+
],
|
|
143
|
+
}),
|
|
144
|
+
prompt: "Test prompt",
|
|
145
|
+
// experimental_transform: smoothStream({ chunking: "line" }),
|
|
146
|
+
onError: (error) => {
|
|
147
|
+
console.error(error);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
await streamer.consumeStream(result.toUIMessageStream());
|
|
151
|
+
const streamId = streamer.streamId!;
|
|
152
|
+
expect(streamId).toBeDefined();
|
|
153
|
+
const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
|
|
154
|
+
threadId,
|
|
155
|
+
cursors: [{ cursor: 0, streamId }],
|
|
156
|
+
});
|
|
157
|
+
const { parts } = getParts(deltas);
|
|
158
|
+
const expected = [
|
|
159
|
+
{ type: "start" },
|
|
160
|
+
{ type: "start-step" },
|
|
161
|
+
{ type: "text-start" },
|
|
162
|
+
// These are collapsed into a single delta
|
|
163
|
+
{ type: "text-delta", delta: "A B C" },
|
|
164
|
+
{ type: "text-end" },
|
|
165
|
+
{ type: "text-start" },
|
|
166
|
+
{ type: "text-delta", delta: "D E F" },
|
|
167
|
+
{ type: "text-end" },
|
|
168
|
+
{ type: "reasoning-start" },
|
|
169
|
+
{ type: "reasoning-delta", delta: "J K L" },
|
|
170
|
+
{ type: "reasoning-end" },
|
|
171
|
+
{ type: "text-start" },
|
|
172
|
+
{ type: "text-delta", delta: "M N O" },
|
|
173
|
+
{ type: "text-end" },
|
|
174
|
+
{ type: "finish-step" },
|
|
175
|
+
{ type: "finish" },
|
|
176
|
+
];
|
|
177
|
+
for (const expectedPart of expected) {
|
|
178
|
+
const part = parts.shift();
|
|
179
|
+
expect(part).toBeDefined();
|
|
180
|
+
expect(part).toMatchObject(expectedPart);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
// TODO: test errors & aborted states
|
|
185
|
+
// TODO: test fetching partial stream data - syncStreams w/ cursors
|
|
186
|
+
});
|
package/src/client/streaming.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ChunkDetector,
|
|
3
|
+
type AsyncIterableStream,
|
|
4
|
+
type StreamTextTransform,
|
|
5
|
+
type ToolSet,
|
|
6
|
+
smoothStream,
|
|
7
|
+
} from "ai";
|
|
2
8
|
import {
|
|
3
9
|
vStreamDelta,
|
|
4
10
|
vStreamMessage,
|
|
@@ -152,6 +158,36 @@ export const DEFAULT_STREAMING_OPTIONS = {
|
|
|
152
158
|
returnImmediately: false,
|
|
153
159
|
} satisfies StreamingOptions;
|
|
154
160
|
|
|
161
|
+
/**
|
|
162
|
+
*
|
|
163
|
+
* @param options The options passed to `agent.streamText` to decide whether to
|
|
164
|
+
* save deltas while streaming.
|
|
165
|
+
* @param existing The transforms passed to `agent.streamText` to merge with.
|
|
166
|
+
* @returns The merged transforms to pass to the underlying `streamText` call.
|
|
167
|
+
*/
|
|
168
|
+
export function mergeTransforms<TOOLS extends ToolSet>(
|
|
169
|
+
options: { chunking?: StreamingOptions["chunking"] } | boolean | undefined,
|
|
170
|
+
existing:
|
|
171
|
+
| StreamTextTransform<TOOLS>
|
|
172
|
+
| Array<StreamTextTransform<TOOLS>>
|
|
173
|
+
| undefined,
|
|
174
|
+
) {
|
|
175
|
+
if (!options) {
|
|
176
|
+
return existing;
|
|
177
|
+
}
|
|
178
|
+
const chunking =
|
|
179
|
+
typeof options === "boolean"
|
|
180
|
+
? DEFAULT_STREAMING_OPTIONS.chunking
|
|
181
|
+
: options.chunking;
|
|
182
|
+
const transforms = Array.isArray(existing)
|
|
183
|
+
? existing
|
|
184
|
+
: existing
|
|
185
|
+
? [existing]
|
|
186
|
+
: [];
|
|
187
|
+
transforms.push(smoothStream({ delayInMs: null, chunking }));
|
|
188
|
+
return transforms;
|
|
189
|
+
}
|
|
190
|
+
|
|
155
191
|
/**
|
|
156
192
|
* DeltaStreamer can be used to save a stream of "parts" by writing
|
|
157
193
|
* batches of them in "deltas" to the database so clients can subscribe
|
|
@@ -162,7 +198,7 @@ export const DEFAULT_STREAMING_OPTIONS = {
|
|
|
162
198
|
export class DeltaStreamer<T> {
|
|
163
199
|
public streamId: string | undefined;
|
|
164
200
|
public readonly config: {
|
|
165
|
-
|
|
201
|
+
throttleMs: number;
|
|
166
202
|
onAsyncAbort: (reason: string) => Promise<void>;
|
|
167
203
|
compress: ((parts: T[]) => T[]) | null;
|
|
168
204
|
};
|
|
@@ -176,7 +212,7 @@ export class DeltaStreamer<T> {
|
|
|
176
212
|
public readonly component: AgentComponent,
|
|
177
213
|
public readonly ctx: RunMutationCtx,
|
|
178
214
|
config: {
|
|
179
|
-
|
|
215
|
+
throttleMs: number | undefined;
|
|
180
216
|
onAsyncAbort: (reason: string) => Promise<void>;
|
|
181
217
|
abortSignal: AbortSignal | undefined;
|
|
182
218
|
compress: ((parts: T[]) => T[]) | null;
|
|
@@ -190,13 +226,11 @@ export class DeltaStreamer<T> {
|
|
|
190
226
|
model?: string;
|
|
191
227
|
provider?: string;
|
|
192
228
|
providerOptions?: ProviderOptions;
|
|
229
|
+
format: "UIMessageChunk" | "TextStreamPart" | undefined;
|
|
193
230
|
},
|
|
194
231
|
) {
|
|
195
232
|
this.config = {
|
|
196
|
-
|
|
197
|
-
config.stream === true
|
|
198
|
-
? DEFAULT_STREAMING_OPTIONS
|
|
199
|
-
: { ...DEFAULT_STREAMING_OPTIONS, ...config },
|
|
233
|
+
throttleMs: config.throttleMs ?? DEFAULT_STREAMING_OPTIONS.throttleMs,
|
|
200
234
|
onAsyncAbort: config.onAsyncAbort,
|
|
201
235
|
compress: config.compress,
|
|
202
236
|
};
|
|
@@ -232,12 +266,19 @@ export class DeltaStreamer<T> {
|
|
|
232
266
|
this.#nextParts.push(...parts);
|
|
233
267
|
if (
|
|
234
268
|
!this.#ongoingWrite &&
|
|
235
|
-
Date.now() - this.#latestWrite >= this.config.
|
|
269
|
+
Date.now() - this.#latestWrite >= this.config.throttleMs
|
|
236
270
|
) {
|
|
237
271
|
this.#ongoingWrite = this.#sendDelta();
|
|
238
272
|
}
|
|
239
273
|
}
|
|
240
274
|
|
|
275
|
+
public async consumeStream(stream: AsyncIterableStream<T>) {
|
|
276
|
+
for await (const chunk of stream) {
|
|
277
|
+
await this.addParts([chunk]);
|
|
278
|
+
}
|
|
279
|
+
await this.finish();
|
|
280
|
+
}
|
|
281
|
+
|
|
241
282
|
async #sendDelta() {
|
|
242
283
|
if (this.abortController.signal.aborted) {
|
|
243
284
|
return;
|
|
@@ -267,7 +308,7 @@ export class DeltaStreamer<T> {
|
|
|
267
308
|
// Now that we've sent the delta, check if we need to send another one.
|
|
268
309
|
if (
|
|
269
310
|
this.#nextParts.length > 0 &&
|
|
270
|
-
Date.now() - this.#latestWrite >= this.config.
|
|
311
|
+
Date.now() - this.#latestWrite >= this.config.throttleMs
|
|
271
312
|
) {
|
|
272
313
|
// We send again immediately with the accumulated deltas.
|
|
273
314
|
this.#ongoingWrite = this.#sendDelta();
|
package/src/client/types.ts
CHANGED
|
@@ -388,7 +388,7 @@ export type GenerateObjectArgs<
|
|
|
388
388
|
? Array<InferSchema<SCHEMA>>
|
|
389
389
|
: InferSchema<SCHEMA>,
|
|
390
390
|
> = Omit<CallSettings, "stopSequences"> &
|
|
391
|
-
Prompt & {
|
|
391
|
+
Partial<Prompt> & {
|
|
392
392
|
/**
|
|
393
393
|
* If provided, this message will be used as the "prompt" for the LLM call,
|
|
394
394
|
* instead of the prompt or messages.
|
|
@@ -555,7 +555,15 @@ export interface Thread<DefaultTools extends ToolSet> {
|
|
|
555
555
|
TOOLS,
|
|
556
556
|
OUTPUT,
|
|
557
557
|
OUTPUT_PARTIAL
|
|
558
|
-
|
|
558
|
+
> & {
|
|
559
|
+
/**
|
|
560
|
+
* If provided, this message will be used as the "prompt" for the LLM call,
|
|
561
|
+
* instead of the prompt or messages.
|
|
562
|
+
* This is useful if you want to first save a user message, then use it as
|
|
563
|
+
* the prompt for the LLM call in another call.
|
|
564
|
+
*/
|
|
565
|
+
promptMessageId?: string;
|
|
566
|
+
},
|
|
559
567
|
options?: Options,
|
|
560
568
|
): Promise<
|
|
561
569
|
GenerateTextResult<TOOLS extends undefined ? DefaultTools : TOOLS, OUTPUT> &
|
|
@@ -582,7 +590,15 @@ export interface Thread<DefaultTools extends ToolSet> {
|
|
|
582
590
|
TOOLS,
|
|
583
591
|
OUTPUT,
|
|
584
592
|
PARTIAL_OUTPUT
|
|
585
|
-
|
|
593
|
+
> & {
|
|
594
|
+
/**
|
|
595
|
+
* If provided, this message will be used as the "prompt" for the LLM call,
|
|
596
|
+
* instead of the prompt or messages.
|
|
597
|
+
* This is useful if you want to first save a user message, then use it as
|
|
598
|
+
* the prompt for the LLM call in another call.
|
|
599
|
+
*/
|
|
600
|
+
promptMessageId?: string;
|
|
601
|
+
},
|
|
586
602
|
options?: Options & {
|
|
587
603
|
/**
|
|
588
604
|
* Whether to save incremental data (deltas) from streaming responses.
|
|
@@ -630,15 +646,6 @@ export interface Thread<DefaultTools extends ToolSet> {
|
|
|
630
646
|
* the prompt for the LLM call in another call.
|
|
631
647
|
*/
|
|
632
648
|
promptMessageId?: string;
|
|
633
|
-
/**
|
|
634
|
-
* The model to use for the LLM calls. This will override the model specified
|
|
635
|
-
* in the Agent constructor.
|
|
636
|
-
*/
|
|
637
|
-
model?: LanguageModel;
|
|
638
|
-
/**
|
|
639
|
-
* The tools to use for the tool calls. This will override tools specified
|
|
640
|
-
* in the Agent constructor or createThread / continueThread.
|
|
641
|
-
*/
|
|
642
649
|
},
|
|
643
650
|
options?: Options,
|
|
644
651
|
): Promise<GenerateObjectResult<RESULT> & ThreadOutputMetadata>;
|
|
@@ -672,15 +679,6 @@ export interface Thread<DefaultTools extends ToolSet> {
|
|
|
672
679
|
* the prompt for the LLM call in another call.
|
|
673
680
|
*/
|
|
674
681
|
promptMessageId?: string;
|
|
675
|
-
/**
|
|
676
|
-
* The model to use for the LLM calls. This will override the model specified
|
|
677
|
-
* in the Agent constructor.
|
|
678
|
-
*/
|
|
679
|
-
model?: LanguageModel;
|
|
680
|
-
/**
|
|
681
|
-
* The tools to use for the tool calls. This will override tools specified
|
|
682
|
-
* in the Agent constructor or createThread / continueThread.
|
|
683
|
-
*/
|
|
684
682
|
},
|
|
685
683
|
options?: Options,
|
|
686
684
|
): Promise<
|
|
@@ -2189,6 +2189,7 @@ export type Mounts = {
|
|
|
2189
2189
|
"public",
|
|
2190
2190
|
{
|
|
2191
2191
|
agentName?: string;
|
|
2192
|
+
format?: "UIMessageChunk" | "TextStreamPart";
|
|
2192
2193
|
model?: string;
|
|
2193
2194
|
order: number;
|
|
2194
2195
|
provider?: string;
|
|
@@ -2253,6 +2254,7 @@ export type Mounts = {
|
|
|
2253
2254
|
},
|
|
2254
2255
|
Array<{
|
|
2255
2256
|
agentName?: string;
|
|
2257
|
+
format?: "UIMessageChunk" | "TextStreamPart";
|
|
2256
2258
|
model?: string;
|
|
2257
2259
|
order: number;
|
|
2258
2260
|
provider?: string;
|
|
@@ -344,7 +344,7 @@ export const finalizeMessage = mutation({
|
|
|
344
344
|
const message = await ctx.db.get(messageId);
|
|
345
345
|
assert(message, `Message ${messageId} not found`);
|
|
346
346
|
if (message.status !== "pending") {
|
|
347
|
-
console.
|
|
347
|
+
console.debug(
|
|
348
348
|
"Trying to finalize a message that's already",
|
|
349
349
|
message.status,
|
|
350
350
|
);
|
package/src/component/schema.ts
CHANGED
|
@@ -91,6 +91,12 @@ export const schema = defineSchema({
|
|
|
91
91
|
model: v.optional(v.string()),
|
|
92
92
|
provider: v.optional(v.string()),
|
|
93
93
|
providerOptions: v.optional(vProviderOptions), // Sent to model
|
|
94
|
+
// The data format for the deltas. By default, we use UIMessageChunks.
|
|
95
|
+
// This format dictates how the messages are materialized for capturing
|
|
96
|
+
// partial messages during failure, as well as on the client side.
|
|
97
|
+
format: v.optional(
|
|
98
|
+
v.union(v.literal("UIMessageChunk"), v.literal("TextStreamPart")),
|
|
99
|
+
),
|
|
94
100
|
|
|
95
101
|
threadId: v.id("threads"),
|
|
96
102
|
order: v.number(),
|
package/src/component/streams.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { stream } from "convex-helpers/server/stream";
|
|
|
20
20
|
import { mergedStream } from "convex-helpers/server/stream";
|
|
21
21
|
import { paginator } from "convex-helpers/server/pagination";
|
|
22
22
|
import type { WithoutSystemFields } from "convex/server";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { deriveUIMessagesFromDeltas } from "../deltas.js";
|
|
24
|
+
import { fromUIMessages } from "../UIMessages.js";
|
|
25
25
|
|
|
26
26
|
const SECOND = 1000;
|
|
27
27
|
const MINUTE = 60 * SECOND;
|
|
@@ -140,6 +140,7 @@ export const list = query({
|
|
|
140
140
|
streamId: m._id,
|
|
141
141
|
status: m.state.kind,
|
|
142
142
|
...pick(m, [
|
|
143
|
+
"format",
|
|
143
144
|
"order",
|
|
144
145
|
"stepOrder",
|
|
145
146
|
"userId",
|
|
@@ -516,72 +517,54 @@ export async function getStreamingMessagesWithMetadata(
|
|
|
516
517
|
);
|
|
517
518
|
const messages = (
|
|
518
519
|
await Promise.all(
|
|
519
|
-
streamingMessages.map((
|
|
520
|
-
|
|
521
|
-
|
|
520
|
+
streamingMessages.map(async (streamingMessage) => {
|
|
521
|
+
const deltas = await ctx.db
|
|
522
|
+
.query("streamDeltas")
|
|
523
|
+
.withIndex("streamId_start_end", (q) =>
|
|
524
|
+
q.eq("streamId", streamingMessage._id),
|
|
525
|
+
)
|
|
526
|
+
.take(1000);
|
|
527
|
+
const uiMessages = await deriveUIMessagesFromDeltas(
|
|
522
528
|
threadId,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
529
|
+
[
|
|
530
|
+
{
|
|
531
|
+
...streamingMessage,
|
|
532
|
+
status: "streaming",
|
|
533
|
+
streamId: streamingMessage._id,
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
deltas,
|
|
537
|
+
);
|
|
538
|
+
// We don't save messages that have already been saved
|
|
539
|
+
const numToSkip = stepOrder - streamingMessage.stepOrder;
|
|
540
|
+
const messages = await Promise.all(
|
|
541
|
+
fromUIMessages(uiMessages, streamingMessage)
|
|
542
|
+
.slice(numToSkip)
|
|
543
|
+
.filter((m) => m.message !== undefined)
|
|
544
|
+
.map(async (msg) => {
|
|
545
|
+
return {
|
|
546
|
+
...pick(msg, [
|
|
547
|
+
"message",
|
|
548
|
+
"fileIds",
|
|
549
|
+
"status",
|
|
550
|
+
"finishReason",
|
|
551
|
+
"model",
|
|
552
|
+
"provider",
|
|
553
|
+
"providerMetadata",
|
|
554
|
+
"sources",
|
|
555
|
+
"reasoning",
|
|
556
|
+
"reasoningDetails",
|
|
557
|
+
"usage",
|
|
558
|
+
"warnings",
|
|
559
|
+
"error",
|
|
560
|
+
]),
|
|
561
|
+
...metadata,
|
|
562
|
+
} as MessageWithMetadataInternal;
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
return messages;
|
|
566
|
+
}),
|
|
528
567
|
)
|
|
529
568
|
).flat();
|
|
530
569
|
return messages;
|
|
531
570
|
}
|
|
532
|
-
|
|
533
|
-
export async function getMessagesWithMetadataForStreamingMessage(
|
|
534
|
-
ctx: MutationCtx,
|
|
535
|
-
threadId: Id<"threads">,
|
|
536
|
-
stepOrder: number,
|
|
537
|
-
streamingMessage: Doc<"streamingMessages">,
|
|
538
|
-
metadata: { status: "success" | "failed"; error?: string },
|
|
539
|
-
): Promise<MessageWithMetadataInternal[]> {
|
|
540
|
-
const deltas = await ctx.db
|
|
541
|
-
.query("streamDeltas")
|
|
542
|
-
.withIndex("streamId_start_end", (q) =>
|
|
543
|
-
q.eq("streamId", streamingMessage._id),
|
|
544
|
-
)
|
|
545
|
-
.take(1000);
|
|
546
|
-
const [messageDocs] = mergeDeltas(
|
|
547
|
-
threadId,
|
|
548
|
-
[
|
|
549
|
-
{
|
|
550
|
-
...streamingMessage,
|
|
551
|
-
status: "streaming",
|
|
552
|
-
streamId: streamingMessage._id,
|
|
553
|
-
},
|
|
554
|
-
],
|
|
555
|
-
[],
|
|
556
|
-
deltas,
|
|
557
|
-
);
|
|
558
|
-
// We don't save messages that have already been saved
|
|
559
|
-
const numToSkip = stepOrder - streamingMessage.stepOrder;
|
|
560
|
-
const messages = await Promise.all(
|
|
561
|
-
messageDocs
|
|
562
|
-
.slice(numToSkip)
|
|
563
|
-
.filter((m) => m.message !== undefined)
|
|
564
|
-
.map(async (msg) => {
|
|
565
|
-
const message = await serializeOrThrow(msg.message!);
|
|
566
|
-
return {
|
|
567
|
-
message,
|
|
568
|
-
...pick(msg, [
|
|
569
|
-
"fileIds",
|
|
570
|
-
"status",
|
|
571
|
-
"finishReason",
|
|
572
|
-
"model",
|
|
573
|
-
"provider",
|
|
574
|
-
"providerMetadata",
|
|
575
|
-
"sources",
|
|
576
|
-
"reasoning",
|
|
577
|
-
"reasoningDetails",
|
|
578
|
-
"usage",
|
|
579
|
-
"warnings",
|
|
580
|
-
"error",
|
|
581
|
-
]),
|
|
582
|
-
...metadata,
|
|
583
|
-
} as MessageWithMetadataInternal;
|
|
584
|
-
}),
|
|
585
|
-
);
|
|
586
|
-
return messages;
|
|
587
|
-
}
|