@convex-dev/agent 0.1.15-alpha.1 → 0.1.15
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/README.md +32 -693
- package/dist/client/index.d.ts +26 -26
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +46 -17
- package/dist/client/index.js.map +1 -1
- package/dist/client/search.d.ts +3 -3
- package/dist/client/streaming.d.ts +24 -2
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +35 -4
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/types.d.ts +2 -3
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +7 -1
- package/dist/component/messages.d.ts +40 -40
- package/dist/component/streams.d.ts +8 -3
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +40 -14
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +18 -18
- package/dist/component/users.d.ts +3 -3
- package/dist/mapping.d.ts +5 -0
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +8 -1
- package/dist/mapping.js.map +1 -1
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +20 -1
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts +1 -0
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +8 -1
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/toUIMessages.d.ts +1 -0
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +2 -0
- package/dist/react/toUIMessages.js.map +1 -1
- package/package.json +3 -2
- package/src/client/index.ts +63 -26
- package/src/client/streaming.ts +55 -4
- package/src/client/types.ts +2 -4
- package/src/component/_generated/api.d.ts +7 -1
- package/src/component/streams.ts +49 -16
- package/src/mapping.ts +8 -1
- package/src/react/deltas.ts +24 -3
- package/src/react/index.ts +1 -1
- package/src/react/optimisticallySendMessage.ts +11 -1
- package/src/react/toUIMessages.ts +3 -0
package/src/client/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
|
|
2
2
|
import type {
|
|
3
|
+
AssistantContent,
|
|
3
4
|
CoreMessage,
|
|
4
5
|
DeepPartial,
|
|
5
6
|
FilePart,
|
|
@@ -10,6 +11,7 @@ import type {
|
|
|
10
11
|
StreamObjectResult,
|
|
11
12
|
StreamTextResult,
|
|
12
13
|
ToolSet,
|
|
14
|
+
UserContent,
|
|
13
15
|
} from "ai";
|
|
14
16
|
import { generateObject, generateText, streamObject, streamText } from "ai";
|
|
15
17
|
import { assert } from "convex-helpers";
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
} from "convex/server";
|
|
23
25
|
import { v } from "convex/values";
|
|
24
26
|
import type { MessageDoc, ThreadDoc } from "../component/schema.js";
|
|
27
|
+
import type { threadFieldsSupportingPatch } from "../component/threads.js";
|
|
25
28
|
import {
|
|
26
29
|
validateVectorDimension,
|
|
27
30
|
type VectorDimension,
|
|
@@ -36,23 +39,27 @@ import {
|
|
|
36
39
|
} from "../mapping.js";
|
|
37
40
|
import { extractText, isTool } from "../shared.js";
|
|
38
41
|
import {
|
|
39
|
-
type
|
|
42
|
+
type MessageEmbeddings,
|
|
40
43
|
type MessageStatus,
|
|
44
|
+
type MessageWithMetadata,
|
|
41
45
|
type ProviderMetadata,
|
|
42
46
|
type StreamArgs,
|
|
43
47
|
type Usage,
|
|
44
48
|
vMessageWithMetadata,
|
|
45
49
|
vSafeObjectArgs,
|
|
46
50
|
vTextArgs,
|
|
47
|
-
type MessageEmbeddings,
|
|
48
51
|
} from "../validators.js";
|
|
49
52
|
import { createTool, wrapTools } from "./createTool.js";
|
|
53
|
+
import { listMessages } from "./listMessages.js";
|
|
54
|
+
import { fetchContextMessages } from "./search.js";
|
|
50
55
|
import {
|
|
51
56
|
DeltaStreamer,
|
|
52
57
|
mergeTransforms,
|
|
53
58
|
type StreamingOptions,
|
|
59
|
+
syncStreams,
|
|
54
60
|
} from "./streaming.js";
|
|
55
61
|
import type {
|
|
62
|
+
ActionCtx,
|
|
56
63
|
AgentComponent,
|
|
57
64
|
ContextOptions,
|
|
58
65
|
GenerationOutputMetadata,
|
|
@@ -63,7 +70,6 @@ import type {
|
|
|
63
70
|
RunActionCtx,
|
|
64
71
|
RunMutationCtx,
|
|
65
72
|
RunQueryCtx,
|
|
66
|
-
ActionCtx,
|
|
67
73
|
StorageOptions,
|
|
68
74
|
StreamingTextArgs,
|
|
69
75
|
SyncStreamsReturnValue,
|
|
@@ -71,14 +77,9 @@ import type {
|
|
|
71
77
|
Thread,
|
|
72
78
|
UsageHandler,
|
|
73
79
|
} from "./types.js";
|
|
74
|
-
import type { threadFieldsSupportingPatch } from "../component/threads.js";
|
|
75
|
-
import { listMessages } from "./listMessages.js";
|
|
76
|
-
import { syncStreams } from "./streaming.js";
|
|
77
|
-
import { fetchContextMessages } from "./search.js";
|
|
78
80
|
|
|
79
|
-
export { storeFile, getFile } from "./files.js";
|
|
80
|
-
export { serializeDataOrUrl } from "../mapping.js";
|
|
81
81
|
export { vMessageDoc, vThreadDoc } from "../component/schema.js";
|
|
82
|
+
export { serializeDataOrUrl } from "../mapping.js";
|
|
82
83
|
export {
|
|
83
84
|
vAssistantMessage,
|
|
84
85
|
vContextOptions,
|
|
@@ -93,7 +94,9 @@ export {
|
|
|
93
94
|
vUserMessage,
|
|
94
95
|
} from "../validators.js";
|
|
95
96
|
export type { ToolCtx } from "./createTool.js";
|
|
97
|
+
export { getFile, storeFile } from "./files.js";
|
|
96
98
|
export { filterOutOrphanedToolMessages } from "./search.js";
|
|
99
|
+
export { abortStream, listStreams } from "./streaming.js";
|
|
97
100
|
export {
|
|
98
101
|
createTool,
|
|
99
102
|
extractText,
|
|
@@ -421,7 +424,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
421
424
|
threadId,
|
|
422
425
|
...options,
|
|
423
426
|
});
|
|
424
|
-
const { args: aiArgs, messageId, userId } = context;
|
|
427
|
+
const { args: aiArgs, messageId, order, userId } = context;
|
|
425
428
|
const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
|
|
426
429
|
const tools = wrapTools(
|
|
427
430
|
toolCtx,
|
|
@@ -476,6 +479,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
476
479
|
> &
|
|
477
480
|
GenerationOutputMetadata;
|
|
478
481
|
result.messageId = messageId;
|
|
482
|
+
result.order = order;
|
|
479
483
|
return result;
|
|
480
484
|
} catch (error) {
|
|
481
485
|
if (threadId && messageId) {
|
|
@@ -642,6 +646,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
642
646
|
> &
|
|
643
647
|
GenerationOutputMetadata;
|
|
644
648
|
result.messageId = messageId;
|
|
649
|
+
result.order = order;
|
|
645
650
|
return result;
|
|
646
651
|
}
|
|
647
652
|
|
|
@@ -674,7 +679,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
674
679
|
threadId,
|
|
675
680
|
...options,
|
|
676
681
|
});
|
|
677
|
-
const { args: aiArgs, messageId, userId } = context;
|
|
682
|
+
const { args: aiArgs, messageId, order, userId } = context;
|
|
678
683
|
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
679
684
|
const saveOutputMessages = this._shouldSaveOutputMessages(
|
|
680
685
|
options?.storageOptions
|
|
@@ -696,6 +701,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
696
701
|
});
|
|
697
702
|
}
|
|
698
703
|
result.messageId = messageId;
|
|
704
|
+
result.order = order;
|
|
699
705
|
if (this.options.rawRequestResponseHandler) {
|
|
700
706
|
await this.options.rawRequestResponseHandler(ctx, {
|
|
701
707
|
userId,
|
|
@@ -760,7 +766,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
760
766
|
threadId,
|
|
761
767
|
...options,
|
|
762
768
|
});
|
|
763
|
-
const { args: aiArgs, messageId, userId } = context;
|
|
769
|
+
const { args: aiArgs, messageId, order, userId } = context;
|
|
764
770
|
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
765
771
|
const saveOutputMessages = this._shouldSaveOutputMessages(
|
|
766
772
|
options?.storageOptions
|
|
@@ -821,6 +827,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
821
827
|
}) as StreamObjectResult<DeepPartial<T>, T, never> &
|
|
822
828
|
GenerationOutputMetadata;
|
|
823
829
|
stream.messageId = messageId;
|
|
830
|
+
stream.order = order;
|
|
824
831
|
return stream;
|
|
825
832
|
}
|
|
826
833
|
|
|
@@ -846,6 +853,12 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
846
853
|
const { lastMessageId, messages } = await this.saveMessages(ctx, {
|
|
847
854
|
threadId: args.threadId,
|
|
848
855
|
userId: args.userId,
|
|
856
|
+
embeddings: args.embedding
|
|
857
|
+
? {
|
|
858
|
+
model: args.embedding.model,
|
|
859
|
+
vectors: [args.embedding.vector],
|
|
860
|
+
}
|
|
861
|
+
: undefined,
|
|
849
862
|
messages:
|
|
850
863
|
args.prompt !== undefined
|
|
851
864
|
? [{ role: "user", content: args.prompt }]
|
|
@@ -882,7 +895,6 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
882
895
|
let embeddings:
|
|
883
896
|
| {
|
|
884
897
|
vectors: (number[] | null)[];
|
|
885
|
-
dimension: VectorDimension;
|
|
886
898
|
model: string;
|
|
887
899
|
}
|
|
888
900
|
| undefined;
|
|
@@ -1678,7 +1690,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
1678
1690
|
return Promise.all(
|
|
1679
1691
|
messages.map(async (message): Promise<CoreMessage> => {
|
|
1680
1692
|
if (
|
|
1681
|
-
message.role !== "user" ||
|
|
1693
|
+
(message.role !== "user" && message.role !== "assistant") ||
|
|
1682
1694
|
typeof message.content === "string" ||
|
|
1683
1695
|
!Array.isArray(message.content)
|
|
1684
1696
|
) {
|
|
@@ -1688,6 +1700,10 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
1688
1700
|
const processedContent = await Promise.all(
|
|
1689
1701
|
message.content.map(async (part) => {
|
|
1690
1702
|
if (part.type === "image" && part.image instanceof URL) {
|
|
1703
|
+
assert(
|
|
1704
|
+
message.role === "user",
|
|
1705
|
+
"Images can only be in user messages"
|
|
1706
|
+
);
|
|
1691
1707
|
if (this._isLocalhostUrl(part.image)) {
|
|
1692
1708
|
const imageData = await this._downloadFile(part.image);
|
|
1693
1709
|
return {
|
|
@@ -1711,11 +1727,17 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
|
|
|
1711
1727
|
return part;
|
|
1712
1728
|
})
|
|
1713
1729
|
);
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1730
|
+
if (message.role === "user") {
|
|
1731
|
+
return {
|
|
1732
|
+
...message,
|
|
1733
|
+
content: processedContent as UserContent,
|
|
1734
|
+
};
|
|
1735
|
+
} else {
|
|
1736
|
+
return {
|
|
1737
|
+
...message,
|
|
1738
|
+
content: processedContent as AssistantContent,
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1719
1741
|
})
|
|
1720
1742
|
);
|
|
1721
1743
|
}
|
|
@@ -2017,7 +2039,7 @@ type SaveMessagesArgs = {
|
|
|
2017
2039
|
/**
|
|
2018
2040
|
* The embeddings to save with the messages.
|
|
2019
2041
|
*/
|
|
2020
|
-
embeddings?: MessageEmbeddings
|
|
2042
|
+
embeddings?: Omit<MessageEmbeddings, "dimension">;
|
|
2021
2043
|
};
|
|
2022
2044
|
|
|
2023
2045
|
/**
|
|
@@ -2033,12 +2055,24 @@ export async function saveMessages(
|
|
|
2033
2055
|
agentName?: string;
|
|
2034
2056
|
}
|
|
2035
2057
|
) {
|
|
2058
|
+
let embeddings: MessageEmbeddings | undefined;
|
|
2059
|
+
if (args.embeddings) {
|
|
2060
|
+
const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
|
|
2061
|
+
if (dimension) {
|
|
2062
|
+
validateVectorDimension(dimension);
|
|
2063
|
+
embeddings = {
|
|
2064
|
+
model: args.embeddings.model,
|
|
2065
|
+
dimension,
|
|
2066
|
+
vectors: args.embeddings.vectors,
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2036
2070
|
const result = await ctx.runMutation(component.messages.addMessages, {
|
|
2037
2071
|
threadId: args.threadId,
|
|
2038
2072
|
userId: args.userId,
|
|
2039
2073
|
agentName: args.agentName,
|
|
2040
2074
|
promptMessageId: args.promptMessageId,
|
|
2041
|
-
embeddings
|
|
2075
|
+
embeddings,
|
|
2042
2076
|
messages: await Promise.all(
|
|
2043
2077
|
args.messages.map(async (m, i) => {
|
|
2044
2078
|
const { message, fileIds } = await serializeMessage(ctx, component, m);
|
|
@@ -2107,19 +2141,22 @@ export async function saveMessage(
|
|
|
2107
2141
|
agentName?: string;
|
|
2108
2142
|
}
|
|
2109
2143
|
) {
|
|
2110
|
-
let embeddings:
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2144
|
+
let embeddings:
|
|
2145
|
+
| {
|
|
2146
|
+
vectors: number[][];
|
|
2147
|
+
model: string;
|
|
2148
|
+
}
|
|
2149
|
+
| undefined;
|
|
2150
|
+
if (args.embedding && args.embedding.vector) {
|
|
2114
2151
|
embeddings = {
|
|
2115
2152
|
model: args.embedding.model,
|
|
2116
|
-
dimension,
|
|
2117
2153
|
vectors: [args.embedding.vector],
|
|
2118
2154
|
};
|
|
2119
2155
|
}
|
|
2120
2156
|
const { lastMessageId, messages } = await saveMessages(ctx, component, {
|
|
2121
2157
|
threadId: args.threadId,
|
|
2122
2158
|
userId: args.userId,
|
|
2159
|
+
agentName: args.agentName,
|
|
2123
2160
|
messages:
|
|
2124
2161
|
args.prompt !== undefined
|
|
2125
2162
|
? [{ role: "user", content: args.prompt }]
|
package/src/client/streaming.ts
CHANGED
|
@@ -8,15 +8,18 @@ import type {
|
|
|
8
8
|
ProviderOptions,
|
|
9
9
|
StreamArgs,
|
|
10
10
|
StreamDelta,
|
|
11
|
+
StreamMessage,
|
|
11
12
|
TextStreamPart,
|
|
12
13
|
} from "../validators.js";
|
|
13
14
|
import type { MessageDoc } from "../component/schema.js";
|
|
14
15
|
import type {
|
|
15
16
|
AgentComponent,
|
|
16
17
|
RunActionCtx,
|
|
18
|
+
RunMutationCtx,
|
|
17
19
|
RunQueryCtx,
|
|
18
20
|
SyncStreamsReturnValue,
|
|
19
21
|
} from "./types.js";
|
|
22
|
+
import { omit } from "convex-helpers";
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* A function that handles fetching stream deltas, used with the React hooks
|
|
@@ -41,10 +44,10 @@ export async function syncStreams(
|
|
|
41
44
|
if (args.streamArgs.kind === "list") {
|
|
42
45
|
return {
|
|
43
46
|
kind: "list",
|
|
44
|
-
messages: await ctx
|
|
47
|
+
messages: await listStreams(ctx, component, {
|
|
45
48
|
threadId: args.threadId,
|
|
46
49
|
startOrder: args.streamArgs.startOrder,
|
|
47
|
-
|
|
50
|
+
includeStatuses: args.includeStatuses,
|
|
48
51
|
}),
|
|
49
52
|
};
|
|
50
53
|
} else {
|
|
@@ -58,6 +61,55 @@ export async function syncStreams(
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
export async function abortStream(
|
|
65
|
+
ctx: RunMutationCtx,
|
|
66
|
+
component: AgentComponent,
|
|
67
|
+
args: {
|
|
68
|
+
reason: string;
|
|
69
|
+
} & ({ streamId: string } | { threadId: string; order: number })
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
if ("streamId" in args) {
|
|
72
|
+
return await ctx.runMutation(component.streams.abort, {
|
|
73
|
+
reason: args.reason,
|
|
74
|
+
streamId: args.streamId,
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
return await ctx.runMutation(component.streams.abortByOrder, {
|
|
78
|
+
reason: args.reason,
|
|
79
|
+
threadId: args.threadId,
|
|
80
|
+
order: args.order,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* List the streaming messages for a thread.
|
|
87
|
+
* @param ctx A ctx object from a query, mutation, or action.
|
|
88
|
+
* @param component The agent component, usually `components.agent`.
|
|
89
|
+
* @param args.threadId The thread to list streams for.
|
|
90
|
+
* @param args.startOrder The order of the messages in the thread to start listing from.
|
|
91
|
+
* @param args.includeStatuses The statuses to include in the list.
|
|
92
|
+
* @returns The streams for the thread.
|
|
93
|
+
*/
|
|
94
|
+
export async function listStreams(
|
|
95
|
+
ctx: RunQueryCtx,
|
|
96
|
+
component: AgentComponent,
|
|
97
|
+
{
|
|
98
|
+
threadId,
|
|
99
|
+
startOrder,
|
|
100
|
+
includeStatuses,
|
|
101
|
+
}: {
|
|
102
|
+
threadId: string;
|
|
103
|
+
startOrder?: number;
|
|
104
|
+
includeStatuses?: ("streaming" | "finished" | "aborted")[];
|
|
105
|
+
}
|
|
106
|
+
): Promise<StreamMessage[]> {
|
|
107
|
+
return ctx.runQuery(component.streams.list, {
|
|
108
|
+
threadId,
|
|
109
|
+
startOrder,
|
|
110
|
+
statuses: includeStatuses,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
61
113
|
|
|
62
114
|
export type StreamingOptions = {
|
|
63
115
|
/**
|
|
@@ -137,7 +189,6 @@ export class DeltaStreamer {
|
|
|
137
189
|
...DEFAULT_STREAMING_OPTIONS,
|
|
138
190
|
...options,
|
|
139
191
|
};
|
|
140
|
-
this.metadata = metadata;
|
|
141
192
|
this.#nextParts = [];
|
|
142
193
|
this.#nextOrder = metadata.order ?? 0;
|
|
143
194
|
this.#nextStepOrder = (metadata.stepOrder ?? 0) + 1;
|
|
@@ -162,7 +213,7 @@ export class DeltaStreamer {
|
|
|
162
213
|
this.streamId = await this.ctx.runMutation(
|
|
163
214
|
this.component.streams.create,
|
|
164
215
|
{
|
|
165
|
-
...this.metadata,
|
|
216
|
+
...omit(this.metadata, ["abortSignal"]),
|
|
166
217
|
order: this.#nextOrder,
|
|
167
218
|
stepOrder: this.#nextStepOrder,
|
|
168
219
|
}
|
package/src/client/types.ts
CHANGED
|
@@ -131,7 +131,7 @@ export type StorageOptions = {
|
|
|
131
131
|
saveOutputMessages?: boolean;
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
export type GenerationOutputMetadata = { messageId?: string };
|
|
134
|
+
export type GenerationOutputMetadata = { messageId?: string; order?: number };
|
|
135
135
|
|
|
136
136
|
export type UsageHandler = (
|
|
137
137
|
ctx: RunActionCtx,
|
|
@@ -333,9 +333,7 @@ export type OurStreamObjectArgs<T> = StreamObjectArgs<T> &
|
|
|
333
333
|
"onError" | "onFinish" | "abortSignal"
|
|
334
334
|
>;
|
|
335
335
|
|
|
336
|
-
type ThreadOutputMetadata = GenerationOutputMetadata
|
|
337
|
-
messageId: string;
|
|
338
|
-
};
|
|
336
|
+
type ThreadOutputMetadata = Required<GenerationOutputMetadata>;
|
|
339
337
|
|
|
340
338
|
/**
|
|
341
339
|
* The interface for a thread returned from {@link createThread} or {@link continueThread}.
|
|
@@ -1437,7 +1437,13 @@ export type Mounts = {
|
|
|
1437
1437
|
"mutation",
|
|
1438
1438
|
"public",
|
|
1439
1439
|
{ reason: string; streamId: string },
|
|
1440
|
-
|
|
1440
|
+
boolean
|
|
1441
|
+
>;
|
|
1442
|
+
abortByOrder: FunctionReference<
|
|
1443
|
+
"mutation",
|
|
1444
|
+
"public",
|
|
1445
|
+
{ order: number; reason: string; threadId: string },
|
|
1446
|
+
boolean
|
|
1441
1447
|
>;
|
|
1442
1448
|
addDelta: FunctionReference<
|
|
1443
1449
|
"mutation",
|
package/src/component/streams.ts
CHANGED
|
@@ -151,30 +151,63 @@ export const list = query({
|
|
|
151
151
|
},
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
export const
|
|
154
|
+
export const abortByOrder = mutation({
|
|
155
155
|
args: {
|
|
156
|
-
|
|
156
|
+
threadId: v.id("threads"),
|
|
157
|
+
order: v.number(),
|
|
157
158
|
reason: v.string(),
|
|
158
159
|
},
|
|
159
|
-
returns: v.
|
|
160
|
+
returns: v.boolean(),
|
|
160
161
|
handler: async (ctx, args) => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
|
|
162
|
+
const streams = await ctx.db
|
|
163
|
+
.query("streamingMessages")
|
|
164
|
+
.withIndex("threadId_state_order_stepOrder", (q) =>
|
|
165
|
+
q
|
|
166
|
+
.eq("threadId", args.threadId)
|
|
167
|
+
.eq("state.kind", "streaming")
|
|
168
|
+
.eq("order", args.order)
|
|
169
|
+
)
|
|
170
|
+
.take(100);
|
|
171
|
+
for (const stream of streams) {
|
|
172
|
+
await abortById(ctx, {
|
|
173
|
+
streamId: stream._id,
|
|
174
|
+
reason: args.reason,
|
|
175
|
+
});
|
|
170
176
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
return streams.length > 0;
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export const abort = mutation({
|
|
182
|
+
args: {
|
|
183
|
+
streamId: v.id("streamingMessages"),
|
|
184
|
+
reason: v.string(),
|
|
175
185
|
},
|
|
186
|
+
returns: v.boolean(),
|
|
187
|
+
handler: abortById,
|
|
176
188
|
});
|
|
177
189
|
|
|
190
|
+
async function abortById(
|
|
191
|
+
ctx: MutationCtx,
|
|
192
|
+
args: { streamId: Id<"streamingMessages">; reason: string }
|
|
193
|
+
) {
|
|
194
|
+
const stream = await ctx.db.get(args.streamId);
|
|
195
|
+
if (!stream) {
|
|
196
|
+
throw new Error(`Stream not found: ${args.streamId}`);
|
|
197
|
+
}
|
|
198
|
+
if (stream.state.kind !== "streaming") {
|
|
199
|
+
console.warn(
|
|
200
|
+
`Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
|
|
201
|
+
);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
await cleanupTimeoutFn(ctx, stream);
|
|
205
|
+
await ctx.db.patch(args.streamId, {
|
|
206
|
+
state: { kind: "aborted", reason: args.reason },
|
|
207
|
+
});
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
178
211
|
async function cleanupTimeoutFn(
|
|
179
212
|
ctx: MutationCtx,
|
|
180
213
|
stream: Doc<"streamingMessages">
|
package/src/mapping.ts
CHANGED
|
@@ -125,7 +125,6 @@ export async function serializeObjectResult(
|
|
|
125
125
|
const { message, fileIds } = await serializeMessage(ctx, component, {
|
|
126
126
|
role: "assistant" as const,
|
|
127
127
|
content: text,
|
|
128
|
-
id: result.response.id,
|
|
129
128
|
});
|
|
130
129
|
return {
|
|
131
130
|
messages: [
|
|
@@ -187,6 +186,9 @@ export async function serializeContent(
|
|
|
187
186
|
}
|
|
188
187
|
return { ...part, data };
|
|
189
188
|
}
|
|
189
|
+
case "tool-result": {
|
|
190
|
+
return { ...part, result: part.result ?? null };
|
|
191
|
+
}
|
|
190
192
|
default:
|
|
191
193
|
return part;
|
|
192
194
|
}
|
|
@@ -281,6 +283,11 @@ export function guessMimeType(buf: ArrayBuffer | string): string {
|
|
|
281
283
|
return "application/octet-stream";
|
|
282
284
|
}
|
|
283
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Serialize an AI SDK `DataContent` or `URL` to a Convex-serializable format.
|
|
288
|
+
* @param dataOrUrl - The data or URL to serialize.
|
|
289
|
+
* @returns The serialized data as an ArrayBuffer or the URL as a string.
|
|
290
|
+
*/
|
|
284
291
|
export function serializeDataOrUrl(
|
|
285
292
|
dataOrUrl: DataContent | URL
|
|
286
293
|
): ArrayBuffer | string {
|
package/src/react/deltas.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
|
|
|
2
2
|
import type { MessageDoc } from "../client/index.js";
|
|
3
3
|
import type {
|
|
4
4
|
Message,
|
|
5
|
+
MessageStatus,
|
|
5
6
|
StreamDelta,
|
|
6
7
|
StreamMessage,
|
|
7
8
|
TextStreamPart,
|
|
@@ -99,6 +100,12 @@ export function applyDeltasToStreamMessage(
|
|
|
99
100
|
cursor = delta.end;
|
|
100
101
|
parts.push(...delta.parts);
|
|
101
102
|
}
|
|
103
|
+
if (existing && existing.messages.length > 0 && !changed) {
|
|
104
|
+
const lastMessage = existing.messages.at(-1)!;
|
|
105
|
+
if (statusFromStreamStatus(streamMessage.status) !== lastMessage.status) {
|
|
106
|
+
changed = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
102
109
|
if (!changed) {
|
|
103
110
|
return [
|
|
104
111
|
existing ?? { streamId: streamMessage.streamId, cursor, messages: [] },
|
|
@@ -115,6 +122,7 @@ export function applyDeltasToStreamMessage(
|
|
|
115
122
|
currentMessage = {
|
|
116
123
|
...lastMessage,
|
|
117
124
|
message: cloneMessageAndContent(lastMessage.message),
|
|
125
|
+
status: statusFromStreamStatus(streamMessage.status),
|
|
118
126
|
};
|
|
119
127
|
} else {
|
|
120
128
|
const newMessage = createStreamingMessage(
|
|
@@ -248,6 +256,21 @@ function getLastContent(message: MessageDoc) {
|
|
|
248
256
|
return undefined;
|
|
249
257
|
}
|
|
250
258
|
|
|
259
|
+
function statusFromStreamStatus(
|
|
260
|
+
status: StreamMessage["status"]
|
|
261
|
+
): MessageStatus {
|
|
262
|
+
switch (status) {
|
|
263
|
+
case "streaming":
|
|
264
|
+
return "pending";
|
|
265
|
+
case "finished":
|
|
266
|
+
return "success";
|
|
267
|
+
case "aborted":
|
|
268
|
+
return "failed";
|
|
269
|
+
default:
|
|
270
|
+
return "pending";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
251
274
|
export function createStreamingMessage(
|
|
252
275
|
threadId: string,
|
|
253
276
|
message: StreamMessage,
|
|
@@ -259,9 +282,7 @@ export function createStreamingMessage(
|
|
|
259
282
|
...rest,
|
|
260
283
|
_id: `${streamId}-${index}`,
|
|
261
284
|
_creationTime: Date.now(),
|
|
262
|
-
status: (
|
|
263
|
-
{ streaming: "pending", finished: "success", aborted: "failed" } as const
|
|
264
|
-
)[message.status],
|
|
285
|
+
status: statusFromStreamStatus(message.status),
|
|
265
286
|
threadId,
|
|
266
287
|
tool: false,
|
|
267
288
|
};
|
package/src/react/index.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function useThreadMessages<
|
|
|
114
114
|
const streamListMessages =
|
|
115
115
|
streamMessages?.map((m) => ({
|
|
116
116
|
...m,
|
|
117
|
-
streaming: !m.status || m.status === "
|
|
117
|
+
streaming: !m.status || m.status === "pending",
|
|
118
118
|
})) ?? [];
|
|
119
119
|
return {
|
|
120
120
|
...paginated,
|
|
@@ -28,7 +28,7 @@ export function optimisticallySendMessage(
|
|
|
28
28
|
argsToMatch: { threadId: args.threadId, streamArgs: undefined },
|
|
29
29
|
item: {
|
|
30
30
|
_creationTime: Date.now(),
|
|
31
|
-
_id:
|
|
31
|
+
_id: randomUUID(),
|
|
32
32
|
order,
|
|
33
33
|
stepOrder,
|
|
34
34
|
status: "pending",
|
|
@@ -44,3 +44,13 @@ export function optimisticallySendMessage(
|
|
|
44
44
|
});
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
export function randomUUID() {
|
|
49
|
+
if (typeof crypto !== "undefined") {
|
|
50
|
+
return crypto.randomUUID();
|
|
51
|
+
}
|
|
52
|
+
return (
|
|
53
|
+
Math.random().toString(36).substring(2, 15) +
|
|
54
|
+
Math.random().toString(36).substring(2, 15)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -9,6 +9,7 @@ export type UIMessage = AIUIMessage & {
|
|
|
9
9
|
order: number;
|
|
10
10
|
stepOrder: number;
|
|
11
11
|
status: "streaming" | MessageStatus;
|
|
12
|
+
agentName?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export function toUIMessages(
|
|
@@ -36,6 +37,7 @@ export function toUIMessages(
|
|
|
36
37
|
...common,
|
|
37
38
|
role: "system",
|
|
38
39
|
content: text,
|
|
40
|
+
agentName: message.agentName,
|
|
39
41
|
parts: [{ type: "text", text }],
|
|
40
42
|
});
|
|
41
43
|
} else if (coreMessage.role === "user") {
|
|
@@ -69,6 +71,7 @@ export function toUIMessages(
|
|
|
69
71
|
assistantMessage = {
|
|
70
72
|
...common,
|
|
71
73
|
role: "assistant",
|
|
74
|
+
agentName: message.agentName,
|
|
72
75
|
content: "",
|
|
73
76
|
parts: [],
|
|
74
77
|
};
|