@firtoz/chat-agent 1.0.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/README.md +443 -0
- package/package.json +74 -0
- package/src/chat-agent-base.ts +1128 -0
- package/src/chat-agent-drizzle.ts +227 -0
- package/src/chat-agent-sql.ts +199 -0
- package/src/chat-messages.ts +472 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema.ts +47 -0
- package/src/index.ts +99 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Tool Definitions (for sending to OpenRouter)
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* JSON Schema for tool parameters (subset of JSON Schema 7)
|
|
9
|
+
*/
|
|
10
|
+
export const JSONSchemaSchema = z.record(z.string(), z.unknown());
|
|
11
|
+
|
|
12
|
+
export type JSONSchema = z.infer<typeof JSONSchemaSchema>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tool definition following OpenAI/OpenRouter format
|
|
16
|
+
* The schema is for wire format (no execute function)
|
|
17
|
+
*/
|
|
18
|
+
export const ToolDefinitionSchema = z.object({
|
|
19
|
+
type: z.literal("function"),
|
|
20
|
+
function: z.object({
|
|
21
|
+
name: z.string(),
|
|
22
|
+
description: z.string().optional(),
|
|
23
|
+
parameters: JSONSchemaSchema.optional(),
|
|
24
|
+
strict: z.boolean().optional(),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute function signature for server-side tools
|
|
30
|
+
* Takes parsed arguments, returns JSON-serializable result
|
|
31
|
+
*/
|
|
32
|
+
// biome-ignore lint/suspicious/noExplicitAny: Tool execute functions need flexible typing
|
|
33
|
+
export type ToolExecuteFunction = (args: any) => unknown | Promise<unknown>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool definition with optional execute function for server-side execution
|
|
37
|
+
* - If `execute` is provided: server runs it automatically and continues
|
|
38
|
+
* - If `execute` is omitted: tool call is sent to client for execution
|
|
39
|
+
*/
|
|
40
|
+
export type ToolDefinition = z.infer<typeof ToolDefinitionSchema> & {
|
|
41
|
+
/** Optional server-side execute function. If omitted, tool call goes to client. */
|
|
42
|
+
execute?: ToolExecuteFunction;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Tool Calls (from AI responses)
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A tool call from the AI (complete, after streaming)
|
|
51
|
+
*/
|
|
52
|
+
export const ToolCallSchema = z.object({
|
|
53
|
+
id: z.string(),
|
|
54
|
+
type: z.literal("function"),
|
|
55
|
+
function: z.object({
|
|
56
|
+
name: z.string(),
|
|
57
|
+
arguments: z.string(), // JSON string
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export type ToolCall = z.infer<typeof ToolCallSchema>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Tool call delta during streaming
|
|
65
|
+
*/
|
|
66
|
+
export const ToolCallDeltaSchema = z.object({
|
|
67
|
+
index: z.number(),
|
|
68
|
+
id: z.string().optional(),
|
|
69
|
+
type: z.literal("function").optional(),
|
|
70
|
+
function: z
|
|
71
|
+
.object({
|
|
72
|
+
name: z.string().optional(),
|
|
73
|
+
arguments: z.string().optional(),
|
|
74
|
+
})
|
|
75
|
+
.optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export type ToolCallDelta = z.infer<typeof ToolCallDeltaSchema>;
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Tool Results (from client execution)
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Tool result from client-side execution
|
|
86
|
+
*/
|
|
87
|
+
export const ToolResultSchema = z.object({
|
|
88
|
+
toolCallId: z.string(),
|
|
89
|
+
output: z.unknown(), // Can be any JSON-serializable value
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export type ToolResult = z.infer<typeof ToolResultSchema>;
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Chat Message Schema (supports text + tool calls)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* User message
|
|
100
|
+
*/
|
|
101
|
+
export const UserMessageSchema = z.object({
|
|
102
|
+
id: z.string(),
|
|
103
|
+
role: z.literal("user"),
|
|
104
|
+
content: z.string(),
|
|
105
|
+
createdAt: z.number(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type UserMessage = z.infer<typeof UserMessageSchema>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Assistant message - can have content, tool calls, or both
|
|
112
|
+
*/
|
|
113
|
+
export const AssistantMessageSchema = z.object({
|
|
114
|
+
id: z.string(),
|
|
115
|
+
role: z.literal("assistant"),
|
|
116
|
+
content: z.string().nullable(), // null when only tool calls
|
|
117
|
+
toolCalls: z.array(ToolCallSchema).optional(),
|
|
118
|
+
createdAt: z.number(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export type AssistantMessage = z.infer<typeof AssistantMessageSchema>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Tool response message (sent back to AI after tool execution)
|
|
125
|
+
*/
|
|
126
|
+
export const ToolMessageSchema = z.object({
|
|
127
|
+
id: z.string(),
|
|
128
|
+
role: z.literal("tool"),
|
|
129
|
+
toolCallId: z.string(),
|
|
130
|
+
content: z.string(), // JSON stringified result
|
|
131
|
+
createdAt: z.number(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export type ToolMessage = z.infer<typeof ToolMessageSchema>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Union of all chat message types
|
|
138
|
+
*/
|
|
139
|
+
export const ChatMessageSchema = z.discriminatedUnion("role", [
|
|
140
|
+
UserMessageSchema,
|
|
141
|
+
AssistantMessageSchema,
|
|
142
|
+
ToolMessageSchema,
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
export type ChatMessage = z.infer<typeof ChatMessageSchema>;
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Token Usage Schema
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
export const TokenUsageSchema = z.object({
|
|
152
|
+
prompt_tokens: z.number(),
|
|
153
|
+
completion_tokens: z.number(),
|
|
154
|
+
total_tokens: z.number(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export type TokenUsage = z.infer<typeof TokenUsageSchema>;
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Client → Server Messages (Discriminated Union)
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
export const ClientMessageSchema = z.discriminatedUnion("type", [
|
|
164
|
+
z.object({
|
|
165
|
+
type: z.literal("sendMessage"),
|
|
166
|
+
content: z.string(),
|
|
167
|
+
}),
|
|
168
|
+
z.object({
|
|
169
|
+
type: z.literal("clearHistory"),
|
|
170
|
+
}),
|
|
171
|
+
z.object({
|
|
172
|
+
type: z.literal("getHistory"),
|
|
173
|
+
}),
|
|
174
|
+
z.object({
|
|
175
|
+
type: z.literal("resumeStream"),
|
|
176
|
+
streamId: z.string(),
|
|
177
|
+
}),
|
|
178
|
+
z.object({
|
|
179
|
+
type: z.literal("cancelRequest"),
|
|
180
|
+
id: z.string(),
|
|
181
|
+
}),
|
|
182
|
+
// Tool result from client-side tool execution
|
|
183
|
+
z.object({
|
|
184
|
+
type: z.literal("toolResult"),
|
|
185
|
+
toolCallId: z.string(),
|
|
186
|
+
toolName: z.string(),
|
|
187
|
+
output: z.unknown(),
|
|
188
|
+
// If true, server should continue the conversation after tool result
|
|
189
|
+
autoContinue: z.boolean().optional(),
|
|
190
|
+
}),
|
|
191
|
+
// Register client-defined tools at runtime
|
|
192
|
+
z.object({
|
|
193
|
+
type: z.literal("registerTools"),
|
|
194
|
+
tools: z.array(
|
|
195
|
+
z.object({
|
|
196
|
+
name: z.string(),
|
|
197
|
+
description: z.string().optional(),
|
|
198
|
+
parameters: JSONSchemaSchema.optional(),
|
|
199
|
+
}),
|
|
200
|
+
),
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
export type ClientMessage = z.infer<typeof ClientMessageSchema>;
|
|
205
|
+
|
|
206
|
+
// Individual message types for convenience
|
|
207
|
+
export type SendMessagePayload = Extract<
|
|
208
|
+
ClientMessage,
|
|
209
|
+
{ type: "sendMessage" }
|
|
210
|
+
>;
|
|
211
|
+
export type ClearHistoryPayload = Extract<
|
|
212
|
+
ClientMessage,
|
|
213
|
+
{ type: "clearHistory" }
|
|
214
|
+
>;
|
|
215
|
+
export type GetHistoryPayload = Extract<ClientMessage, { type: "getHistory" }>;
|
|
216
|
+
export type ResumeStreamPayload = Extract<
|
|
217
|
+
ClientMessage,
|
|
218
|
+
{ type: "resumeStream" }
|
|
219
|
+
>;
|
|
220
|
+
export type CancelRequestPayload = Extract<
|
|
221
|
+
ClientMessage,
|
|
222
|
+
{ type: "cancelRequest" }
|
|
223
|
+
>;
|
|
224
|
+
export type ToolResultPayload = Extract<ClientMessage, { type: "toolResult" }>;
|
|
225
|
+
export type RegisterToolsPayload = Extract<
|
|
226
|
+
ClientMessage,
|
|
227
|
+
{ type: "registerTools" }
|
|
228
|
+
>;
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Server → Client Messages (Discriminated Union)
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
export const ServerMessageSchema = z.discriminatedUnion("type", [
|
|
235
|
+
// Full message history
|
|
236
|
+
z.object({
|
|
237
|
+
type: z.literal("history"),
|
|
238
|
+
messages: z.array(ChatMessageSchema),
|
|
239
|
+
}),
|
|
240
|
+
// Stream start
|
|
241
|
+
z.object({
|
|
242
|
+
type: z.literal("messageStart"),
|
|
243
|
+
id: z.string(),
|
|
244
|
+
streamId: z.string(),
|
|
245
|
+
}),
|
|
246
|
+
// Text content chunk
|
|
247
|
+
z.object({
|
|
248
|
+
type: z.literal("messageChunk"),
|
|
249
|
+
id: z.string(),
|
|
250
|
+
chunk: z.string(),
|
|
251
|
+
}),
|
|
252
|
+
// Tool call streaming delta
|
|
253
|
+
z.object({
|
|
254
|
+
type: z.literal("toolCallDelta"),
|
|
255
|
+
id: z.string(),
|
|
256
|
+
delta: ToolCallDeltaSchema,
|
|
257
|
+
}),
|
|
258
|
+
// Tool call complete (full tool call ready for execution)
|
|
259
|
+
z.object({
|
|
260
|
+
type: z.literal("toolCall"),
|
|
261
|
+
id: z.string(), // message id
|
|
262
|
+
toolCall: ToolCallSchema,
|
|
263
|
+
}),
|
|
264
|
+
// Stream end
|
|
265
|
+
z.object({
|
|
266
|
+
type: z.literal("messageEnd"),
|
|
267
|
+
id: z.string(),
|
|
268
|
+
// Final message state (with tool calls if any)
|
|
269
|
+
toolCalls: z.array(ToolCallSchema).optional(),
|
|
270
|
+
createdAt: z.number(),
|
|
271
|
+
usage: TokenUsageSchema.optional(),
|
|
272
|
+
}),
|
|
273
|
+
// Stream resumption
|
|
274
|
+
z.object({
|
|
275
|
+
type: z.literal("streamResume"),
|
|
276
|
+
streamId: z.string(),
|
|
277
|
+
chunks: z.array(z.string()),
|
|
278
|
+
done: z.boolean(),
|
|
279
|
+
}),
|
|
280
|
+
z.object({
|
|
281
|
+
type: z.literal("streamResuming"),
|
|
282
|
+
id: z.string(),
|
|
283
|
+
streamId: z.string(),
|
|
284
|
+
}),
|
|
285
|
+
// Message updated (e.g., tool result applied)
|
|
286
|
+
z.object({
|
|
287
|
+
type: z.literal("messageUpdated"),
|
|
288
|
+
message: ChatMessageSchema,
|
|
289
|
+
}),
|
|
290
|
+
// General error
|
|
291
|
+
z.object({
|
|
292
|
+
type: z.literal("error"),
|
|
293
|
+
message: z.string(),
|
|
294
|
+
}),
|
|
295
|
+
// Tool input error (arguments validation failed)
|
|
296
|
+
z.object({
|
|
297
|
+
type: z.literal("toolError"),
|
|
298
|
+
errorType: z.enum(["input", "output", "not_found"]),
|
|
299
|
+
toolCallId: z.string(),
|
|
300
|
+
toolName: z.string(),
|
|
301
|
+
message: z.string(),
|
|
302
|
+
}),
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
export type ServerMessage = z.infer<typeof ServerMessageSchema>;
|
|
306
|
+
|
|
307
|
+
// Individual message types for convenience
|
|
308
|
+
export type HistoryMessage = Extract<ServerMessage, { type: "history" }>;
|
|
309
|
+
export type MessageStartMessage = Extract<
|
|
310
|
+
ServerMessage,
|
|
311
|
+
{ type: "messageStart" }
|
|
312
|
+
>;
|
|
313
|
+
export type MessageChunkMessage = Extract<
|
|
314
|
+
ServerMessage,
|
|
315
|
+
{ type: "messageChunk" }
|
|
316
|
+
>;
|
|
317
|
+
export type ToolCallDeltaMessage = Extract<
|
|
318
|
+
ServerMessage,
|
|
319
|
+
{ type: "toolCallDelta" }
|
|
320
|
+
>;
|
|
321
|
+
export type ToolCallMessage = Extract<ServerMessage, { type: "toolCall" }>;
|
|
322
|
+
export type MessageEndMessage = Extract<ServerMessage, { type: "messageEnd" }>;
|
|
323
|
+
export type StreamResumeMessage = Extract<
|
|
324
|
+
ServerMessage,
|
|
325
|
+
{ type: "streamResume" }
|
|
326
|
+
>;
|
|
327
|
+
export type StreamResumingMessage = Extract<
|
|
328
|
+
ServerMessage,
|
|
329
|
+
{ type: "streamResuming" }
|
|
330
|
+
>;
|
|
331
|
+
export type MessageUpdatedMessage = Extract<
|
|
332
|
+
ServerMessage,
|
|
333
|
+
{ type: "messageUpdated" }
|
|
334
|
+
>;
|
|
335
|
+
export type ErrorMessage = Extract<ServerMessage, { type: "error" }>;
|
|
336
|
+
export type ToolErrorMessage = Extract<ServerMessage, { type: "toolError" }>;
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// Parsing Helpers
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parse and validate a client message from JSON string
|
|
344
|
+
* @throws ZodError if validation fails
|
|
345
|
+
*/
|
|
346
|
+
export function parseClientMessage(json: string): ClientMessage {
|
|
347
|
+
const data = JSON.parse(json);
|
|
348
|
+
return ClientMessageSchema.parse(data);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Safely parse a client message, returning null on failure
|
|
353
|
+
*/
|
|
354
|
+
export function safeParseClientMessage(json: string): ClientMessage | null {
|
|
355
|
+
try {
|
|
356
|
+
const data = JSON.parse(json);
|
|
357
|
+
const result = ClientMessageSchema.safeParse(data);
|
|
358
|
+
return result.success ? result.data : null;
|
|
359
|
+
} catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Parse and validate a server message from JSON string
|
|
366
|
+
* @throws ZodError if validation fails
|
|
367
|
+
*/
|
|
368
|
+
export function parseServerMessage(json: string): ServerMessage {
|
|
369
|
+
const data = JSON.parse(json);
|
|
370
|
+
return ServerMessageSchema.parse(data);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Safely parse a server message, returning null on failure
|
|
375
|
+
*/
|
|
376
|
+
export function safeParseServerMessage(json: string): ServerMessage | null {
|
|
377
|
+
try {
|
|
378
|
+
const data = JSON.parse(json);
|
|
379
|
+
const result = ServerMessageSchema.safeParse(data);
|
|
380
|
+
return result.success ? result.data : null;
|
|
381
|
+
} catch {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// Type Guards
|
|
388
|
+
// ============================================================================
|
|
389
|
+
|
|
390
|
+
export function isClientMessage(data: unknown): data is ClientMessage {
|
|
391
|
+
return ClientMessageSchema.safeParse(data).success;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function isServerMessage(data: unknown): data is ServerMessage {
|
|
395
|
+
return ServerMessageSchema.safeParse(data).success;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function isUserMessage(msg: ChatMessage): msg is UserMessage {
|
|
399
|
+
return msg.role === "user";
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function isAssistantMessage(msg: ChatMessage): msg is AssistantMessage {
|
|
403
|
+
return msg.role === "assistant";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function isToolMessage(msg: ChatMessage): msg is ToolMessage {
|
|
407
|
+
return msg.role === "tool";
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function hasToolCalls(msg: AssistantMessage): boolean {
|
|
411
|
+
return !!msg.toolCalls && msg.toolCalls.length > 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// Helper Functions
|
|
416
|
+
// ============================================================================
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Parse tool call arguments from JSON string
|
|
420
|
+
*/
|
|
421
|
+
export function parseToolArguments<T = unknown>(toolCall: ToolCall): T {
|
|
422
|
+
return JSON.parse(toolCall.function.arguments) as T;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Create a tool definition helper
|
|
427
|
+
*
|
|
428
|
+
* @example Server-side tool (executes automatically on server)
|
|
429
|
+
* ```ts
|
|
430
|
+
* defineTool({
|
|
431
|
+
* name: "get_weather",
|
|
432
|
+
* description: "Get current weather",
|
|
433
|
+
* parameters: { type: "object", properties: { location: { type: "string" } } },
|
|
434
|
+
* execute: async (args) => {
|
|
435
|
+
* const weather = await fetchWeather(args.location);
|
|
436
|
+
* return { temp: weather.temp, conditions: weather.desc };
|
|
437
|
+
* }
|
|
438
|
+
* })
|
|
439
|
+
* ```
|
|
440
|
+
*
|
|
441
|
+
* @example Client-side tool (sent to client for execution)
|
|
442
|
+
* ```ts
|
|
443
|
+
* defineTool({
|
|
444
|
+
* name: "get_user_location",
|
|
445
|
+
* description: "Get user's current location",
|
|
446
|
+
* parameters: { type: "object", properties: {} },
|
|
447
|
+
* // No execute function - client handles this
|
|
448
|
+
* })
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export function defineTool(config: {
|
|
452
|
+
name: string;
|
|
453
|
+
description?: string;
|
|
454
|
+
parameters?: JSONSchema;
|
|
455
|
+
strict?: boolean;
|
|
456
|
+
/** Server-side execute function. If omitted, tool call goes to client. */
|
|
457
|
+
execute?: ToolExecuteFunction;
|
|
458
|
+
}): ToolDefinition {
|
|
459
|
+
const tool: ToolDefinition = {
|
|
460
|
+
type: "function",
|
|
461
|
+
function: {
|
|
462
|
+
name: config.name,
|
|
463
|
+
description: config.description,
|
|
464
|
+
parameters: config.parameters,
|
|
465
|
+
strict: config.strict,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
if (config.execute) {
|
|
469
|
+
tool.execute = config.execute;
|
|
470
|
+
}
|
|
471
|
+
return tool;
|
|
472
|
+
}
|
package/src/db/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DrizzleSqliteDODatabase,
|
|
3
|
+
drizzle,
|
|
4
|
+
} from "drizzle-orm/durable-sqlite";
|
|
5
|
+
import * as schema from "./schema";
|
|
6
|
+
|
|
7
|
+
export * from "./schema";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a Drizzle instance for Durable Object SQLite storage
|
|
11
|
+
* Uses the native drizzle-orm/durable-sqlite driver
|
|
12
|
+
*
|
|
13
|
+
* @see https://orm.drizzle.team/docs/connect-cloudflare-do
|
|
14
|
+
*/
|
|
15
|
+
export function createDb(
|
|
16
|
+
storage: DurableObjectStorage,
|
|
17
|
+
): DrizzleSqliteDODatabase<typeof schema> {
|
|
18
|
+
return drizzle(storage, { schema, logger: false });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Database = DrizzleSqliteDODatabase<typeof schema>;
|
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chat messages table
|
|
5
|
+
* Stores the full conversation history for each agent session
|
|
6
|
+
* Messages are stored as JSON to support complex structures (tool calls, etc.)
|
|
7
|
+
*/
|
|
8
|
+
export const messagesTable = sqliteTable("messages", {
|
|
9
|
+
id: text("id").primaryKey(),
|
|
10
|
+
role: text("role", { enum: ["user", "assistant", "tool"] }).notNull(),
|
|
11
|
+
// JSON-serialized message data (content, toolCalls, toolCallId, etc.)
|
|
12
|
+
messageJson: text("message_json").notNull(),
|
|
13
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Stream chunks table for resumable streaming
|
|
18
|
+
* Buffers streamed content so clients can resume mid-stream
|
|
19
|
+
*/
|
|
20
|
+
export const streamChunksTable = sqliteTable("stream_chunks", {
|
|
21
|
+
id: text("id").primaryKey(),
|
|
22
|
+
streamId: text("stream_id").notNull(),
|
|
23
|
+
content: text("content").notNull(),
|
|
24
|
+
chunkIndex: integer("chunk_index").notNull(),
|
|
25
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Stream metadata table for tracking active/completed streams
|
|
30
|
+
*/
|
|
31
|
+
export const streamMetadataTable = sqliteTable("stream_metadata", {
|
|
32
|
+
id: text("id").primaryKey(),
|
|
33
|
+
messageId: text("message_id").notNull(), // The assistant message being streamed
|
|
34
|
+
status: text("status", {
|
|
35
|
+
enum: ["streaming", "completed", "error"],
|
|
36
|
+
}).notNull(),
|
|
37
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
38
|
+
completedAt: integer("completed_at", { mode: "timestamp_ms" }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Type exports for use in the agent
|
|
42
|
+
export type Message = typeof messagesTable.$inferSelect;
|
|
43
|
+
export type NewMessage = typeof messagesTable.$inferInsert;
|
|
44
|
+
export type StreamChunk = typeof streamChunksTable.$inferSelect;
|
|
45
|
+
export type NewStreamChunk = typeof streamChunksTable.$inferInsert;
|
|
46
|
+
export type StreamMeta = typeof streamMetadataTable.$inferSelect;
|
|
47
|
+
export type NewStreamMeta = typeof streamMetadataTable.$inferInsert;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @firtoz/chat-agent - ChatAgent for Cloudflare Durable Objects with OpenRouter
|
|
3
|
+
*
|
|
4
|
+
* A simplified alternative to @cloudflare/ai-chat's AIChatAgent that uses OpenRouter
|
|
5
|
+
* directly instead of Vercel AI SDK.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { ChatAgent, defineTool, type ToolDefinition } from "@firtoz/chat-agent";
|
|
10
|
+
*
|
|
11
|
+
* class MyAgent extends ChatAgent {
|
|
12
|
+
* protected override getSystemPrompt(): string {
|
|
13
|
+
* return "You are a helpful assistant.";
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* protected override getModel(): string {
|
|
17
|
+
* return "anthropic/claude-sonnet-4.5";
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* protected override getTools(): ToolDefinition[] {
|
|
21
|
+
* return [
|
|
22
|
+
* defineTool({
|
|
23
|
+
* name: "get_time",
|
|
24
|
+
* description: "Get current time",
|
|
25
|
+
* parameters: { type: "object", properties: {} },
|
|
26
|
+
* execute: async () => ({ time: new Date().toISOString() })
|
|
27
|
+
* })
|
|
28
|
+
* ];
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Export the abstract base class
|
|
35
|
+
export { ChatAgentBase } from "./chat-agent-base";
|
|
36
|
+
|
|
37
|
+
// Export concrete implementations
|
|
38
|
+
export { DrizzleChatAgent } from "./chat-agent-drizzle";
|
|
39
|
+
export { SqlChatAgent } from "./chat-agent-sql";
|
|
40
|
+
|
|
41
|
+
// Alias DrizzleChatAgent as ChatAgent for convenience (Drizzle is recommended)
|
|
42
|
+
export { DrizzleChatAgent as ChatAgent } from "./chat-agent-drizzle";
|
|
43
|
+
|
|
44
|
+
// Re-export all types and utilities from chat-messages
|
|
45
|
+
export type {
|
|
46
|
+
// Message types
|
|
47
|
+
AssistantMessage,
|
|
48
|
+
ChatMessage,
|
|
49
|
+
UserMessage,
|
|
50
|
+
ToolMessage,
|
|
51
|
+
// Tool types
|
|
52
|
+
ToolCall,
|
|
53
|
+
ToolCallDelta,
|
|
54
|
+
ToolDefinition,
|
|
55
|
+
ToolResult,
|
|
56
|
+
JSONSchema,
|
|
57
|
+
// Client/Server message types
|
|
58
|
+
ClientMessage,
|
|
59
|
+
ServerMessage,
|
|
60
|
+
SendMessagePayload,
|
|
61
|
+
ClearHistoryPayload,
|
|
62
|
+
GetHistoryPayload,
|
|
63
|
+
ResumeStreamPayload,
|
|
64
|
+
CancelRequestPayload,
|
|
65
|
+
ToolResultPayload,
|
|
66
|
+
RegisterToolsPayload,
|
|
67
|
+
HistoryMessage,
|
|
68
|
+
MessageStartMessage,
|
|
69
|
+
MessageChunkMessage,
|
|
70
|
+
ToolCallDeltaMessage,
|
|
71
|
+
ToolCallMessage,
|
|
72
|
+
MessageEndMessage,
|
|
73
|
+
StreamResumeMessage,
|
|
74
|
+
StreamResumingMessage,
|
|
75
|
+
MessageUpdatedMessage,
|
|
76
|
+
ErrorMessage,
|
|
77
|
+
ToolErrorMessage,
|
|
78
|
+
// Usage types
|
|
79
|
+
TokenUsage,
|
|
80
|
+
} from "./chat-messages";
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
// Tool definition helper
|
|
84
|
+
defineTool,
|
|
85
|
+
// Parsing helpers
|
|
86
|
+
parseClientMessage,
|
|
87
|
+
safeParseClientMessage,
|
|
88
|
+
parseServerMessage,
|
|
89
|
+
safeParseServerMessage,
|
|
90
|
+
// Type guards
|
|
91
|
+
isClientMessage,
|
|
92
|
+
isServerMessage,
|
|
93
|
+
isUserMessage,
|
|
94
|
+
isAssistantMessage,
|
|
95
|
+
isToolMessage,
|
|
96
|
+
hasToolCalls,
|
|
97
|
+
// Helper functions
|
|
98
|
+
parseToolArguments,
|
|
99
|
+
} from "./chat-messages";
|