@convex-dev/agent 0.0.1-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/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/commonjs/client/index.d.ts +198 -0
- package/dist/commonjs/client/index.d.ts.map +1 -0
- package/dist/commonjs/client/index.js +365 -0
- package/dist/commonjs/client/index.js.map +1 -0
- package/dist/commonjs/client/types.d.ts +21 -0
- package/dist/commonjs/client/types.d.ts.map +1 -0
- package/dist/commonjs/client/types.js +2 -0
- package/dist/commonjs/client/types.js.map +1 -0
- package/dist/commonjs/component/_generated/api.d.ts +12 -0
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/api.js +22 -0
- package/dist/commonjs/component/_generated/api.js.map +1 -0
- package/dist/commonjs/component/_generated/server.d.ts +64 -0
- package/dist/commonjs/component/_generated/server.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/server.js +74 -0
- package/dist/commonjs/component/_generated/server.js.map +1 -0
- package/dist/commonjs/component/convex.config.d.ts +3 -0
- package/dist/commonjs/component/convex.config.d.ts.map +1 -0
- package/dist/commonjs/component/convex.config.js +3 -0
- package/dist/commonjs/component/convex.config.js.map +1 -0
- package/dist/commonjs/component/lib.d.ts +2 -0
- package/dist/commonjs/component/lib.d.ts.map +1 -0
- package/dist/commonjs/component/lib.js +2 -0
- package/dist/commonjs/component/lib.js.map +1 -0
- package/dist/commonjs/component/messages.d.ts +1913 -0
- package/dist/commonjs/component/messages.d.ts.map +1 -0
- package/dist/commonjs/component/messages.js +787 -0
- package/dist/commonjs/component/messages.js.map +1 -0
- package/dist/commonjs/component/schema.d.ts +5496 -0
- package/dist/commonjs/component/schema.d.ts.map +1 -0
- package/dist/commonjs/component/schema.js +97 -0
- package/dist/commonjs/component/schema.js.map +1 -0
- package/dist/commonjs/component/vector/tables.d.ts +40 -0
- package/dist/commonjs/component/vector/tables.d.ts.map +1 -0
- package/dist/commonjs/component/vector/tables.js +46 -0
- package/dist/commonjs/component/vector/tables.js.map +1 -0
- package/dist/commonjs/mapping.d.ts +26 -0
- package/dist/commonjs/mapping.d.ts.map +1 -0
- package/dist/commonjs/mapping.js +101 -0
- package/dist/commonjs/mapping.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/react/index.d.ts +2 -0
- package/dist/commonjs/react/index.d.ts.map +1 -0
- package/dist/commonjs/react/index.js +8 -0
- package/dist/commonjs/react/index.js.map +1 -0
- package/dist/commonjs/shared.d.ts +9 -0
- package/dist/commonjs/shared.d.ts.map +1 -0
- package/dist/commonjs/shared.js +29 -0
- package/dist/commonjs/shared.js.map +1 -0
- package/dist/commonjs/validators.d.ts +6177 -0
- package/dist/commonjs/validators.d.ts.map +1 -0
- package/dist/commonjs/validators.js +171 -0
- package/dist/commonjs/validators.js.map +1 -0
- package/dist/esm/client/index.d.ts +198 -0
- package/dist/esm/client/index.d.ts.map +1 -0
- package/dist/esm/client/index.js +365 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/types.d.ts +21 -0
- package/dist/esm/client/types.d.ts.map +1 -0
- package/dist/esm/client/types.js +2 -0
- package/dist/esm/client/types.js.map +1 -0
- package/dist/esm/component/_generated/api.d.ts +12 -0
- package/dist/esm/component/_generated/api.d.ts.map +1 -0
- package/dist/esm/component/_generated/api.js +22 -0
- package/dist/esm/component/_generated/api.js.map +1 -0
- package/dist/esm/component/_generated/server.d.ts +64 -0
- package/dist/esm/component/_generated/server.d.ts.map +1 -0
- package/dist/esm/component/_generated/server.js +74 -0
- package/dist/esm/component/_generated/server.js.map +1 -0
- package/dist/esm/component/convex.config.d.ts +3 -0
- package/dist/esm/component/convex.config.d.ts.map +1 -0
- package/dist/esm/component/convex.config.js +3 -0
- package/dist/esm/component/convex.config.js.map +1 -0
- package/dist/esm/component/lib.d.ts +2 -0
- package/dist/esm/component/lib.d.ts.map +1 -0
- package/dist/esm/component/lib.js +2 -0
- package/dist/esm/component/lib.js.map +1 -0
- package/dist/esm/component/messages.d.ts +1913 -0
- package/dist/esm/component/messages.d.ts.map +1 -0
- package/dist/esm/component/messages.js +787 -0
- package/dist/esm/component/messages.js.map +1 -0
- package/dist/esm/component/schema.d.ts +5496 -0
- package/dist/esm/component/schema.d.ts.map +1 -0
- package/dist/esm/component/schema.js +97 -0
- package/dist/esm/component/schema.js.map +1 -0
- package/dist/esm/component/vector/tables.d.ts +40 -0
- package/dist/esm/component/vector/tables.d.ts.map +1 -0
- package/dist/esm/component/vector/tables.js +46 -0
- package/dist/esm/component/vector/tables.js.map +1 -0
- package/dist/esm/mapping.d.ts +26 -0
- package/dist/esm/mapping.d.ts.map +1 -0
- package/dist/esm/mapping.js +101 -0
- package/dist/esm/mapping.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/react/index.d.ts +2 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +8 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/shared.d.ts +9 -0
- package/dist/esm/shared.d.ts.map +1 -0
- package/dist/esm/shared.js +29 -0
- package/dist/esm/shared.js.map +1 -0
- package/dist/esm/validators.d.ts +6177 -0
- package/dist/esm/validators.d.ts.map +1 -0
- package/dist/esm/validators.js +171 -0
- package/dist/esm/validators.js.map +1 -0
- package/package.json +91 -0
- package/react/package.json +5 -0
- package/src/client/index.ts +659 -0
- package/src/client/types.ts +54 -0
- package/src/component/_generated/api.d.ts +1497 -0
- package/src/component/_generated/api.js +23 -0
- package/src/component/_generated/dataModel.d.ts +60 -0
- package/src/component/_generated/server.d.ts +149 -0
- package/src/component/_generated/server.js +90 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +13 -0
- package/src/component/lib.ts +2 -0
- package/src/component/messages.ts +959 -0
- package/src/component/schema.ts +101 -0
- package/src/component/setup.test.ts +5 -0
- package/src/component/vector/tables.ts +92 -0
- package/src/mapping.ts +160 -0
- package/src/react/index.ts +8 -0
- package/src/shared.ts +35 -0
- package/src/validators.test.ts +101 -0
- package/src/validators.ts +258 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { vChatStatus, vMessage, vMessageStatus, vStep } from "../validators";
|
|
4
|
+
import { typedV } from "convex-helpers/validators";
|
|
5
|
+
import vectorTables, { vVectorId } from "./vector/tables";
|
|
6
|
+
|
|
7
|
+
export const schema = defineSchema({
|
|
8
|
+
chats: defineTable({
|
|
9
|
+
userId: v.optional(v.string()), // Unset for anonymous
|
|
10
|
+
order: v.optional(v.number()), // within a domain
|
|
11
|
+
// TODO: is this bubbling up in continue?
|
|
12
|
+
defaultSystemPrompt: v.optional(v.string()),
|
|
13
|
+
title: v.optional(v.string()),
|
|
14
|
+
summary: v.optional(v.string()),
|
|
15
|
+
status: vChatStatus,
|
|
16
|
+
// If this is a chat continuation, we can use this to find context from
|
|
17
|
+
// the parent chat(s). There are multiple if the chat is a merging of
|
|
18
|
+
// multiple chats.
|
|
19
|
+
parentChatIds: v.optional(v.array(v.id("chats"))),
|
|
20
|
+
}).index("status_userId_order", ["status", "userId", "order"]),
|
|
21
|
+
// TODO: text search on title/ summary
|
|
22
|
+
messages: defineTable({
|
|
23
|
+
id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
|
|
24
|
+
userId: v.optional(v.string()), // useful for future indexes (text search)
|
|
25
|
+
chatId: v.id("chats"),
|
|
26
|
+
threadId: v.optional(v.id("messages")),
|
|
27
|
+
stepId: v.optional(v.id("steps")),
|
|
28
|
+
agentName: v.optional(v.string()),
|
|
29
|
+
message: v.optional(vMessage),
|
|
30
|
+
model: v.optional(v.string()),
|
|
31
|
+
text: v.optional(v.string()),
|
|
32
|
+
embeddingId: v.optional(vVectorId),
|
|
33
|
+
// TODO: add sub-messages back in? or be able to skip them?
|
|
34
|
+
tool: v.boolean(),
|
|
35
|
+
// Repeats until a non-tool message.
|
|
36
|
+
// Set when the message is finished
|
|
37
|
+
order: v.optional(v.number()),
|
|
38
|
+
stepOrder: v.optional(v.number()),
|
|
39
|
+
fileId: v.optional(v.id("files")),
|
|
40
|
+
status: vMessageStatus,
|
|
41
|
+
})
|
|
42
|
+
// Allows finding successful visible messages in order
|
|
43
|
+
// Also surface pending messages separately to e.g. stream
|
|
44
|
+
.index("chatId_status_tool_order_stepOrder", [
|
|
45
|
+
"chatId",
|
|
46
|
+
"status",
|
|
47
|
+
"tool",
|
|
48
|
+
"order",
|
|
49
|
+
"stepOrder",
|
|
50
|
+
])
|
|
51
|
+
// Allows finding all threaded messages in order
|
|
52
|
+
// Allows finding all failed messages to evaluate
|
|
53
|
+
// .index("status_threadId_order_stepOrder", [
|
|
54
|
+
// "status",
|
|
55
|
+
// "threadId",
|
|
56
|
+
// "order",
|
|
57
|
+
// "stepOrder",
|
|
58
|
+
// ])
|
|
59
|
+
// Allows text search on message content
|
|
60
|
+
.searchIndex("text_search", {
|
|
61
|
+
searchField: "text",
|
|
62
|
+
filterFields: ["userId", "chatId"],
|
|
63
|
+
})
|
|
64
|
+
// Allows finding messages by vector embedding id
|
|
65
|
+
.index("embeddingId", ["embeddingId"]),
|
|
66
|
+
|
|
67
|
+
steps: defineTable({
|
|
68
|
+
chatId: v.id("chats"),
|
|
69
|
+
// Could be different from the order if we fail.
|
|
70
|
+
parentMessageId: v.id("messages"),
|
|
71
|
+
order: v.number(), // parent message order
|
|
72
|
+
stepOrder: v.number(), // step order
|
|
73
|
+
step: vStep,
|
|
74
|
+
status: vMessageStatus,
|
|
75
|
+
})
|
|
76
|
+
.index("status_chatId_order_stepOrder", [
|
|
77
|
+
"status",
|
|
78
|
+
"chatId",
|
|
79
|
+
"order",
|
|
80
|
+
"stepOrder",
|
|
81
|
+
])
|
|
82
|
+
.index("parentMessageId_order_stepOrder", [
|
|
83
|
+
"parentMessageId",
|
|
84
|
+
"order",
|
|
85
|
+
"stepOrder",
|
|
86
|
+
]),
|
|
87
|
+
|
|
88
|
+
files: defineTable({
|
|
89
|
+
storageId: v.string(),
|
|
90
|
+
hash: v.string(),
|
|
91
|
+
refcount: v.number(),
|
|
92
|
+
})
|
|
93
|
+
.index("hash", ["hash"])
|
|
94
|
+
.index("refcount", ["refcount"]),
|
|
95
|
+
...vectorTables,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export const vv = typedV(schema);
|
|
99
|
+
export { vv as v };
|
|
100
|
+
|
|
101
|
+
export default schema;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { literals } from "convex-helpers/validators";
|
|
2
|
+
import {
|
|
3
|
+
defineTable,
|
|
4
|
+
GenericTableSearchIndexes,
|
|
5
|
+
TableDefinition,
|
|
6
|
+
} from "convex/server";
|
|
7
|
+
import { GenericId, ObjectType, v, VId, VObject, VUnion } from "convex/values";
|
|
8
|
+
import { QueryCtx } from "../_generated/server";
|
|
9
|
+
|
|
10
|
+
// We only generate embeddings for non-tool, non-system messages
|
|
11
|
+
const embeddings = {
|
|
12
|
+
model: v.string(),
|
|
13
|
+
kind: v.union(v.literal("chat"), v.literal("memory")),
|
|
14
|
+
userId: v.optional(v.string()),
|
|
15
|
+
chatId: v.optional(v.string()),
|
|
16
|
+
// not set for private chats
|
|
17
|
+
model_kind_userId: v.optional(v.array(v.string())),
|
|
18
|
+
model_kind_chatId: v.optional(v.array(v.string())),
|
|
19
|
+
vector: v.array(v.number()),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function table<D extends number>(dimensions: D): Table<D> {
|
|
23
|
+
return defineTable(embeddings).vectorIndex("vector", {
|
|
24
|
+
vectorField: "vector",
|
|
25
|
+
dimensions,
|
|
26
|
+
filterFields: ["model_kind_userId", "model_kind_chatId"],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const VectorDimensions = [
|
|
31
|
+
128, 256, 512, 768, 1024, 1536, 2048, 3072, 4096,
|
|
32
|
+
] as const;
|
|
33
|
+
export type VectorDimension = (typeof VectorDimensions)[number];
|
|
34
|
+
export const VectorTableNames = VectorDimensions.map(
|
|
35
|
+
(d) => `embeddings_${d}`
|
|
36
|
+
) as `embeddings_${(typeof VectorDimensions)[number]}`[];
|
|
37
|
+
export type VectorTableName = (typeof VectorTableNames)[number];
|
|
38
|
+
export type VectorTableId = GenericId<(typeof VectorTableNames)[number]>;
|
|
39
|
+
|
|
40
|
+
export const vVectorDimension = literals(...VectorDimensions);
|
|
41
|
+
export const vVectorTableName = literals(...VectorTableNames);
|
|
42
|
+
export const vVectorId = v.union(
|
|
43
|
+
...VectorTableNames.map((name) => v.id(name))
|
|
44
|
+
) as VUnion<
|
|
45
|
+
GenericId<(typeof VectorTableNames)[number]>,
|
|
46
|
+
VId<(typeof VectorTableNames)[number]>[]
|
|
47
|
+
>;
|
|
48
|
+
|
|
49
|
+
type Table<D extends number> = TableDefinition<
|
|
50
|
+
VObject<ObjectType<typeof embeddings>, typeof embeddings>,
|
|
51
|
+
{ id: ["id"] },
|
|
52
|
+
GenericTableSearchIndexes,
|
|
53
|
+
VectorIndex<D>
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
type VectorIndex<D extends number> = {
|
|
57
|
+
vector: {
|
|
58
|
+
vectorField: "vector";
|
|
59
|
+
dimensions: D;
|
|
60
|
+
filterFields: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function getVectorTableName(dimension: VectorDimension) {
|
|
65
|
+
return `embeddings_${dimension}` as VectorTableName;
|
|
66
|
+
}
|
|
67
|
+
export function getVectorIdInfo(ctx: QueryCtx, id: VectorTableId) {
|
|
68
|
+
for (const dimension of VectorDimensions) {
|
|
69
|
+
const tableName = getVectorTableName(dimension);
|
|
70
|
+
if (ctx.db.normalizeId(tableName, id)) {
|
|
71
|
+
return { tableName, dimension };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Unknown vector table id: ${id}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tables: {
|
|
78
|
+
[K in keyof typeof VectorDimensions &
|
|
79
|
+
number as `embeddings_${(typeof VectorDimensions)[K]}`]: Table<
|
|
80
|
+
(typeof VectorDimensions)[K]
|
|
81
|
+
>;
|
|
82
|
+
} = Object.fromEntries(
|
|
83
|
+
VectorDimensions.map((dimensions) => [
|
|
84
|
+
`embeddings_${dimensions}`,
|
|
85
|
+
table(dimensions),
|
|
86
|
+
])
|
|
87
|
+
) as Record<
|
|
88
|
+
`embeddings_${(typeof VectorDimensions)[number]}`,
|
|
89
|
+
Table<(typeof VectorDimensions)[number]>
|
|
90
|
+
>;
|
|
91
|
+
|
|
92
|
+
export default tables;
|
package/src/mapping.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssistantContent,
|
|
3
|
+
CoreAssistantMessage,
|
|
4
|
+
CoreMessage,
|
|
5
|
+
CoreToolMessage,
|
|
6
|
+
DataContent,
|
|
7
|
+
GenerateTextResult,
|
|
8
|
+
StepResult,
|
|
9
|
+
ToolContent,
|
|
10
|
+
ToolSet,
|
|
11
|
+
UserContent,
|
|
12
|
+
} from "ai";
|
|
13
|
+
import { MessageWithFileAndId, Step } from "./validators";
|
|
14
|
+
|
|
15
|
+
export type SerializeUrlsAndUint8Arrays<T> = T extends URL
|
|
16
|
+
? string
|
|
17
|
+
: T extends Uint8Array | ArrayBufferLike
|
|
18
|
+
? ArrayBuffer
|
|
19
|
+
: T extends Array<infer Inner>
|
|
20
|
+
? Array<SerializeUrlsAndUint8Arrays<Inner>>
|
|
21
|
+
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
T extends Record<string, any>
|
|
23
|
+
? { [K in keyof T]: SerializeUrlsAndUint8Arrays<T[K]> }
|
|
24
|
+
: T;
|
|
25
|
+
|
|
26
|
+
export type Content = UserContent | AssistantContent | ToolContent;
|
|
27
|
+
export type SerializedContent = SerializeUrlsAndUint8Arrays<Content>;
|
|
28
|
+
|
|
29
|
+
export type SerializedMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
|
|
30
|
+
|
|
31
|
+
export function serializeMessage(
|
|
32
|
+
messageWithId: CoreMessage & { id?: string }
|
|
33
|
+
): SerializedMessage {
|
|
34
|
+
const { id: _, ...message } = messageWithId;
|
|
35
|
+
const content = message.content;
|
|
36
|
+
return {
|
|
37
|
+
...message,
|
|
38
|
+
content: serializeContent(content),
|
|
39
|
+
} as SerializedMessage;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function serializeMessageWithId(
|
|
43
|
+
messageWithId: CoreMessage & { id?: string }
|
|
44
|
+
): { message: SerializedMessage; id: string | undefined } {
|
|
45
|
+
return { message: serializeMessage(messageWithId), id: messageWithId.id };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function serializeContent(content: Content): SerializedContent {
|
|
49
|
+
if (typeof content === "string") {
|
|
50
|
+
return content;
|
|
51
|
+
}
|
|
52
|
+
const serialized = content.map((part) => {
|
|
53
|
+
switch (part.type) {
|
|
54
|
+
case "image":
|
|
55
|
+
return { ...part, image: serializeDataOrUrl(part.image) };
|
|
56
|
+
case "file":
|
|
57
|
+
return { ...part, file: serializeDataOrUrl(part.data) };
|
|
58
|
+
default:
|
|
59
|
+
return part;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return serialized as SerializedContent;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function serializeResponse<TOOLS extends ToolSet, OUTPUT>(
|
|
66
|
+
response: GenerateTextResult<TOOLS, OUTPUT>["response"]
|
|
67
|
+
): { message: SerializedMessage; id?: string }[] {
|
|
68
|
+
const { id, timestamp, modelId, headers, messages, body } = response;
|
|
69
|
+
// TODO: what to do about all the rest?
|
|
70
|
+
// Store body?
|
|
71
|
+
return messages.map((m) => serializeMessageWithId(m));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function serializeStep<TOOLS extends ToolSet>(
|
|
75
|
+
step: StepResult<TOOLS>
|
|
76
|
+
): Step {
|
|
77
|
+
const content = step.response?.messages.map((message) => {
|
|
78
|
+
return serializeMessageWithId(message);
|
|
79
|
+
});
|
|
80
|
+
const timestamp = step.response?.timestamp.getTime();
|
|
81
|
+
const response = {
|
|
82
|
+
...step.response,
|
|
83
|
+
messages: content,
|
|
84
|
+
timestamp,
|
|
85
|
+
headers: {},
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
...step,
|
|
89
|
+
response,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
94
|
+
step: StepResult<TOOLS>
|
|
95
|
+
): MessageWithFileAndId[] {
|
|
96
|
+
const messages: MessageWithFileAndId[] = [];
|
|
97
|
+
if (step.text) {
|
|
98
|
+
messages.push(serializeMessageWithId(step.response.messages.at(-1)!));
|
|
99
|
+
}
|
|
100
|
+
// TODO: get ID from regular messages
|
|
101
|
+
if (step.toolCalls || step.toolResults) {
|
|
102
|
+
messages.push(
|
|
103
|
+
...step.response.messages
|
|
104
|
+
.slice(
|
|
105
|
+
0,
|
|
106
|
+
-((step.toolCalls?.length ?? 0) + (step.toolResults?.length ?? 0))
|
|
107
|
+
)
|
|
108
|
+
.map((m) => serializeMessageWithId(m))
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return messages;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function deserializeContent(content: SerializedContent): Content {
|
|
115
|
+
if (typeof content === "string") {
|
|
116
|
+
return content;
|
|
117
|
+
}
|
|
118
|
+
return content.map((part) => {
|
|
119
|
+
switch (part.type) {
|
|
120
|
+
case "image":
|
|
121
|
+
return { ...part, file: deserializeUrl(part.image) };
|
|
122
|
+
case "file":
|
|
123
|
+
return { ...part, file: deserializeUrl(part.data) };
|
|
124
|
+
default:
|
|
125
|
+
return part;
|
|
126
|
+
}
|
|
127
|
+
}) as Content;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// TODO: store in file storage if it's big
|
|
131
|
+
function serializeDataOrUrl(
|
|
132
|
+
dataOrUrl: DataContent | URL
|
|
133
|
+
): ArrayBuffer | string {
|
|
134
|
+
if (typeof dataOrUrl === "string") {
|
|
135
|
+
return dataOrUrl;
|
|
136
|
+
}
|
|
137
|
+
if (dataOrUrl instanceof ArrayBuffer) {
|
|
138
|
+
return dataOrUrl; // Already an ArrayBuffer
|
|
139
|
+
}
|
|
140
|
+
if (dataOrUrl instanceof URL) {
|
|
141
|
+
return dataOrUrl.toString();
|
|
142
|
+
}
|
|
143
|
+
return dataOrUrl.buffer.slice(
|
|
144
|
+
dataOrUrl.byteOffset,
|
|
145
|
+
dataOrUrl.byteOffset + dataOrUrl.byteLength
|
|
146
|
+
) as ArrayBuffer;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function deserializeUrl(urlOrString: string | ArrayBuffer): URL | DataContent {
|
|
150
|
+
if (typeof urlOrString === "string") {
|
|
151
|
+
if (
|
|
152
|
+
urlOrString.startsWith("http://") ||
|
|
153
|
+
urlOrString.startsWith("https://")
|
|
154
|
+
) {
|
|
155
|
+
return new URL(urlOrString);
|
|
156
|
+
}
|
|
157
|
+
return urlOrString;
|
|
158
|
+
}
|
|
159
|
+
return urlOrString;
|
|
160
|
+
}
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CoreMessage } from "ai";
|
|
2
|
+
import { Message } from "./validators";
|
|
3
|
+
|
|
4
|
+
export function isTool(message: Message | CoreMessage) {
|
|
5
|
+
return (
|
|
6
|
+
message.role === "tool" ||
|
|
7
|
+
(message.role === "assistant" &&
|
|
8
|
+
Array.isArray(message.content) &&
|
|
9
|
+
message.content.some((c) => c.type === "tool-call"))
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function extractText(message: Message | CoreMessage) {
|
|
14
|
+
switch (message.role) {
|
|
15
|
+
case "user":
|
|
16
|
+
if (typeof message.content === "string") {
|
|
17
|
+
return message.content;
|
|
18
|
+
}
|
|
19
|
+
return message.content
|
|
20
|
+
.filter((c) => c.type === "text")
|
|
21
|
+
.map((c) => c.text)
|
|
22
|
+
.join("");
|
|
23
|
+
case "assistant":
|
|
24
|
+
if (typeof message.content === "string") {
|
|
25
|
+
return message.content;
|
|
26
|
+
}
|
|
27
|
+
return message.content
|
|
28
|
+
.filter((c) => c.type === "text")
|
|
29
|
+
.map((c) => c.text)
|
|
30
|
+
.join("");
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_MESSAGE_RANGE = { before: 2, after: 1 };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Infer } from "convex/values";
|
|
2
|
+
import { expectTypeOf, test } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
vAssistantContent,
|
|
5
|
+
vAssistantMessage,
|
|
6
|
+
vFilePart,
|
|
7
|
+
vImagePart,
|
|
8
|
+
vMessage,
|
|
9
|
+
vReasoningPart,
|
|
10
|
+
vRedactedReasoningPart,
|
|
11
|
+
vSystemMessage,
|
|
12
|
+
vTextPart,
|
|
13
|
+
vToolCallPart,
|
|
14
|
+
vToolContent,
|
|
15
|
+
vToolMessage,
|
|
16
|
+
vUserMessage,
|
|
17
|
+
} from "./validators";
|
|
18
|
+
import { vUserContent } from "./validators";
|
|
19
|
+
import {
|
|
20
|
+
AssistantContent,
|
|
21
|
+
CoreAssistantMessage,
|
|
22
|
+
CoreMessage,
|
|
23
|
+
CoreSystemMessage,
|
|
24
|
+
CoreToolMessage,
|
|
25
|
+
CoreUserMessage,
|
|
26
|
+
FilePart,
|
|
27
|
+
ImagePart,
|
|
28
|
+
TextPart,
|
|
29
|
+
ToolCallPart,
|
|
30
|
+
ToolContent,
|
|
31
|
+
UserContent,
|
|
32
|
+
} from "ai";
|
|
33
|
+
import { SerializeUrlsAndUint8Arrays } from "./mapping";
|
|
34
|
+
|
|
35
|
+
// type assertion
|
|
36
|
+
type OurUserContent = SerializeUrlsAndUint8Arrays<UserContent>;
|
|
37
|
+
expectTypeOf<OurUserContent>().toExtend<Infer<typeof vUserContent>>();
|
|
38
|
+
expectTypeOf<Infer<typeof vUserContent>>().toExtend<OurUserContent>();
|
|
39
|
+
|
|
40
|
+
type OurAssistantContent = SerializeUrlsAndUint8Arrays<AssistantContent>;
|
|
41
|
+
expectTypeOf<OurAssistantContent>().toExtend<Infer<typeof vAssistantContent>>();
|
|
42
|
+
expectTypeOf<Infer<typeof vAssistantContent>>().toExtend<OurAssistantContent>();
|
|
43
|
+
|
|
44
|
+
type OurToolContent = SerializeUrlsAndUint8Arrays<ToolContent>;
|
|
45
|
+
expectTypeOf<OurToolContent>().toExtend<Infer<typeof vToolContent>>();
|
|
46
|
+
expectTypeOf<Infer<typeof vToolContent>>().toExtend<OurToolContent>();
|
|
47
|
+
|
|
48
|
+
expectTypeOf<Infer<typeof vToolCallPart>>().toExtend<ToolCallPart>();
|
|
49
|
+
expectTypeOf<ToolCallPart>().toExtend<Infer<typeof vToolCallPart>>();
|
|
50
|
+
|
|
51
|
+
type OurTextPart = SerializeUrlsAndUint8Arrays<TextPart>;
|
|
52
|
+
expectTypeOf<OurTextPart>().toExtend<Infer<typeof vTextPart>>();
|
|
53
|
+
expectTypeOf<Infer<typeof vTextPart>>().toExtend<OurTextPart>();
|
|
54
|
+
|
|
55
|
+
type OurImagePart = SerializeUrlsAndUint8Arrays<ImagePart>;
|
|
56
|
+
expectTypeOf<OurImagePart>().toExtend<Infer<typeof vImagePart>>();
|
|
57
|
+
expectTypeOf<Infer<typeof vImagePart>>().toExtend<OurImagePart>();
|
|
58
|
+
|
|
59
|
+
type OurFilePart = SerializeUrlsAndUint8Arrays<FilePart>;
|
|
60
|
+
expectTypeOf<OurFilePart>().toExtend<Infer<typeof vFilePart>>();
|
|
61
|
+
expectTypeOf<Infer<typeof vFilePart>>().toExtend<OurFilePart>();
|
|
62
|
+
|
|
63
|
+
// narrow to the type
|
|
64
|
+
type ReasoningPart = AssistantContent[number] & { type: "reasoning" } & object;
|
|
65
|
+
type OurReasoningPart = SerializeUrlsAndUint8Arrays<ReasoningPart>;
|
|
66
|
+
expectTypeOf<OurReasoningPart>().toExtend<Infer<typeof vReasoningPart>>();
|
|
67
|
+
expectTypeOf<Infer<typeof vReasoningPart>>().toExtend<OurReasoningPart>();
|
|
68
|
+
|
|
69
|
+
// narrow to the type
|
|
70
|
+
type RedactedReasoningPart = AssistantContent[number] & {
|
|
71
|
+
type: "redacted-reasoning";
|
|
72
|
+
} & object;
|
|
73
|
+
type OurRedactedReasoningPart =
|
|
74
|
+
SerializeUrlsAndUint8Arrays<RedactedReasoningPart>;
|
|
75
|
+
expectTypeOf<OurRedactedReasoningPart>().toExtend<
|
|
76
|
+
Infer<typeof vRedactedReasoningPart>
|
|
77
|
+
>();
|
|
78
|
+
expectTypeOf<
|
|
79
|
+
Infer<typeof vRedactedReasoningPart>
|
|
80
|
+
>().toExtend<OurRedactedReasoningPart>();
|
|
81
|
+
|
|
82
|
+
// test("noop", () => {
|
|
83
|
+
type OurUserMessage = SerializeUrlsAndUint8Arrays<CoreUserMessage>;
|
|
84
|
+
expectTypeOf<OurUserMessage>().toExtend<Infer<typeof vUserMessage>>();
|
|
85
|
+
expectTypeOf<Infer<typeof vUserMessage>>().toExtend<OurUserMessage>();
|
|
86
|
+
|
|
87
|
+
type OurAssistantMessage = SerializeUrlsAndUint8Arrays<CoreAssistantMessage>;
|
|
88
|
+
expectTypeOf<OurAssistantMessage>().toExtend<Infer<typeof vAssistantMessage>>();
|
|
89
|
+
expectTypeOf<Infer<typeof vAssistantMessage>>().toExtend<OurAssistantMessage>();
|
|
90
|
+
|
|
91
|
+
expectTypeOf<Infer<typeof vToolMessage>>().toExtend<CoreToolMessage>();
|
|
92
|
+
expectTypeOf<CoreToolMessage>().toExtend<Infer<typeof vToolMessage>>();
|
|
93
|
+
|
|
94
|
+
expectTypeOf<Infer<typeof vSystemMessage>>().toExtend<CoreSystemMessage>();
|
|
95
|
+
expectTypeOf<CoreSystemMessage>().toExtend<Infer<typeof vSystemMessage>>();
|
|
96
|
+
|
|
97
|
+
type OurMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
|
|
98
|
+
expectTypeOf<OurMessage>().toExtend<Infer<typeof vMessage>>();
|
|
99
|
+
expectTypeOf<Infer<typeof vMessage>>().toExtend<OurMessage>();
|
|
100
|
+
|
|
101
|
+
test("noop", () => { });
|