@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 ADDED
@@ -0,0 +1,443 @@
1
+ # @firtoz/chat-agent
2
+
3
+ DB-agnostic ChatAgent for Cloudflare Durable Objects with OpenRouter - a simplified alternative to `@cloudflare/ai-chat`.
4
+
5
+ ## Overview
6
+
7
+ Three classes for different database preferences:
8
+
9
+ - **`ChatAgentBase`** - Abstract base class with all chat logic
10
+ - **`DrizzleChatAgent`** - Type-safe implementation using Drizzle ORM (recommended)
11
+ - **`SqlChatAgent`** - Raw SQL implementation using `this.sql` template tags
12
+
13
+ All implementations share the same API and features:
14
+ - OpenRouter API integration (simpler than AI SDK)
15
+ - Resumable streaming with chunk buffering
16
+ - Server-side and client-side tool execution
17
+ - Cloudflare AI Gateway support
18
+ - Message persistence in SQLite
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ bun add @firtoz/chat-agent @openrouter/sdk agents
24
+
25
+ # For Drizzle implementation:
26
+ bun add drizzle-orm
27
+ bun add -d drizzle-kit
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Using DrizzleChatAgent (Recommended)
33
+
34
+ ```typescript
35
+ import { DrizzleChatAgent, defineTool } from "@firtoz/chat-agent";
36
+ import type { AgentContext } from "agents";
37
+
38
+ interface Env {
39
+ OPENROUTER_API_KEY: string;
40
+ }
41
+
42
+ class MyAgent extends DrizzleChatAgent<Env> {
43
+ protected override getSystemPrompt(): string {
44
+ return "You are a helpful assistant.";
45
+ }
46
+
47
+ protected override getModel(): string {
48
+ return "anthropic/claude-sonnet-4.5";
49
+ }
50
+
51
+ protected override getTools() {
52
+ return [
53
+ defineTool({
54
+ name: "get_time",
55
+ description: "Get current time",
56
+ parameters: { type: "object", properties: {} },
57
+ execute: async () => ({ time: new Date().toISOString() })
58
+ })
59
+ ];
60
+ }
61
+ }
62
+
63
+ export { MyAgent };
64
+ ```
65
+
66
+ ### Using SqlChatAgent
67
+
68
+ ```typescript
69
+ import { SqlChatAgent } from "@firtoz/chat-agent";
70
+
71
+ class MyAgent extends SqlChatAgent<Env> {
72
+ // Same API as DrizzleChatAgent
73
+ protected override getSystemPrompt(): string {
74
+ return "You are a helpful assistant.";
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Setup Instructions
80
+
81
+ ### DrizzleChatAgent Setup
82
+
83
+ #### 1. Add Wrangler Rules
84
+
85
+ **IMPORTANT:** Add this to your `wrangler.jsonc` to import SQL migration files:
86
+
87
+ ```jsonc
88
+ {
89
+ /**
90
+ * Rules to import SQL migration files as text
91
+ * Required for Drizzle ORM migrations in Durable Objects
92
+ * @see https://orm.drizzle.team/docs/connect-cloudflare-do
93
+ */
94
+ "rules": [
95
+ {
96
+ "type": "Text",
97
+ "globs": ["**/*.sql"],
98
+ "fallthrough": true
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ #### 2. Create Drizzle Config
105
+
106
+ Create `drizzle.config.ts` in your package:
107
+
108
+ ```typescript
109
+ import { defineConfig } from "drizzle-kit";
110
+
111
+ export default defineConfig({
112
+ schema: "./node_modules/@firtoz/chat-agent/src/db/schema.ts",
113
+ out: "./drizzle",
114
+ dialect: "sqlite",
115
+ driver: "durable-sqlite",
116
+ });
117
+ ```
118
+
119
+ Or if you're in the chat-agent package itself:
120
+
121
+ ```typescript
122
+ import { defineConfig } from "drizzle-kit";
123
+
124
+ export default defineConfig({
125
+ schema: "./src/db/schema.ts",
126
+ out: "./drizzle",
127
+ dialect: "sqlite",
128
+ driver: "durable-sqlite",
129
+ });
130
+ ```
131
+
132
+ #### 3. Add Script to package.json
133
+
134
+ ```json
135
+ {
136
+ "scripts": {
137
+ "db:generate": "bunx drizzle-kit generate"
138
+ }
139
+ }
140
+ ```
141
+
142
+ #### 4. Generate Migrations
143
+
144
+ ```bash
145
+ bun run db:generate
146
+ ```
147
+
148
+ This creates:
149
+ - `drizzle/migrations.js` - Runtime migration imports
150
+ - `drizzle/0000_*.sql` - Migration SQL files
151
+ - `drizzle/meta/` - Journal and snapshots
152
+
153
+ The migrations are automatically run when `DrizzleChatAgent` initializes.
154
+
155
+ ### SqlChatAgent Setup
156
+
157
+ No additional setup needed! The SQL version creates tables automatically using `this.sql` template tags in `dbInitialize()`.
158
+
159
+ ## ChatAgentBase (Abstract Class)
160
+
161
+ The base class handles all chat logic and defines the abstract database interface that implementations must provide.
162
+
163
+ ### Abstract Methods
164
+
165
+ Subclasses must implement these database operations:
166
+
167
+ ```typescript
168
+ // Initialization
169
+ protected abstract dbInitialize(): void;
170
+
171
+ // Messages
172
+ protected abstract dbLoadMessages(): ChatMessage[];
173
+ protected abstract dbSaveMessage(msg: ChatMessage): void;
174
+ protected abstract dbClearAll(): void;
175
+
176
+ // Stream metadata
177
+ protected abstract dbFindActiveStream(): {
178
+ id: string;
179
+ messageId: string;
180
+ createdAt: Date
181
+ } | null;
182
+ protected abstract dbDeleteStreamWithChunks(streamId: string): void;
183
+ protected abstract dbInsertStreamMetadata(streamId: string, messageId: string): void;
184
+ protected abstract dbUpdateStreamStatus(streamId: string, status: 'completed' | 'error'): void;
185
+ protected abstract dbDeleteOldCompletedStreams(cutoffMs: number): void;
186
+
187
+ // Stream chunks
188
+ protected abstract dbFindMaxChunkIndex(streamId: string): number | null;
189
+ protected abstract dbInsertChunks(chunks: Array<{
190
+ id: string;
191
+ streamId: string;
192
+ content: string;
193
+ chunkIndex: number
194
+ }>): void;
195
+ protected abstract dbGetChunks(streamId: string): string[];
196
+ protected abstract dbDeleteChunks(streamId: string): void;
197
+ ```
198
+
199
+ ### Override Methods
200
+
201
+ Customize your agent's behavior:
202
+
203
+ ```typescript
204
+ protected getSystemPrompt(): string {
205
+ return "Your custom system prompt";
206
+ }
207
+
208
+ protected getModel(): string {
209
+ // Popular OpenRouter models:
210
+ // - anthropic/claude-opus-4.5 (most capable)
211
+ // - anthropic/claude-sonnet-4.5 (balanced, default)
212
+ // - anthropic/claude-haiku-3.5 (fastest, cheapest)
213
+ return "anthropic/claude-sonnet-4.5";
214
+ }
215
+
216
+ protected getTools(): ToolDefinition[] {
217
+ return [
218
+ // Your tools here
219
+ ];
220
+ }
221
+ ```
222
+
223
+ ## DrizzleChatAgent
224
+
225
+ Type-safe implementation using Drizzle ORM.
226
+
227
+ ### Database Schema
228
+
229
+ The package provides three tables:
230
+
231
+ ```typescript
232
+ // From @firtoz/chat-agent/db/schema
233
+
234
+ export const messagesTable = sqliteTable("messages", {
235
+ id: text("id").primaryKey(),
236
+ role: text("role", { enum: ["user", "assistant", "tool"] }).notNull(),
237
+ messageJson: text("message_json").notNull(),
238
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
239
+ });
240
+
241
+ export const streamChunksTable = sqliteTable("stream_chunks", {
242
+ id: text("id").primaryKey(),
243
+ streamId: text("stream_id").notNull(),
244
+ content: text("content").notNull(),
245
+ chunkIndex: integer("chunk_index").notNull(),
246
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
247
+ });
248
+
249
+ export const streamMetadataTable = sqliteTable("stream_metadata", {
250
+ id: text("id").primaryKey(),
251
+ messageId: text("message_id").notNull(),
252
+ status: text("status", { enum: ["streaming", "completed", "error"] }).notNull(),
253
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
254
+ completedAt: integer("completed_at", { mode: "timestamp_ms" }),
255
+ });
256
+ ```
257
+
258
+ ### Implementation Details
259
+
260
+ ```typescript
261
+ import { DrizzleChatAgent } from "@firtoz/chat-agent";
262
+
263
+ // Automatically:
264
+ // - Creates Drizzle DB instance in dbInitialize()
265
+ // - Runs migrations from drizzle/migrations.js
266
+ // - Uses type-safe query builder for all operations
267
+ ```
268
+
269
+ ## SqlChatAgent
270
+
271
+ Raw SQL implementation following `@cloudflare/ai-chat` pattern.
272
+
273
+ ### Table Structure
274
+
275
+ Creates these tables automatically:
276
+
277
+ ```sql
278
+ CREATE TABLE IF NOT EXISTS cf_ai_chat_agent_messages (
279
+ id TEXT PRIMARY KEY,
280
+ message TEXT NOT NULL,
281
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
282
+ );
283
+
284
+ CREATE TABLE IF NOT EXISTS cf_ai_chat_stream_chunks (
285
+ id TEXT PRIMARY KEY,
286
+ stream_id TEXT NOT NULL,
287
+ body TEXT NOT NULL,
288
+ chunk_index INTEGER NOT NULL,
289
+ created_at INTEGER NOT NULL
290
+ );
291
+
292
+ CREATE TABLE IF NOT EXISTS cf_ai_chat_stream_metadata (
293
+ id TEXT PRIMARY KEY,
294
+ request_id TEXT NOT NULL,
295
+ status TEXT NOT NULL,
296
+ created_at INTEGER NOT NULL,
297
+ completed_at INTEGER
298
+ );
299
+
300
+ CREATE INDEX IF NOT EXISTS idx_stream_chunks_stream_id
301
+ ON cf_ai_chat_stream_chunks(stream_id, chunk_index);
302
+ ```
303
+
304
+ ### Implementation Details
305
+
306
+ ```typescript
307
+ import { SqlChatAgent } from "@firtoz/chat-agent";
308
+
309
+ // Uses Agent's built-in this.sql template tag:
310
+ // - this.sql`SELECT * FROM table WHERE id = ${id}`
311
+ // - No additional dependencies
312
+ // - Tables created automatically in dbInitialize()
313
+ ```
314
+
315
+ ## Tools
316
+
317
+ ### Server-Side Tools
318
+
319
+ Tools with `execute` function run on the server:
320
+
321
+ ```typescript
322
+ defineTool({
323
+ name: "get_weather",
324
+ description: "Get weather for a location",
325
+ parameters: {
326
+ type: "object",
327
+ properties: {
328
+ location: { type: "string" }
329
+ },
330
+ required: ["location"]
331
+ },
332
+ execute: async (args: { location: string }) => {
333
+ // Runs on server, result automatically added to conversation
334
+ const weather = await fetchWeather(args.location);
335
+ return { temperature: weather.temp, condition: weather.condition };
336
+ }
337
+ })
338
+ ```
339
+
340
+ ### Client-Side Tools
341
+
342
+ Tools without `execute` are sent to client for execution:
343
+
344
+ ```typescript
345
+ defineTool({
346
+ name: "get_user_location",
347
+ description: "Get user's browser location",
348
+ parameters: {
349
+ type: "object",
350
+ properties: {}
351
+ }
352
+ // No execute - client handles this via WebSocket
353
+ })
354
+ ```
355
+
356
+ ## Environment Variables
357
+
358
+ ```env
359
+ # Required
360
+ OPENROUTER_API_KEY=sk-or-...
361
+
362
+ # Optional: Cloudflare AI Gateway
363
+ CLOUDFLARE_ACCOUNT_ID=your-account-id
364
+ AI_GATEWAY_NAME=your-gateway-name
365
+ AI_GATEWAY_TOKEN=your-gateway-token
366
+ ```
367
+
368
+ ## API Reference
369
+
370
+ ### ChatMessage
371
+
372
+ ```typescript
373
+ type UserMessage = {
374
+ id: string;
375
+ role: "user";
376
+ content: string;
377
+ createdAt: number;
378
+ };
379
+
380
+ type AssistantMessage = {
381
+ id: string;
382
+ role: "assistant";
383
+ content: string | null;
384
+ toolCalls?: ToolCall[];
385
+ createdAt: number;
386
+ };
387
+
388
+ type ToolMessage = {
389
+ id: string;
390
+ role: "tool";
391
+ toolCallId: string;
392
+ content: string;
393
+ createdAt: number;
394
+ };
395
+ ```
396
+
397
+ ### ToolDefinition
398
+
399
+ ```typescript
400
+ type ToolDefinition = {
401
+ type: "function";
402
+ function: {
403
+ name: string;
404
+ description?: string;
405
+ parameters?: JSONSchema;
406
+ strict?: boolean;
407
+ };
408
+ execute?: (args: any) => unknown | Promise<unknown>;
409
+ };
410
+ ```
411
+
412
+ ## Features
413
+
414
+ ### Implemented
415
+
416
+ - ✅ Message persistence (Drizzle or SQL)
417
+ - ✅ Resumable streaming with chunk buffering
418
+ - ✅ Stream restoration on reconnect
419
+ - ✅ Request cancellation via AbortController
420
+ - ✅ Server-side and client-side tools
421
+ - ✅ Tool result handling with auto-continue
422
+ - ✅ Cloudflare AI Gateway support
423
+ - ✅ DB-agnostic architecture
424
+
425
+ ### Comparison: Drizzle vs SQL
426
+
427
+ | Feature | DrizzleChatAgent | SqlChatAgent |
428
+ |---------|-----------------|--------------|
429
+ | Type Safety | ✅ Full type inference | ❌ Template string types only |
430
+ | Setup Complexity | ⚠️ Requires migrations | ✅ Auto-creates tables |
431
+ | Dependencies | Drizzle ORM + drizzle-kit | None (uses Agent.sql) |
432
+ | Query Builder | ✅ Yes | ❌ Raw SQL only |
433
+ | Performance | Same | Same |
434
+ | Wrangler Config | ⚠️ Requires rules for .sql | ✅ No special config |
435
+ | Recommended For | New projects, teams | Quick prototypes, SQL experts |
436
+
437
+ ## License
438
+
439
+ MIT
440
+
441
+ ## Contributing
442
+
443
+ PRs welcome!
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@firtoz/chat-agent",
3
+ "version": "1.0.0",
4
+ "description": "ChatAgent for Cloudflare Durable Objects with OpenRouter - a simplified alternative to @cloudflare/ai-chat",
5
+ "main": "./src/index.ts",
6
+ "module": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts",
12
+ "require": "./src/index.ts"
13
+ },
14
+ "./db/schema": {
15
+ "types": "./src/db/schema.ts",
16
+ "import": "./src/db/schema.ts",
17
+ "require": "./src/db/schema.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "src/**/*.ts",
22
+ "!src/**/*.test.ts",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "typecheck": "tsc --noEmit -p ./tsconfig.json",
27
+ "lint": "biome check --write src",
28
+ "lint:ci": "biome ci src",
29
+ "db:generate": "drizzle-kit generate",
30
+ "format": "biome format src --write"
31
+ },
32
+ "keywords": [
33
+ "typescript",
34
+ "durable-objects",
35
+ "cloudflare",
36
+ "cloudflare-workers",
37
+ "chat",
38
+ "ai",
39
+ "openrouter",
40
+ "drizzle-orm",
41
+ "agents"
42
+ ],
43
+ "author": "Firtina Ozbalikchi <firtoz@github.com>",
44
+ "license": "MIT",
45
+ "homepage": "https://github.com/firtoz/fullstack-toolkit#readme",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/firtoz/fullstack-toolkit.git",
49
+ "directory": "packages/chat-agent"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/firtoz/fullstack-toolkit/issues"
53
+ },
54
+ "peerDependencies": {
55
+ "@cloudflare/workers-types": "^4.20260118.0",
56
+ "@openrouter/sdk": "^0.3.12",
57
+ "agents": "^0.3.4",
58
+ "drizzle-orm": "^0.45.1"
59
+ },
60
+ "optionalDependencies": {
61
+ "zod": "^4.3.5"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "devDependencies": {
70
+ "bun-types": "^1.3.6",
71
+ "drizzle-kit": "^0.31.8",
72
+ "typescript": "^5.9.3"
73
+ }
74
+ }