@convex-dev/agent 0.1.18 → 0.2.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/client/createTool.d.ts +30 -11
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +24 -10
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/defaultComponent.d.ts +11 -0
- package/dist/client/defaultComponent.d.ts.map +1 -0
- package/dist/client/defaultComponent.js +7 -0
- package/dist/client/defaultComponent.js.map +1 -0
- package/dist/client/definePlaygroundAPI.d.ts +68 -23
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +7 -20
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.d.ts +14 -4
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +66 -11
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +323 -780
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +445 -543
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +256 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +97 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/search.d.ts +35 -12
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +21 -4
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +21 -21
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +35 -40
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/threads.d.ts +46 -0
- package/dist/client/threads.d.ts.map +1 -0
- package/dist/client/threads.js +49 -0
- package/dist/client/threads.js.map +1 -0
- package/dist/client/types.d.ts +159 -75
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +261 -209
- package/dist/component/messages.d.ts +314 -105
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +141 -83
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +631 -870
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +11 -16
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +29 -149
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +104 -60
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +4 -4
- package/dist/component/users.d.ts +3 -3
- package/dist/mapping.d.ts +15 -16
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +208 -62
- package/dist/mapping.js.map +1 -1
- package/dist/parts.d.ts +3 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +34 -0
- package/dist/parts.js.map +1 -0
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/deltas.d.ts +3 -5
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +89 -53
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +31 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +2 -8
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/toUIMessages.d.ts +5 -4
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +103 -40
- package/dist/react/toUIMessages.js.map +1 -1
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +1 -1
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/validators.d.ts +751 -1464
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +39 -80
- package/dist/validators.js.map +1 -1
- package/package.json +34 -30
- package/src/client/createTool.ts +66 -40
- package/src/client/defaultComponent.ts +17 -0
- package/src/client/definePlaygroundAPI.ts +17 -31
- package/src/client/files.ts +94 -17
- package/src/client/index.test.ts +28 -38
- package/src/client/index.ts +785 -977
- package/src/client/messages.ts +205 -0
- package/src/client/search.ts +37 -10
- package/src/client/streaming.ts +53 -56
- package/src/client/threads.ts +78 -0
- package/src/client/types.ts +239 -131
- package/src/component/_generated/api.d.ts +261 -209
- package/src/component/messages.test.ts +182 -40
- package/src/component/messages.ts +165 -108
- package/src/component/schema.ts +11 -17
- package/src/component/streams.ts +180 -69
- package/src/mapping.ts +253 -96
- package/src/parts.ts +39 -0
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/deltas.test.ts +38 -42
- package/src/react/deltas.ts +116 -65
- package/src/react/index.ts +35 -13
- package/src/react/optimisticallySendMessage.ts +2 -8
- package/src/react/toUIMessages.test.ts +154 -36
- package/src/react/toUIMessages.ts +136 -57
- package/src/react/useSmoothText.ts +14 -15
- package/src/validators.test.ts +1 -117
- package/src/validators.ts +57 -107
- package/dist/client/listMessages.d.ts +0 -22
- package/dist/client/listMessages.d.ts.map +0 -1
- package/dist/client/listMessages.js +0 -25
- package/dist/client/listMessages.js.map +0 -1
- package/src/client/listMessages.ts +0 -38
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
2
|
+
import type { PaginationOptions, PaginationResult } from "convex/server";
|
|
3
|
+
import type { MessageDoc } from "../component/schema.js";
|
|
4
|
+
import { validateVectorDimension } from "../component/vector/tables.js";
|
|
5
|
+
import {
|
|
6
|
+
vMessageWithMetadata,
|
|
7
|
+
type Message,
|
|
8
|
+
type MessageEmbeddings,
|
|
9
|
+
type MessageEmbeddingsWithDimension,
|
|
10
|
+
type MessageStatus,
|
|
11
|
+
type MessageWithMetadata,
|
|
12
|
+
} from "../validators.js";
|
|
13
|
+
import { serializeMessage } from "./index.js";
|
|
14
|
+
import type { AgentComponent, RunMutationCtx, RunQueryCtx } from "./types.js";
|
|
15
|
+
import { parse } from "convex-helpers/validators";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List messages from a thread.
|
|
19
|
+
* @param ctx A ctx object from a query, mutation, or action.
|
|
20
|
+
* @param component The agent component, usually `components.agent`.
|
|
21
|
+
* @param args.threadId The thread to list messages from.
|
|
22
|
+
* @param args.paginationOpts Pagination options (e.g. via usePaginatedQuery).
|
|
23
|
+
* @param args.excludeToolMessages Whether to exclude tool messages.
|
|
24
|
+
* False by default.
|
|
25
|
+
* @param args.statuses What statuses to include. All by default.
|
|
26
|
+
* @returns The MessageDoc's in a format compatible with usePaginatedQuery.
|
|
27
|
+
*/
|
|
28
|
+
export async function listMessages(
|
|
29
|
+
ctx: RunQueryCtx,
|
|
30
|
+
component: AgentComponent,
|
|
31
|
+
args: {
|
|
32
|
+
threadId: string;
|
|
33
|
+
paginationOpts: PaginationOptions;
|
|
34
|
+
excludeToolMessages?: boolean;
|
|
35
|
+
statuses?: MessageStatus[];
|
|
36
|
+
},
|
|
37
|
+
): Promise<PaginationResult<MessageDoc>> {
|
|
38
|
+
if (args.paginationOpts.numItems === 0) {
|
|
39
|
+
return {
|
|
40
|
+
page: [],
|
|
41
|
+
isDone: true,
|
|
42
|
+
continueCursor: args.paginationOpts.cursor ?? "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return ctx.runQuery(component.messages.listMessagesByThreadId, {
|
|
46
|
+
order: "desc",
|
|
47
|
+
...args,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type SaveMessagesArgs = {
|
|
52
|
+
threadId: string;
|
|
53
|
+
userId?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* The message that these messages are in response to. They will be
|
|
56
|
+
* the same "order" as this message, at increasing stepOrder(s).
|
|
57
|
+
*/
|
|
58
|
+
promptMessageId?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The messages to save.
|
|
61
|
+
*/
|
|
62
|
+
messages: (ModelMessage | Message)[];
|
|
63
|
+
/**
|
|
64
|
+
* Metadata to save with the messages. Each element corresponds to the
|
|
65
|
+
* message at the same index.
|
|
66
|
+
*/
|
|
67
|
+
metadata?: Omit<MessageWithMetadata, "message">[];
|
|
68
|
+
/**
|
|
69
|
+
* If true, it will fail any pending steps.
|
|
70
|
+
* Defaults to false.
|
|
71
|
+
*/
|
|
72
|
+
failPendingSteps?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The embeddings to save with the messages.
|
|
75
|
+
*/
|
|
76
|
+
embeddings?: MessageEmbeddings;
|
|
77
|
+
/**
|
|
78
|
+
* A pending message ID to replace when adding messages.
|
|
79
|
+
*/
|
|
80
|
+
pendingMessageId?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Explicitly save messages associated with the thread (& user if provided)
|
|
85
|
+
*/
|
|
86
|
+
export async function saveMessages(
|
|
87
|
+
ctx: RunMutationCtx,
|
|
88
|
+
component: AgentComponent,
|
|
89
|
+
args: SaveMessagesArgs & {
|
|
90
|
+
/**
|
|
91
|
+
* The agent name to associate with the messages.
|
|
92
|
+
*/
|
|
93
|
+
agentName?: string;
|
|
94
|
+
},
|
|
95
|
+
): Promise<{ messages: MessageDoc[] }> {
|
|
96
|
+
let embeddings: MessageEmbeddingsWithDimension | undefined;
|
|
97
|
+
if (args.embeddings) {
|
|
98
|
+
const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
|
|
99
|
+
if (dimension) {
|
|
100
|
+
validateVectorDimension(dimension);
|
|
101
|
+
embeddings = {
|
|
102
|
+
model: args.embeddings.model,
|
|
103
|
+
dimension,
|
|
104
|
+
vectors: args.embeddings.vectors,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = await ctx.runMutation(component.messages.addMessages, {
|
|
109
|
+
threadId: args.threadId,
|
|
110
|
+
userId: args.userId ?? undefined,
|
|
111
|
+
agentName: args.agentName,
|
|
112
|
+
promptMessageId: args.promptMessageId,
|
|
113
|
+
pendingMessageId: args.pendingMessageId,
|
|
114
|
+
embeddings,
|
|
115
|
+
messages: await Promise.all(
|
|
116
|
+
args.messages.map(async (m, i) => {
|
|
117
|
+
const { message, fileIds } = await serializeMessage(ctx, component, m);
|
|
118
|
+
const allFileIds = args.metadata?.[i]?.fileIds ?? [];
|
|
119
|
+
if (fileIds) {
|
|
120
|
+
allFileIds.push(...fileIds);
|
|
121
|
+
}
|
|
122
|
+
return parse(vMessageWithMetadata, {
|
|
123
|
+
...args.metadata?.[i],
|
|
124
|
+
message,
|
|
125
|
+
fileIds: allFileIds.length > 0 ? allFileIds : undefined,
|
|
126
|
+
});
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
failPendingSteps: args.failPendingSteps ?? false,
|
|
130
|
+
});
|
|
131
|
+
return { messages: result.messages };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type SaveMessageArgs = {
|
|
135
|
+
threadId: string;
|
|
136
|
+
userId?: string | null;
|
|
137
|
+
/**
|
|
138
|
+
* Metadata to save with the messages. Each element corresponds to the
|
|
139
|
+
* message at the same index.
|
|
140
|
+
*/
|
|
141
|
+
metadata?: Omit<MessageWithMetadata, "message">;
|
|
142
|
+
/**
|
|
143
|
+
* The embedding to save with the message.
|
|
144
|
+
*/
|
|
145
|
+
embedding?: { vector: number[]; model: string };
|
|
146
|
+
/**
|
|
147
|
+
* A pending message ID to replace with this message.
|
|
148
|
+
*/
|
|
149
|
+
pendingMessageId?: string;
|
|
150
|
+
} & (
|
|
151
|
+
| {
|
|
152
|
+
prompt?: undefined;
|
|
153
|
+
/**
|
|
154
|
+
* The message to save.
|
|
155
|
+
*/
|
|
156
|
+
message: ModelMessage | Message;
|
|
157
|
+
}
|
|
158
|
+
| {
|
|
159
|
+
/*
|
|
160
|
+
* The prompt to save with the message.
|
|
161
|
+
*/
|
|
162
|
+
prompt: string;
|
|
163
|
+
message?: undefined;
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Save a message to the thread.
|
|
169
|
+
* @param ctx A ctx object from a mutation or action.
|
|
170
|
+
* @param args The message and what to associate it with (user / thread)
|
|
171
|
+
* You can pass extra metadata alongside the message, e.g. associated fileIds.
|
|
172
|
+
* @returns The messageId of the saved message.
|
|
173
|
+
*/
|
|
174
|
+
export async function saveMessage(
|
|
175
|
+
ctx: RunMutationCtx,
|
|
176
|
+
component: AgentComponent,
|
|
177
|
+
args: SaveMessageArgs & {
|
|
178
|
+
/**
|
|
179
|
+
* The agent name to associate with the message.
|
|
180
|
+
*/
|
|
181
|
+
agentName?: string;
|
|
182
|
+
},
|
|
183
|
+
) {
|
|
184
|
+
let embeddings: { vectors: number[][]; model: string } | undefined;
|
|
185
|
+
if (args.embedding && args.embedding.vector) {
|
|
186
|
+
embeddings = {
|
|
187
|
+
model: args.embedding.model,
|
|
188
|
+
vectors: [args.embedding.vector],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const { messages } = await saveMessages(ctx, component, {
|
|
192
|
+
threadId: args.threadId,
|
|
193
|
+
userId: args.userId ?? undefined,
|
|
194
|
+
agentName: args.agentName,
|
|
195
|
+
pendingMessageId: args.pendingMessageId,
|
|
196
|
+
messages:
|
|
197
|
+
args.prompt !== undefined
|
|
198
|
+
? [{ role: "user", content: args.prompt }]
|
|
199
|
+
: [args.message],
|
|
200
|
+
metadata: args.metadata ? [args.metadata] : undefined,
|
|
201
|
+
embeddings,
|
|
202
|
+
});
|
|
203
|
+
const message = messages.at(-1)!;
|
|
204
|
+
return { messageId: message._id, message };
|
|
205
|
+
}
|
package/src/client/search.ts
CHANGED
|
@@ -5,19 +5,22 @@ import type {
|
|
|
5
5
|
RunQueryCtx,
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
import type { MessageDoc } from "../component/schema.js";
|
|
8
|
-
import type {
|
|
8
|
+
import type { EmbeddingModel, LanguageModel, ModelMessage } from "ai";
|
|
9
9
|
import { assert } from "convex-helpers";
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_MESSAGE_RANGE,
|
|
12
12
|
DEFAULT_RECENT_MESSAGES,
|
|
13
13
|
extractText,
|
|
14
14
|
} from "../shared.js";
|
|
15
|
+
import type { Message } from "../validators.js";
|
|
15
16
|
|
|
16
17
|
const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
|
|
17
18
|
|
|
18
|
-
export type GetEmbedding = (
|
|
19
|
+
export type GetEmbedding = (
|
|
20
|
+
text: string,
|
|
21
|
+
) => Promise<{
|
|
19
22
|
embedding: number[];
|
|
20
|
-
|
|
23
|
+
textEmbeddingModel: string | EmbeddingModel<string>;
|
|
21
24
|
}>;
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -34,7 +37,7 @@ export async function fetchContextMessages(
|
|
|
34
37
|
args: {
|
|
35
38
|
userId: string | undefined;
|
|
36
39
|
threadId: string | undefined;
|
|
37
|
-
messages:
|
|
40
|
+
messages: (ModelMessage | Message)[];
|
|
38
41
|
/**
|
|
39
42
|
* If provided, it will search for messages up to and including this message.
|
|
40
43
|
* Note: if this is far in the past, text and vector search results may be more
|
|
@@ -95,20 +98,21 @@ export async function fetchContextMessages(
|
|
|
95
98
|
"You must provide an embedding and embeddingModel to use vector search",
|
|
96
99
|
);
|
|
97
100
|
}
|
|
98
|
-
const embeddingFields =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
const embeddingFields =
|
|
102
|
+
opts.searchOptions?.vectorSearch && text
|
|
103
|
+
? await args.getEmbedding?.(text)
|
|
104
|
+
: undefined;
|
|
101
105
|
const searchMessages = await ctx.runAction(
|
|
102
106
|
component.messages.searchMessages,
|
|
103
107
|
{
|
|
104
108
|
searchAllMessagesForUserId: opts?.searchOtherThreads
|
|
105
|
-
? args.userId ??
|
|
109
|
+
? (args.userId ??
|
|
106
110
|
(args.threadId &&
|
|
107
111
|
(
|
|
108
112
|
await ctx.runQuery(component.threads.getThread, {
|
|
109
113
|
threadId: args.threadId,
|
|
110
114
|
})
|
|
111
|
-
)?.userId)
|
|
115
|
+
)?.userId))
|
|
112
116
|
: undefined,
|
|
113
117
|
threadId: args.threadId,
|
|
114
118
|
beforeMessageId: args.upToAndIncludingMessageId,
|
|
@@ -122,7 +126,9 @@ export async function fetchContextMessages(
|
|
|
122
126
|
opts.searchOptions?.vectorScoreThreshold ??
|
|
123
127
|
DEFAULT_VECTOR_SCORE_THRESHOLD,
|
|
124
128
|
embedding: embeddingFields?.embedding,
|
|
125
|
-
embeddingModel: embeddingFields?.
|
|
129
|
+
embeddingModel: embeddingFields?.textEmbeddingModel
|
|
130
|
+
? getModelName(embeddingFields.textEmbeddingModel)
|
|
131
|
+
: undefined,
|
|
126
132
|
},
|
|
127
133
|
);
|
|
128
134
|
// TODO: track what messages we used for context
|
|
@@ -170,3 +176,24 @@ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
|
170
176
|
}
|
|
171
177
|
return result;
|
|
172
178
|
}
|
|
179
|
+
|
|
180
|
+
export function getModelName(
|
|
181
|
+
embeddingModel: string | EmbeddingModel<string> | LanguageModel,
|
|
182
|
+
): string {
|
|
183
|
+
if (typeof embeddingModel === "string") {
|
|
184
|
+
if (embeddingModel.includes("/")) {
|
|
185
|
+
return embeddingModel.split("/").slice(1).join("/");
|
|
186
|
+
}
|
|
187
|
+
return embeddingModel;
|
|
188
|
+
}
|
|
189
|
+
return embeddingModel.modelId;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getProviderName(
|
|
193
|
+
embeddingModel: string | EmbeddingModel<string> | LanguageModel,
|
|
194
|
+
): string {
|
|
195
|
+
if (typeof embeddingModel === "string") {
|
|
196
|
+
return embeddingModel.split("/").at(0)!;
|
|
197
|
+
}
|
|
198
|
+
return embeddingModel.provider;
|
|
199
|
+
}
|
package/src/client/streaming.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type ChunkDetector,
|
|
3
3
|
smoothStream,
|
|
4
4
|
type StreamTextTransform,
|
|
5
|
+
type TextStreamPart,
|
|
5
6
|
type ToolSet,
|
|
6
7
|
} from "ai";
|
|
7
8
|
import type {
|
|
@@ -9,9 +10,7 @@ import type {
|
|
|
9
10
|
StreamArgs,
|
|
10
11
|
StreamDelta,
|
|
11
12
|
StreamMessage,
|
|
12
|
-
TextStreamPart,
|
|
13
13
|
} from "../validators.js";
|
|
14
|
-
import type { MessageDoc } from "../component/schema.js";
|
|
15
14
|
import type {
|
|
16
15
|
AgentComponent,
|
|
17
16
|
RunActionCtx,
|
|
@@ -20,6 +19,7 @@ import type {
|
|
|
20
19
|
SyncStreamsReturnValue,
|
|
21
20
|
} from "./types.js";
|
|
22
21
|
import { omit } from "convex-helpers";
|
|
22
|
+
import { serializeTextStreamingPartsV5 } from "../parts.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* A function that handles fetching stream deltas, used with the React hooks
|
|
@@ -64,9 +64,10 @@ export async function syncStreams(
|
|
|
64
64
|
export async function abortStream(
|
|
65
65
|
ctx: RunMutationCtx,
|
|
66
66
|
component: AgentComponent,
|
|
67
|
-
args: {
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
args: { reason: string } & (
|
|
68
|
+
| { streamId: string }
|
|
69
|
+
| { threadId: string; order: number }
|
|
70
|
+
),
|
|
70
71
|
): Promise<boolean> {
|
|
71
72
|
if ("streamId" in args) {
|
|
72
73
|
return await ctx.runMutation(component.streams.abort, {
|
|
@@ -158,9 +159,7 @@ export function mergeTransforms<TOOLS extends ToolSet>(
|
|
|
158
159
|
export class DeltaStreamer {
|
|
159
160
|
public streamId: string | undefined;
|
|
160
161
|
public readonly options: Required<StreamingOptions>;
|
|
161
|
-
#nextParts: TextStreamPart[] = [];
|
|
162
|
-
#nextOrder: number;
|
|
163
|
-
#nextStepOrder: number;
|
|
162
|
+
#nextParts: TextStreamPart<ToolSet>[] = [];
|
|
164
163
|
#latestWrite: number = 0;
|
|
165
164
|
#ongoingWrite: Promise<void> | undefined;
|
|
166
165
|
#cursor: number = 0;
|
|
@@ -172,51 +171,46 @@ export class DeltaStreamer {
|
|
|
172
171
|
options: true | StreamingOptions,
|
|
173
172
|
public readonly metadata: {
|
|
174
173
|
threadId: string;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
abortSignal
|
|
174
|
+
userId?: string;
|
|
175
|
+
order: number;
|
|
176
|
+
stepOrder: number;
|
|
177
|
+
agentName?: string;
|
|
178
|
+
model?: string;
|
|
179
|
+
provider?: string;
|
|
180
|
+
providerOptions?: ProviderOptions;
|
|
181
|
+
abortSignal?: AbortSignal;
|
|
183
182
|
},
|
|
184
183
|
) {
|
|
185
184
|
this.options =
|
|
186
185
|
typeof options === "boolean"
|
|
187
186
|
? DEFAULT_STREAMING_OPTIONS
|
|
188
|
-
: {
|
|
189
|
-
...DEFAULT_STREAMING_OPTIONS,
|
|
190
|
-
...options,
|
|
191
|
-
};
|
|
187
|
+
: { ...DEFAULT_STREAMING_OPTIONS, ...options };
|
|
192
188
|
this.#nextParts = [];
|
|
193
|
-
this.#nextOrder = metadata.order ?? 0;
|
|
194
|
-
this.#nextStepOrder = (metadata.stepOrder ?? 0) + 1;
|
|
195
189
|
this.abortController = new AbortController();
|
|
196
190
|
if (metadata.abortSignal) {
|
|
197
191
|
metadata.abortSignal.addEventListener("abort", async () => {
|
|
198
192
|
if (this.streamId) {
|
|
193
|
+
this.abortController.abort();
|
|
194
|
+
const finalDelta = this.#createDelta();
|
|
195
|
+
await this.#ongoingWrite;
|
|
199
196
|
await this.ctx.runMutation(this.component.streams.abort, {
|
|
200
197
|
streamId: this.streamId,
|
|
201
198
|
reason: "abortSignal",
|
|
199
|
+
finalDelta,
|
|
202
200
|
});
|
|
203
201
|
}
|
|
204
|
-
this.abortController.abort();
|
|
205
202
|
});
|
|
206
203
|
}
|
|
207
204
|
}
|
|
208
|
-
|
|
205
|
+
|
|
206
|
+
public async addParts(parts: TextStreamPart<ToolSet>[]) {
|
|
209
207
|
if (this.abortController.signal.aborted) {
|
|
210
208
|
return;
|
|
211
209
|
}
|
|
212
210
|
if (!this.streamId) {
|
|
213
211
|
this.streamId = await this.ctx.runMutation(
|
|
214
212
|
this.component.streams.create,
|
|
215
|
-
|
|
216
|
-
...omit(this.metadata, ["abortSignal"]),
|
|
217
|
-
order: this.#nextOrder,
|
|
218
|
-
stepOrder: this.#nextStepOrder,
|
|
219
|
-
},
|
|
213
|
+
omit(this.metadata, ["abortSignal"]),
|
|
220
214
|
);
|
|
221
215
|
}
|
|
222
216
|
this.#nextParts.push(...parts);
|
|
@@ -233,6 +227,9 @@ export class DeltaStreamer {
|
|
|
233
227
|
return;
|
|
234
228
|
}
|
|
235
229
|
const delta = this.#createDelta();
|
|
230
|
+
if (!delta) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
236
233
|
this.#latestWrite = Date.now();
|
|
237
234
|
try {
|
|
238
235
|
const success = await this.ctx.runMutation(
|
|
@@ -258,46 +255,46 @@ export class DeltaStreamer {
|
|
|
258
255
|
}
|
|
259
256
|
}
|
|
260
257
|
|
|
261
|
-
#createDelta(): StreamDelta {
|
|
258
|
+
#createDelta(): StreamDelta | undefined {
|
|
259
|
+
if (this.#nextParts.length === 0) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
262
|
const start = this.#cursor;
|
|
263
263
|
const end = start + this.#nextParts.length;
|
|
264
264
|
this.#cursor = end;
|
|
265
|
-
const parts = this.#nextParts;
|
|
265
|
+
const parts = serializeTextStreamingPartsV5(this.#nextParts);
|
|
266
266
|
this.#nextParts = [];
|
|
267
267
|
if (!this.streamId) {
|
|
268
268
|
throw new Error("Creating a delta before the stream is created");
|
|
269
269
|
}
|
|
270
|
-
return {
|
|
270
|
+
return { streamId: this.streamId, start, end, parts };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
public async finish() {
|
|
274
|
+
if (!this.streamId) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const finalDelta = this.#createDelta();
|
|
278
|
+
await this.#ongoingWrite;
|
|
279
|
+
await this.ctx.runMutation(this.component.streams.finish, {
|
|
271
280
|
streamId: this.streamId,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
parts,
|
|
275
|
-
};
|
|
281
|
+
finalDelta,
|
|
282
|
+
});
|
|
276
283
|
}
|
|
277
284
|
|
|
278
|
-
public async
|
|
279
|
-
if (this
|
|
280
|
-
|
|
281
|
-
this.#ongoingWrite = undefined;
|
|
285
|
+
public async fail(reason: string) {
|
|
286
|
+
if (this.abortController.signal.aborted) {
|
|
287
|
+
return;
|
|
282
288
|
}
|
|
289
|
+
this.abortController.abort();
|
|
283
290
|
if (!this.streamId) {
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
const lastMessage = messages.at(-1);
|
|
287
|
-
if (lastMessage) {
|
|
288
|
-
this.#nextOrder = lastMessage.order;
|
|
289
|
-
this.#nextStepOrder = lastMessage.stepOrder + 1;
|
|
290
|
-
} else {
|
|
291
|
-
console.warn("Step finished without generating a message");
|
|
291
|
+
return;
|
|
292
292
|
}
|
|
293
|
-
const finalDelta =
|
|
294
|
-
|
|
295
|
-
this
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.#cursor = 0;
|
|
299
|
-
await this.ctx.runMutation(this.component.streams.finish, {
|
|
300
|
-
streamId,
|
|
293
|
+
const finalDelta = this.#createDelta();
|
|
294
|
+
await this.#ongoingWrite;
|
|
295
|
+
await this.ctx.runMutation(this.component.streams.abort, {
|
|
296
|
+
streamId: this.streamId,
|
|
297
|
+
reason,
|
|
301
298
|
finalDelta,
|
|
302
299
|
});
|
|
303
300
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { WithoutSystemFields } from "convex/server";
|
|
2
|
+
import type { ThreadDoc } from "../component/schema.js";
|
|
3
|
+
import type { AgentComponent, RunMutationCtx, RunQueryCtx } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a thread to store messages with an Agent.
|
|
7
|
+
* @param ctx The context from a mutation or action.
|
|
8
|
+
* @param component The Agent component, usually `components.agent`.
|
|
9
|
+
* @param args The associated thread metadata.
|
|
10
|
+
* @returns The id of the created thread.
|
|
11
|
+
*/
|
|
12
|
+
export async function createThread(
|
|
13
|
+
ctx: RunMutationCtx,
|
|
14
|
+
component: AgentComponent,
|
|
15
|
+
args?: { userId?: string | null; title?: string; summary?: string },
|
|
16
|
+
) {
|
|
17
|
+
const { _id: threadId } = await ctx.runMutation(
|
|
18
|
+
component.threads.createThread,
|
|
19
|
+
{
|
|
20
|
+
userId: args?.userId ?? undefined,
|
|
21
|
+
title: args?.title,
|
|
22
|
+
summary: args?.summary,
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
return threadId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the metadata for a thread.
|
|
30
|
+
* @param ctx A ctx object from a query, mutation, or action.
|
|
31
|
+
* @param args.threadId The thread to get the metadata for.
|
|
32
|
+
* @returns The metadata for the thread.
|
|
33
|
+
*/
|
|
34
|
+
export async function getThreadMetadata(
|
|
35
|
+
ctx: RunQueryCtx,
|
|
36
|
+
component: AgentComponent,
|
|
37
|
+
args: { threadId: string },
|
|
38
|
+
): Promise<ThreadDoc> {
|
|
39
|
+
const thread = await ctx.runQuery(component.threads.getThread, {
|
|
40
|
+
threadId: args.threadId,
|
|
41
|
+
});
|
|
42
|
+
if (!thread) {
|
|
43
|
+
throw new Error("Thread not found");
|
|
44
|
+
}
|
|
45
|
+
return thread;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function updateThreadMetadata(
|
|
49
|
+
ctx: RunMutationCtx,
|
|
50
|
+
component: AgentComponent,
|
|
51
|
+
args: { threadId: string; patch: Partial<WithoutSystemFields<ThreadDoc>> },
|
|
52
|
+
) {
|
|
53
|
+
return ctx.runMutation(component.threads.updateThread, {
|
|
54
|
+
threadId: args.threadId,
|
|
55
|
+
patch: args.patch,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Search for threads by title, paginated.
|
|
61
|
+
* @param ctx The context passed from the query/mutation/action.
|
|
62
|
+
* @returns The threads matching the search, paginated.
|
|
63
|
+
*/
|
|
64
|
+
export async function searchThreadTitles(
|
|
65
|
+
ctx: RunQueryCtx,
|
|
66
|
+
component: AgentComponent,
|
|
67
|
+
{
|
|
68
|
+
userId,
|
|
69
|
+
query,
|
|
70
|
+
limit,
|
|
71
|
+
}: { userId?: string | undefined; query: string; limit?: number },
|
|
72
|
+
): Promise<ThreadDoc[]> {
|
|
73
|
+
return ctx.runQuery(component.threads.searchThreadTitles, {
|
|
74
|
+
userId,
|
|
75
|
+
query,
|
|
76
|
+
limit: limit ?? 10,
|
|
77
|
+
});
|
|
78
|
+
}
|