@andreprado/agentkit 0.1.0-alpha.10

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.
Files changed (122) hide show
  1. package/README.md +69 -0
  2. package/bin/agentkit.mjs +23 -0
  3. package/docs/guides/add-channel.md +114 -0
  4. package/docs/guides/add-knowledge.md +134 -0
  5. package/docs/guides/add-tool.md +342 -0
  6. package/docs/guides/agentkit-skills-architecture.md +471 -0
  7. package/docs/guides/channel-security.md +81 -0
  8. package/docs/guides/channels-implementation-map.md +243 -0
  9. package/docs/guides/channels-production-handoff.md +102 -0
  10. package/docs/guides/connect-telegram.md +110 -0
  11. package/docs/guides/connect-whatsapp-zapster.md +119 -0
  12. package/docs/guides/create-agent.md +220 -0
  13. package/docs/guides/prepare-deploy.md +209 -0
  14. package/docs/guides/run-evals.md +179 -0
  15. package/docs/guides/security-rules.md +156 -0
  16. package/docs/guides/use-provider.md +140 -0
  17. package/docs/llms-full.txt +876 -0
  18. package/docs/llms.txt +83 -0
  19. package/docs/portable-deploy-release-checklist.md +41 -0
  20. package/package.json +47 -0
  21. package/src/cli/args.ts +36 -0
  22. package/src/cli/cloud-client.ts +265 -0
  23. package/src/cli/commands/channels.ts +810 -0
  24. package/src/cli/commands/knowledge.ts +136 -0
  25. package/src/cli/constants.ts +4 -0
  26. package/src/cli/deploy-chat-ui.ts +392 -0
  27. package/src/cli/deploy-readiness.ts +348 -0
  28. package/src/cli/flags.ts +162 -0
  29. package/src/cli/help.ts +184 -0
  30. package/src/cli/index.ts +1276 -0
  31. package/src/cli/process.ts +31 -0
  32. package/src/cloud/artifact.ts +139 -0
  33. package/src/cloud/client.ts +79 -0
  34. package/src/cloud/contracts.ts +63 -0
  35. package/src/cloud/index.ts +3 -0
  36. package/src/create-project.ts +177 -0
  37. package/src/index.ts +408 -0
  38. package/src/providers/index.ts +25 -0
  39. package/src/providers/pi.ts +286 -0
  40. package/src/providers/test.ts +133 -0
  41. package/src/providers/types.ts +34 -0
  42. package/src/runtime/build.ts +43 -0
  43. package/src/runtime/channel-buffer.ts +30 -0
  44. package/src/runtime/channel-test-harness.ts +112 -0
  45. package/src/runtime/channels/telegram.ts +360 -0
  46. package/src/runtime/channels/website.ts +132 -0
  47. package/src/runtime/channels/whatsapp-meta.ts +71 -0
  48. package/src/runtime/channels/whatsapp-zapster.ts +278 -0
  49. package/src/runtime/channels.ts +138 -0
  50. package/src/runtime/chat.ts +218 -0
  51. package/src/runtime/config.ts +684 -0
  52. package/src/runtime/conversations.ts +38 -0
  53. package/src/runtime/core/deploy-state.ts +54 -0
  54. package/src/runtime/core/manifest.ts +213 -0
  55. package/src/runtime/core/targets.ts +133 -0
  56. package/src/runtime/database.ts +256 -0
  57. package/src/runtime/db-commands.ts +167 -0
  58. package/src/runtime/deploy-readiness.ts +105 -0
  59. package/src/runtime/deploy.ts +1 -0
  60. package/src/runtime/dev-server.ts +1247 -0
  61. package/src/runtime/docs.ts +36 -0
  62. package/src/runtime/env.ts +152 -0
  63. package/src/runtime/errors.ts +13 -0
  64. package/src/runtime/evals.ts +509 -0
  65. package/src/runtime/inspect.ts +203 -0
  66. package/src/runtime/knowledge/chunk.ts +333 -0
  67. package/src/runtime/knowledge/config.ts +135 -0
  68. package/src/runtime/knowledge/embeddings.ts +133 -0
  69. package/src/runtime/knowledge/ingest.ts +521 -0
  70. package/src/runtime/knowledge/prompt-policy.ts +30 -0
  71. package/src/runtime/knowledge/retrieve.ts +283 -0
  72. package/src/runtime/knowledge/schema.ts +56 -0
  73. package/src/runtime/knowledge/tool.ts +64 -0
  74. package/src/runtime/knowledge/vector.ts +258 -0
  75. package/src/runtime/runtime-contract.ts +93 -0
  76. package/src/runtime/spec.ts +152 -0
  77. package/src/runtime/sync.ts +144 -0
  78. package/src/runtime/targets/cloudflare/build.ts +2517 -0
  79. package/src/runtime/targets/container/build.ts +146 -0
  80. package/src/runtime/targets/container/server.ts +33 -0
  81. package/src/runtime/targets/vps/deploy.ts +206 -0
  82. package/src/runtime/tool-runner.ts +65 -0
  83. package/src/runtime/tools.ts +470 -0
  84. package/src/runtime/traces.ts +41 -0
  85. package/src/storage/sqlite.ts +1118 -0
  86. package/src/templates/blank.ts +394 -0
  87. package/src/templates/dentista.ts +1003 -0
  88. package/src/templates/index.ts +33 -0
  89. package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
  90. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
  91. package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
  92. package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
  93. package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
  94. package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
  95. package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
  96. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
  97. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +41 -0
  98. package/src/templates/skills/agentkit-channels/references/telegram.md +38 -0
  99. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +44 -0
  100. package/src/templates/skills/agentkit-database/SKILL.md +45 -0
  101. package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
  102. package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
  103. package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
  104. package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
  105. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
  106. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
  107. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
  108. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
  109. package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
  110. package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
  111. package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
  112. package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
  113. package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
  114. package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
  115. package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
  116. package/src/templates/skills/agentkit-security/SKILL.md +55 -0
  117. package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
  118. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
  119. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
  120. package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
  121. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
  122. package/src/templates/support.ts +401 -0
@@ -0,0 +1,1118 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+
5
+ import type { DatabaseArgs, DatabaseResult, DatabaseRow, DatabaseStatement } from "../index";
6
+ import type { AgentMessageRole, ProviderRunResult } from "../providers";
7
+ import type { LoadedAgentCapsule } from "../runtime/config";
8
+ import { AgentKitError } from "../runtime/errors";
9
+ import { KNOWLEDGE_MIGRATION_ID, KNOWLEDGE_SCHEMA_SQL } from "../runtime/knowledge/schema";
10
+
11
+ export type ConversationSummary = {
12
+ id: string;
13
+ agentName: string;
14
+ title: string | null;
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ messageCount: number;
18
+ };
19
+
20
+ export type StoredMessage = {
21
+ id: string;
22
+ conversationId: string;
23
+ role: AgentMessageRole;
24
+ content: string;
25
+ createdAt: string;
26
+ sequence: number;
27
+ };
28
+
29
+ export type ConversationRecord = ConversationSummary & {
30
+ messages: StoredMessage[];
31
+ };
32
+
33
+ export type RunStatus = "running" | "completed" | "failed";
34
+ export type ToolCallStatus = "running" | "completed" | "failed";
35
+ export type ToolCallVisibility = "user" | "internal";
36
+
37
+ export type StoredToolCall = {
38
+ id: string;
39
+ runId: string;
40
+ conversationId: string | null;
41
+ toolName: string;
42
+ visibility: ToolCallVisibility;
43
+ input: unknown;
44
+ output: unknown;
45
+ rendered: unknown;
46
+ status: ToolCallStatus;
47
+ startedAt: string;
48
+ completedAt: string | null;
49
+ errorMessage: string | null;
50
+ };
51
+
52
+ export type StoredRun = {
53
+ id: string;
54
+ conversationId: string | null;
55
+ provider: string;
56
+ model: string;
57
+ status: RunStatus;
58
+ usage: {
59
+ inputTokens: number | null;
60
+ outputTokens: number | null;
61
+ totalTokens: number | null;
62
+ };
63
+ startedAt: string;
64
+ completedAt: string | null;
65
+ errorCode: string | null;
66
+ errorMessage: string | null;
67
+ };
68
+
69
+ export type PersistedChatRun = {
70
+ conversationId: string;
71
+ runId: string;
72
+ userMessageId: string;
73
+ assistantMessageId?: string;
74
+ };
75
+
76
+ type SqliteRow = Record<string, unknown>;
77
+ type SqliteColumn = {
78
+ name: string;
79
+ };
80
+ type SqliteStatement = {
81
+ run(...bindings: unknown[]): { changes?: number; lastInsertRowid?: number | bigint };
82
+ all(...bindings: unknown[]): SqliteRow[];
83
+ get(...bindings: unknown[]): SqliteRow | null | undefined;
84
+ columnNames?: string[];
85
+ columns?: () => SqliteColumn[];
86
+ };
87
+ type SqliteDatabase = {
88
+ close(): void;
89
+ exec(sql: string): unknown;
90
+ prepare(sql: string): SqliteStatement;
91
+ };
92
+
93
+ const MIGRATIONS = [
94
+ {
95
+ id: "0001_create_conversations",
96
+ sql: `
97
+ CREATE TABLE IF NOT EXISTS conversations (
98
+ id TEXT PRIMARY KEY,
99
+ agent_name TEXT NOT NULL,
100
+ title TEXT,
101
+ created_at TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL,
103
+ metadata_json TEXT NOT NULL DEFAULT '{}'
104
+ );
105
+ `,
106
+ },
107
+ {
108
+ id: "0002_create_messages",
109
+ sql: `
110
+ CREATE TABLE IF NOT EXISTS messages (
111
+ id TEXT PRIMARY KEY,
112
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
113
+ role TEXT NOT NULL,
114
+ content TEXT NOT NULL,
115
+ created_at TEXT NOT NULL,
116
+ sequence INTEGER NOT NULL,
117
+ metadata_json TEXT NOT NULL DEFAULT '{}'
118
+ );
119
+
120
+ CREATE INDEX IF NOT EXISTS messages_conversation_sequence_idx
121
+ ON messages(conversation_id, sequence);
122
+ `,
123
+ },
124
+ {
125
+ id: "0003_create_runs",
126
+ sql: `
127
+ CREATE TABLE IF NOT EXISTS runs (
128
+ id TEXT PRIMARY KEY,
129
+ conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,
130
+ provider TEXT NOT NULL,
131
+ model TEXT NOT NULL,
132
+ status TEXT NOT NULL,
133
+ input_tokens INTEGER,
134
+ output_tokens INTEGER,
135
+ total_tokens INTEGER,
136
+ started_at TEXT NOT NULL,
137
+ completed_at TEXT,
138
+ error_code TEXT,
139
+ error_message TEXT,
140
+ metadata_json TEXT NOT NULL DEFAULT '{}'
141
+ );
142
+
143
+ CREATE INDEX IF NOT EXISTS runs_conversation_started_idx
144
+ ON runs(conversation_id, started_at);
145
+ `,
146
+ },
147
+ {
148
+ id: "0004_create_tool_calls",
149
+ sql: `
150
+ CREATE TABLE IF NOT EXISTS tool_calls (
151
+ id TEXT PRIMARY KEY,
152
+ run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
153
+ conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,
154
+ tool_name TEXT NOT NULL,
155
+ permission_json TEXT NOT NULL DEFAULT '[]',
156
+ input_json TEXT NOT NULL,
157
+ output_json TEXT,
158
+ status TEXT NOT NULL,
159
+ started_at TEXT NOT NULL,
160
+ completed_at TEXT,
161
+ error_message TEXT
162
+ );
163
+
164
+ CREATE INDEX IF NOT EXISTS tool_calls_run_started_idx
165
+ ON tool_calls(run_id, started_at);
166
+ `,
167
+ },
168
+ {
169
+ id: "0005_add_tool_call_visibility",
170
+ sql: `
171
+ ALTER TABLE tool_calls
172
+ ADD COLUMN visibility TEXT NOT NULL DEFAULT 'user';
173
+ `,
174
+ },
175
+ {
176
+ id: KNOWLEDGE_MIGRATION_ID,
177
+ sql: KNOWLEDGE_SCHEMA_SQL,
178
+ },
179
+ {
180
+ id: "0007_add_tool_call_rendered",
181
+ sql: `
182
+ ALTER TABLE tool_calls
183
+ ADD COLUMN rendered_json TEXT;
184
+ `,
185
+ },
186
+ ] as const;
187
+
188
+ export async function openCapsuleStore(capsule: LoadedAgentCapsule): Promise<SqliteAgentKitStore> {
189
+ if (capsule.config.storage.driver !== "sqlite" && capsule.config.storage.driver !== "agentkit") {
190
+ throw new AgentKitError(
191
+ "storage_not_supported",
192
+ `Storage driver "${capsule.config.storage.driver}" is not supported by the local runtime.`,
193
+ );
194
+ }
195
+
196
+ if (!capsule.storagePath) {
197
+ throw new AgentKitError("invalid_config", 'SQLite storage requires storage.path, usually ".agentkit/agentkit.db".');
198
+ }
199
+
200
+ return SqliteAgentKitStore.open(capsule.storagePath);
201
+ }
202
+
203
+ async function openSqliteDatabase(dbPath: string): Promise<SqliteDatabase> {
204
+ if (typeof (globalThis as { Bun?: unknown }).Bun !== "undefined") {
205
+ const bunSqliteSpecifier: string = "bun:sqlite";
206
+ const sqlite = (await import(bunSqliteSpecifier)) as {
207
+ Database: new (path: string, options?: { create?: boolean; readwrite?: boolean }) => SqliteDatabase;
208
+ };
209
+ return new sqlite.Database(dbPath, { create: true, readwrite: true });
210
+ }
211
+
212
+ const sqlite = (await import("node:sqlite")) as {
213
+ DatabaseSync: new (path: string) => SqliteDatabase;
214
+ };
215
+ return new sqlite.DatabaseSync(dbPath);
216
+ }
217
+
218
+ function sqliteColumnNames(statement: SqliteStatement): string[] {
219
+ if (statement.columns) {
220
+ return statement.columns().map((column) => column.name);
221
+ }
222
+
223
+ return statement.columnNames ?? [];
224
+ }
225
+
226
+ export class SqliteAgentKitStore {
227
+ static async open(dbPath: string): Promise<SqliteAgentKitStore> {
228
+ await mkdir(dirname(dbPath), { recursive: true });
229
+ const database = await openSqliteDatabase(dbPath);
230
+ const store = new SqliteAgentKitStore(dbPath, database);
231
+ try {
232
+ store.migrate();
233
+ } catch (error) {
234
+ database.close();
235
+ throw translateSqliteError(error);
236
+ }
237
+ return store;
238
+ }
239
+
240
+ private constructor(
241
+ readonly dbPath: string,
242
+ private readonly db: SqliteDatabase,
243
+ ) {}
244
+
245
+ private applicationTransactionActive = false;
246
+
247
+ close(): void {
248
+ this.db.close();
249
+ }
250
+
251
+ createConversation(input: { id?: string; agentName: string; title: string | null; now?: string }): ConversationSummary {
252
+ return this.sqlite(() => {
253
+ const now = input.now ?? new Date().toISOString();
254
+ const id = input.id ?? randomId();
255
+
256
+ this.db
257
+ .prepare(
258
+ `
259
+ INSERT INTO conversations (id, agent_name, title, created_at, updated_at, metadata_json)
260
+ VALUES ($id, $agentName, $title, $createdAt, $updatedAt, '{}')
261
+ `,
262
+ )
263
+ .run({
264
+ $id: id,
265
+ $agentName: input.agentName,
266
+ $title: input.title,
267
+ $createdAt: now,
268
+ $updatedAt: now,
269
+ });
270
+
271
+ return {
272
+ id,
273
+ agentName: input.agentName,
274
+ title: input.title,
275
+ createdAt: now,
276
+ updatedAt: now,
277
+ messageCount: 0,
278
+ };
279
+ });
280
+ }
281
+
282
+ appendMessage(input: {
283
+ conversationId: string;
284
+ role: AgentMessageRole;
285
+ content: string;
286
+ now?: string;
287
+ }): StoredMessage {
288
+ return this.sqlite(() => {
289
+ const now = input.now ?? new Date().toISOString();
290
+ const sequence = this.nextMessageSequence(input.conversationId);
291
+ const id = randomId();
292
+
293
+ const insertMessage = () => this.transaction(() => {
294
+ this.db
295
+ .prepare(
296
+ `
297
+ INSERT INTO messages (id, conversation_id, role, content, created_at, sequence, metadata_json)
298
+ VALUES ($id, $conversationId, $role, $content, $createdAt, $sequence, '{}')
299
+ `,
300
+ )
301
+ .run({
302
+ $id: id,
303
+ $conversationId: input.conversationId,
304
+ $role: input.role,
305
+ $content: input.content,
306
+ $createdAt: now,
307
+ $sequence: sequence,
308
+ });
309
+
310
+ this.touchConversation(input.conversationId, now);
311
+ });
312
+
313
+ insertMessage();
314
+
315
+ return {
316
+ id,
317
+ conversationId: input.conversationId,
318
+ role: input.role,
319
+ content: input.content,
320
+ createdAt: now,
321
+ sequence,
322
+ };
323
+ });
324
+ }
325
+
326
+ createRun(input: {
327
+ conversationId: string;
328
+ provider: string;
329
+ model: string;
330
+ status?: RunStatus;
331
+ startedAt?: string;
332
+ }): { id: string; conversationId: string; startedAt: string; status: RunStatus } {
333
+ return this.sqlite(() => {
334
+ const id = randomId();
335
+ const startedAt = input.startedAt ?? new Date().toISOString();
336
+ const status = input.status ?? "running";
337
+
338
+ this.db
339
+ .prepare(
340
+ `
341
+ INSERT INTO runs (
342
+ id,
343
+ conversation_id,
344
+ provider,
345
+ model,
346
+ status,
347
+ started_at,
348
+ metadata_json
349
+ )
350
+ VALUES (
351
+ $id,
352
+ $conversationId,
353
+ $provider,
354
+ $model,
355
+ $status,
356
+ $startedAt,
357
+ '{}'
358
+ )
359
+ `,
360
+ )
361
+ .run({
362
+ $id: id,
363
+ $conversationId: input.conversationId,
364
+ $provider: input.provider,
365
+ $model: input.model,
366
+ $status: status,
367
+ $startedAt: startedAt,
368
+ });
369
+
370
+ return { id, conversationId: input.conversationId, startedAt, status };
371
+ });
372
+ }
373
+
374
+ completeRun(input: {
375
+ runId: string;
376
+ status: RunStatus;
377
+ completedAt?: string;
378
+ usage?: ProviderRunResult["usage"];
379
+ errorCode?: string;
380
+ errorMessage?: string;
381
+ }): void {
382
+ this.sqlite(() => {
383
+ this.db
384
+ .prepare(
385
+ `
386
+ UPDATE runs
387
+ SET
388
+ status = $status,
389
+ completed_at = $completedAt,
390
+ input_tokens = $inputTokens,
391
+ output_tokens = $outputTokens,
392
+ total_tokens = $totalTokens,
393
+ error_code = $errorCode,
394
+ error_message = $errorMessage
395
+ WHERE id = $runId
396
+ `,
397
+ )
398
+ .run({
399
+ $runId: input.runId,
400
+ $status: input.status,
401
+ $completedAt: input.completedAt ?? new Date().toISOString(),
402
+ $inputTokens: input.usage?.inputTokens ?? null,
403
+ $outputTokens: input.usage?.outputTokens ?? null,
404
+ $totalTokens: input.usage?.totalTokens ?? null,
405
+ $errorCode: input.errorCode ?? null,
406
+ $errorMessage: redactSecrets(input.errorMessage ?? null),
407
+ });
408
+ });
409
+ }
410
+
411
+ createToolCall(input: {
412
+ runId: string;
413
+ conversationId: string;
414
+ toolName: string;
415
+ visibility?: ToolCallVisibility;
416
+ permissions?: string[];
417
+ input: unknown;
418
+ startedAt?: string;
419
+ secretValues?: string[];
420
+ }): {
421
+ id: string;
422
+ runId: string;
423
+ conversationId: string;
424
+ toolName: string;
425
+ visibility: ToolCallVisibility;
426
+ startedAt: string;
427
+ status: ToolCallStatus;
428
+ } {
429
+ return this.sqlite(() => {
430
+ const id = randomId();
431
+ const startedAt = input.startedAt ?? new Date().toISOString();
432
+ const status: ToolCallStatus = "running";
433
+ const visibility = input.visibility ?? "user";
434
+
435
+ this.db
436
+ .prepare(
437
+ `
438
+ INSERT INTO tool_calls (
439
+ id,
440
+ run_id,
441
+ conversation_id,
442
+ tool_name,
443
+ visibility,
444
+ permission_json,
445
+ input_json,
446
+ status,
447
+ started_at
448
+ )
449
+ VALUES (
450
+ $id,
451
+ $runId,
452
+ $conversationId,
453
+ $toolName,
454
+ $visibility,
455
+ $permissionJson,
456
+ $inputJson,
457
+ $status,
458
+ $startedAt
459
+ )
460
+ `,
461
+ )
462
+ .run({
463
+ $id: id,
464
+ $runId: input.runId,
465
+ $conversationId: input.conversationId,
466
+ $toolName: input.toolName,
467
+ $visibility: visibility,
468
+ $permissionJson: serializeJson(input.permissions ?? [], input.secretValues),
469
+ $inputJson: serializeJson(input.input, input.secretValues),
470
+ $status: status,
471
+ $startedAt: startedAt,
472
+ });
473
+
474
+ return {
475
+ id,
476
+ runId: input.runId,
477
+ conversationId: input.conversationId,
478
+ toolName: input.toolName,
479
+ visibility,
480
+ startedAt,
481
+ status,
482
+ };
483
+ });
484
+ }
485
+
486
+ completeToolCall(input: {
487
+ toolCallId: string;
488
+ status: ToolCallStatus;
489
+ output?: unknown;
490
+ rendered?: unknown;
491
+ completedAt?: string;
492
+ errorMessage?: string;
493
+ secretValues?: string[];
494
+ }): void {
495
+ this.sqlite(() => {
496
+ this.db
497
+ .prepare(
498
+ `
499
+ UPDATE tool_calls
500
+ SET
501
+ status = $status,
502
+ output_json = $outputJson,
503
+ rendered_json = $renderedJson,
504
+ completed_at = $completedAt,
505
+ error_message = $errorMessage
506
+ WHERE id = $toolCallId
507
+ `,
508
+ )
509
+ .run({
510
+ $toolCallId: input.toolCallId,
511
+ $status: input.status,
512
+ $outputJson: input.output === undefined ? null : serializeJson(input.output, input.secretValues),
513
+ $renderedJson: input.rendered === undefined ? null : serializeJson(input.rendered, input.secretValues),
514
+ $completedAt: input.completedAt ?? new Date().toISOString(),
515
+ $errorMessage: redactSecrets(input.errorMessage ?? null, input.secretValues),
516
+ });
517
+ });
518
+ }
519
+
520
+ listConversations(): ConversationSummary[] {
521
+ return this.sqlite(() => {
522
+ const rows = this.db
523
+ .prepare(
524
+ `
525
+ SELECT
526
+ conversations.id,
527
+ conversations.agent_name,
528
+ conversations.title,
529
+ conversations.created_at,
530
+ conversations.updated_at,
531
+ COUNT(messages.id) AS message_count
532
+ FROM conversations
533
+ LEFT JOIN messages ON messages.conversation_id = conversations.id
534
+ GROUP BY conversations.id
535
+ ORDER BY conversations.updated_at DESC
536
+ `,
537
+ )
538
+ .all() as SqliteRow[];
539
+
540
+ return rows.map(conversationSummaryFromRow);
541
+ });
542
+ }
543
+
544
+ getConversation(id: string): ConversationRecord | null {
545
+ return this.sqlite(() => {
546
+ const conversationRow = this.db
547
+ .prepare(
548
+ `
549
+ SELECT
550
+ conversations.id,
551
+ conversations.agent_name,
552
+ conversations.title,
553
+ conversations.created_at,
554
+ conversations.updated_at,
555
+ COUNT(messages.id) AS message_count
556
+ FROM conversations
557
+ LEFT JOIN messages ON messages.conversation_id = conversations.id
558
+ WHERE conversations.id = $id
559
+ GROUP BY conversations.id
560
+ `,
561
+ )
562
+ .get({ $id: id }) as SqliteRow | null;
563
+
564
+ if (!conversationRow) {
565
+ return null;
566
+ }
567
+
568
+ const messageRows = this.db
569
+ .prepare(
570
+ `
571
+ SELECT id, conversation_id, role, content, created_at, sequence
572
+ FROM messages
573
+ WHERE conversation_id = $id
574
+ ORDER BY sequence ASC
575
+ `,
576
+ )
577
+ .all({ $id: id }) as SqliteRow[];
578
+
579
+ return {
580
+ ...conversationSummaryFromRow(conversationRow),
581
+ messages: messageRows.map(storedMessageFromRow),
582
+ };
583
+ });
584
+ }
585
+
586
+ listRunsForConversation(conversationId: string): StoredRun[] {
587
+ return this.sqlite(() => {
588
+ const rows = this.db
589
+ .prepare(
590
+ `
591
+ SELECT
592
+ id,
593
+ conversation_id,
594
+ provider,
595
+ model,
596
+ status,
597
+ input_tokens,
598
+ output_tokens,
599
+ total_tokens,
600
+ started_at,
601
+ completed_at,
602
+ error_code,
603
+ error_message
604
+ FROM runs
605
+ WHERE conversation_id = $conversationId
606
+ ORDER BY started_at ASC
607
+ `,
608
+ )
609
+ .all({ $conversationId: conversationId }) as SqliteRow[];
610
+
611
+ return rows.map(storedRunFromRow);
612
+ });
613
+ }
614
+
615
+ listToolCallsForRun(runId: string): StoredToolCall[] {
616
+ return this.sqlite(() => {
617
+ const rows = this.db
618
+ .prepare(
619
+ `
620
+ SELECT
621
+ id,
622
+ run_id,
623
+ conversation_id,
624
+ tool_name,
625
+ visibility,
626
+ input_json,
627
+ output_json,
628
+ rendered_json,
629
+ status,
630
+ started_at,
631
+ completed_at,
632
+ error_message
633
+ FROM tool_calls
634
+ WHERE run_id = $runId
635
+ ORDER BY started_at ASC
636
+ `,
637
+ )
638
+ .all({ $runId: runId }) as SqliteRow[];
639
+
640
+ return rows.map(storedToolCallFromRow);
641
+ });
642
+ }
643
+
644
+ applyApplicationMigrations(migrations: Array<{ id: string; sql: string }>): { applied: string[]; skipped: string[] } {
645
+ return this.sqlite(() => {
646
+ if (migrations.length === 0) {
647
+ return { applied: [], skipped: [] };
648
+ }
649
+
650
+ this.db.exec(`
651
+ CREATE TABLE IF NOT EXISTS agentkit_app_migrations (
652
+ id TEXT PRIMARY KEY,
653
+ applied_at TEXT NOT NULL
654
+ )
655
+ `);
656
+
657
+ const applied = new Set(
658
+ (
659
+ this.db.prepare("SELECT id FROM agentkit_app_migrations").all() as Array<{
660
+ id: string;
661
+ }>
662
+ ).map((row) => row.id),
663
+ );
664
+ const result = {
665
+ applied: [] as string[],
666
+ skipped: [] as string[],
667
+ };
668
+
669
+ for (const migration of migrations) {
670
+ if (applied.has(migration.id)) {
671
+ result.skipped.push(migration.id);
672
+ continue;
673
+ }
674
+
675
+ const statements = splitSqlStatements(migration.sql);
676
+ this.transaction(() => {
677
+ for (const statement of statements) {
678
+ this.db.exec(statement);
679
+ }
680
+
681
+ this.db
682
+ .prepare("INSERT INTO agentkit_app_migrations (id, applied_at) VALUES ($id, $appliedAt)")
683
+ .run({ $id: migration.id, $appliedAt: new Date().toISOString() });
684
+ });
685
+ result.applied.push(migration.id);
686
+ }
687
+
688
+ return result;
689
+ });
690
+ }
691
+
692
+ applyApplicationSchema(source: string): void {
693
+ this.sqlite(() => {
694
+ const statements = splitSqlStatements(source);
695
+
696
+ if (statements.length === 0) {
697
+ return;
698
+ }
699
+
700
+ const transaction = () => this.transaction(() => {
701
+ for (const statement of statements) {
702
+ this.db.exec(statement);
703
+ }
704
+ });
705
+
706
+ transaction();
707
+ });
708
+ }
709
+
710
+ executeApplicationSql(sql: string, args: DatabaseArgs = []): DatabaseResult {
711
+ return this.sqlite(() => {
712
+ const statement = this.db.prepare(sql);
713
+ const columns = sqliteColumnNames(statement);
714
+ const bindings = [...args] as unknown[];
715
+
716
+ if (columns.length > 0) {
717
+ return {
718
+ rows: normalizeRows((statement.all as (...bindings: unknown[]) => SqliteRow[])(...bindings)),
719
+ rowsAffected: 0,
720
+ lastInsertRowid: null,
721
+ columns,
722
+ };
723
+ }
724
+
725
+ const result = (statement.run as (...bindings: unknown[]) => { changes: number; lastInsertRowid: number | bigint })(
726
+ ...bindings,
727
+ );
728
+ return {
729
+ rows: [],
730
+ rowsAffected: result.changes,
731
+ lastInsertRowid: normalizeLastInsertRowid(result.lastInsertRowid),
732
+ columns: [],
733
+ };
734
+ });
735
+ }
736
+
737
+ executeApplicationBatch(statements: DatabaseStatement[]): DatabaseResult[] {
738
+ return this.runApplicationTransaction(() =>
739
+ statements.map((statement) => this.executeApplicationSql(statement.sql, statement.args ?? [])),
740
+ ) as unknown as DatabaseResult[];
741
+ }
742
+
743
+ runApplicationTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
744
+ runApplicationTransaction<T>(callback: () => T): T;
745
+ runApplicationTransaction<T>(callback: () => Promise<T> | T): Promise<T> | T {
746
+ if (this.applicationTransactionActive) {
747
+ throw new AgentKitError(
748
+ "database_transaction_unsupported",
749
+ "Nested database transactions are not supported. Use one ctx.db.transaction callback or ctx.db.batch call.",
750
+ );
751
+ }
752
+
753
+ return this.sqlite(() => {
754
+ this.applicationTransactionActive = true;
755
+ this.db.exec("BEGIN IMMEDIATE");
756
+
757
+ let callbackResult: Promise<T> | T;
758
+
759
+ try {
760
+ callbackResult = callback();
761
+ } catch (error) {
762
+ this.rollbackApplicationTransaction();
763
+ throw error;
764
+ }
765
+
766
+ if (callbackResult && typeof (callbackResult as Promise<T>).then === "function") {
767
+ return (callbackResult as Promise<T>).then(
768
+ (value) => {
769
+ this.commitApplicationTransaction();
770
+ return value;
771
+ },
772
+ (error) => {
773
+ this.rollbackApplicationTransaction();
774
+ throw error;
775
+ },
776
+ );
777
+ }
778
+
779
+ this.commitApplicationTransaction();
780
+ return callbackResult;
781
+ });
782
+ }
783
+
784
+ private migrate(): void {
785
+ this.db.exec(`PRAGMA busy_timeout = ${sqliteBusyTimeoutMs()}`);
786
+ this.db.exec("PRAGMA foreign_keys = ON");
787
+ this.db.exec("PRAGMA journal_mode = WAL");
788
+ this.db.exec(`
789
+ CREATE TABLE IF NOT EXISTS agentkit_migrations (
790
+ id TEXT PRIMARY KEY,
791
+ applied_at TEXT NOT NULL
792
+ )
793
+ `);
794
+
795
+ const applied = new Set(
796
+ (
797
+ this.db.prepare("SELECT id FROM agentkit_migrations").all() as Array<{
798
+ id: string;
799
+ }>
800
+ ).map((row) => row.id),
801
+ );
802
+
803
+ const transaction = () => this.transaction(() => {
804
+ for (const migration of MIGRATIONS) {
805
+ if (applied.has(migration.id)) {
806
+ continue;
807
+ }
808
+
809
+ this.db.exec(migration.sql);
810
+ this.db
811
+ .prepare("INSERT INTO agentkit_migrations (id, applied_at) VALUES ($id, $appliedAt)")
812
+ .run({ $id: migration.id, $appliedAt: new Date().toISOString() });
813
+ }
814
+ });
815
+
816
+ transaction();
817
+ }
818
+
819
+ private nextMessageSequence(conversationId: string): number {
820
+ const row = this.db
821
+ .prepare("SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence FROM messages WHERE conversation_id = $id")
822
+ .get({ $id: conversationId }) as { next_sequence: number };
823
+
824
+ return row.next_sequence;
825
+ }
826
+
827
+ private touchConversation(conversationId: string, updatedAt: string): void {
828
+ this.db
829
+ .prepare("UPDATE conversations SET updated_at = $updatedAt WHERE id = $id")
830
+ .run({ $id: conversationId, $updatedAt: updatedAt });
831
+ }
832
+
833
+ private sqlite<T>(operation: () => T): T {
834
+ try {
835
+ return operation();
836
+ } catch (error) {
837
+ throw translateSqliteError(error);
838
+ }
839
+ }
840
+
841
+ private commitApplicationTransaction(): void {
842
+ try {
843
+ this.db.exec("COMMIT");
844
+ } finally {
845
+ this.applicationTransactionActive = false;
846
+ }
847
+ }
848
+
849
+ private rollbackApplicationTransaction(): void {
850
+ try {
851
+ this.db.exec("ROLLBACK");
852
+ } finally {
853
+ this.applicationTransactionActive = false;
854
+ }
855
+ }
856
+
857
+ private transaction<T>(callback: () => T): T {
858
+ this.db.exec("BEGIN IMMEDIATE");
859
+
860
+ try {
861
+ const result = callback();
862
+ this.db.exec("COMMIT");
863
+ return result;
864
+ } catch (error) {
865
+ this.db.exec("ROLLBACK");
866
+ throw error;
867
+ }
868
+ }
869
+ }
870
+
871
+ function conversationSummaryFromRow(row: SqliteRow): ConversationSummary {
872
+ return {
873
+ id: String(row.id),
874
+ agentName: String(row.agent_name),
875
+ title: row.title === null ? null : String(row.title),
876
+ createdAt: String(row.created_at),
877
+ updatedAt: String(row.updated_at),
878
+ messageCount: Number(row.message_count),
879
+ };
880
+ }
881
+
882
+ function storedMessageFromRow(row: SqliteRow): StoredMessage {
883
+ const role = String(row.role);
884
+
885
+ if (role !== "system" && role !== "user" && role !== "assistant") {
886
+ throw new AgentKitError("storage_error", `Invalid stored message role "${role}".`);
887
+ }
888
+
889
+ return {
890
+ id: String(row.id),
891
+ conversationId: String(row.conversation_id),
892
+ role,
893
+ content: String(row.content),
894
+ createdAt: String(row.created_at),
895
+ sequence: Number(row.sequence),
896
+ };
897
+ }
898
+
899
+ function storedRunFromRow(row: SqliteRow): StoredRun {
900
+ const status = String(row.status);
901
+
902
+ if (status !== "running" && status !== "completed" && status !== "failed") {
903
+ throw new AgentKitError("storage_error", `Invalid stored run status "${status}".`);
904
+ }
905
+
906
+ return {
907
+ id: String(row.id),
908
+ conversationId: row.conversation_id === null ? null : String(row.conversation_id),
909
+ provider: String(row.provider),
910
+ model: String(row.model),
911
+ status,
912
+ usage: {
913
+ inputTokens: nullableNumber(row.input_tokens),
914
+ outputTokens: nullableNumber(row.output_tokens),
915
+ totalTokens: nullableNumber(row.total_tokens),
916
+ },
917
+ startedAt: String(row.started_at),
918
+ completedAt: row.completed_at === null ? null : String(row.completed_at),
919
+ errorCode: row.error_code === null ? null : String(row.error_code),
920
+ errorMessage: row.error_message === null ? null : String(row.error_message),
921
+ };
922
+ }
923
+
924
+ function storedToolCallFromRow(row: SqliteRow): StoredToolCall {
925
+ const status = String(row.status);
926
+ const visibility = String(row.visibility);
927
+
928
+ if (status !== "running" && status !== "completed" && status !== "failed") {
929
+ throw new AgentKitError("storage_error", `Invalid stored tool call status "${status}".`);
930
+ }
931
+
932
+ if (visibility !== "user" && visibility !== "internal") {
933
+ throw new AgentKitError("storage_error", `Invalid stored tool call visibility "${visibility}".`);
934
+ }
935
+
936
+ return {
937
+ id: String(row.id),
938
+ runId: String(row.run_id),
939
+ conversationId: row.conversation_id === null ? null : String(row.conversation_id),
940
+ toolName: String(row.tool_name),
941
+ visibility,
942
+ input: parseStoredJson(row.input_json),
943
+ output: row.output_json === null ? undefined : parseStoredJson(row.output_json),
944
+ rendered: row.rendered_json === null ? undefined : parseStoredJson(row.rendered_json),
945
+ status,
946
+ startedAt: String(row.started_at),
947
+ completedAt: row.completed_at === null ? null : String(row.completed_at),
948
+ errorMessage: row.error_message === null ? null : String(row.error_message),
949
+ };
950
+ }
951
+
952
+ function nullableNumber(value: unknown): number | null {
953
+ return value === null || value === undefined ? null : Number(value);
954
+ }
955
+
956
+ function parseStoredJson(value: unknown): unknown {
957
+ if (typeof value !== "string") {
958
+ return value;
959
+ }
960
+
961
+ return JSON.parse(value);
962
+ }
963
+
964
+ function normalizeRows(rows: SqliteRow[]): DatabaseRow[] {
965
+ return rows.map((row) =>
966
+ Object.fromEntries(
967
+ Object.entries(row).map(([key, value]) => {
968
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
969
+ return [key, value];
970
+ }
971
+
972
+ if (typeof value === "bigint") {
973
+ const numberValue = Number(value);
974
+ return [key, Number.isSafeInteger(numberValue) ? numberValue : value.toString()];
975
+ }
976
+
977
+ if (value instanceof Uint8Array) {
978
+ return [key, Buffer.from(value).toString("base64")];
979
+ }
980
+
981
+ return [key, String(value)];
982
+ }),
983
+ ),
984
+ );
985
+ }
986
+
987
+ function normalizeLastInsertRowid(value: number | bigint): number | string {
988
+ if (typeof value === "number") {
989
+ return value;
990
+ }
991
+
992
+ const numberValue = Number(value);
993
+ return Number.isSafeInteger(numberValue) ? numberValue : value.toString();
994
+ }
995
+
996
+ function splitSqlStatements(source: string): string[] {
997
+ return source
998
+ .split(";")
999
+ .map((statement) => statement.trim())
1000
+ .filter(Boolean);
1001
+ }
1002
+
1003
+ function serializeJson(value: unknown, secretValues: string[] = []): string {
1004
+ return redactSecrets(JSON.stringify(value), secretValues);
1005
+ }
1006
+
1007
+ function redactSecrets(value: string, secretValues?: string[]): string;
1008
+ function redactSecrets(value: null, secretValues?: string[]): null;
1009
+ function redactSecrets(value: string | null, secretValues?: string[]): string | null;
1010
+ function redactSecrets(value: string | null, secretValues: string[] = []): string | null {
1011
+ if (value === null) {
1012
+ return null;
1013
+ }
1014
+
1015
+ let redacted = value;
1016
+
1017
+ for (const secret of secretValues) {
1018
+ if (!secret) {
1019
+ continue;
1020
+ }
1021
+
1022
+ redacted = redacted.split(secret).join("[REDACTED]");
1023
+ }
1024
+
1025
+ return redactSecretPatterns(redacted);
1026
+ }
1027
+
1028
+ function redactSecretPatterns(value: string): string {
1029
+ return value
1030
+ .replace(/bot[0-9]{5,}:[A-Za-z0-9_-]{20,}/g, "bot[REDACTED]")
1031
+ .replace(/\b(?:sk|rk|pk|zap|meta|telegram|bearer)[-_A-Za-z0-9]{16,}\b/gi, "[REDACTED]")
1032
+ .replace(/\b[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\b/g, "[REDACTED]");
1033
+ }
1034
+
1035
+ function randomId(): string {
1036
+ return crypto.randomUUID();
1037
+ }
1038
+
1039
+ function translateSqliteError(error: unknown): never {
1040
+ if (isAgentKitError(error)) {
1041
+ throw error;
1042
+ }
1043
+
1044
+ if (isSqliteBusyError(error)) {
1045
+ throw new AgentKitError(
1046
+ "database_unavailable",
1047
+ "The local SQLite database is busy or locked. Wait for the active AgentKit run to finish, then retry.",
1048
+ { cause: error },
1049
+ );
1050
+ }
1051
+
1052
+ if (isSqliteConstraintError(error)) {
1053
+ throw new AgentKitError("constraint_conflict", "Database constraint conflict.", { cause: error });
1054
+ }
1055
+
1056
+ if (isSqliteSchemaError(error)) {
1057
+ throw new AgentKitError("database_schema_error", sqliteSchemaErrorMessage(error), { cause: error });
1058
+ }
1059
+
1060
+ if (isSqliteQueryError(error)) {
1061
+ throw new AgentKitError("database_query_invalid", "Database query is invalid.", { cause: error });
1062
+ }
1063
+
1064
+ throw error;
1065
+ }
1066
+
1067
+ function isAgentKitError(error: unknown): error is AgentKitError {
1068
+ return error instanceof AgentKitError;
1069
+ }
1070
+
1071
+ function isSqliteBusyError(error: unknown): boolean {
1072
+ if (!error || typeof error !== "object") {
1073
+ return false;
1074
+ }
1075
+
1076
+ const record = error as Record<string, unknown>;
1077
+ const code = typeof record.code === "string" ? record.code : "";
1078
+ const message = error instanceof Error ? error.message : String(error);
1079
+
1080
+ return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED" || /database is locked|database table is locked/i.test(message);
1081
+ }
1082
+
1083
+ function isSqliteConstraintError(error: unknown): boolean {
1084
+ const code = sqliteErrorCode(error);
1085
+ const message = error instanceof Error ? error.message : String(error);
1086
+
1087
+ return code.startsWith("SQLITE_CONSTRAINT") || /constraint failed|UNIQUE constraint|FOREIGN KEY constraint|CHECK constraint/i.test(message);
1088
+ }
1089
+
1090
+ function isSqliteSchemaError(error: unknown): boolean {
1091
+ const message = error instanceof Error ? error.message : String(error);
1092
+ return /no such table|no such column|has no column named|database schema has changed|malformed database schema/i.test(message);
1093
+ }
1094
+
1095
+ function isSqliteQueryError(error: unknown): boolean {
1096
+ const message = error instanceof Error ? error.message : String(error);
1097
+ return /syntax error|incomplete input|near ".+": syntax error|SQL logic error/i.test(message);
1098
+ }
1099
+
1100
+ function sqliteSchemaErrorMessage(error: unknown): string {
1101
+ const message = error instanceof Error ? error.message : String(error);
1102
+ return `Database schema error: ${message}`;
1103
+ }
1104
+
1105
+ function sqliteErrorCode(error: unknown): string {
1106
+ if (!error || typeof error !== "object") {
1107
+ return "";
1108
+ }
1109
+
1110
+ const code = (error as Record<string, unknown>).code;
1111
+ return typeof code === "string" ? code : "";
1112
+ }
1113
+
1114
+ function sqliteBusyTimeoutMs(): number {
1115
+ const value = Number(process.env.AGENTKIT_SQLITE_BUSY_TIMEOUT_MS ?? 5000);
1116
+
1117
+ return Number.isInteger(value) && value >= 0 ? value : 5000;
1118
+ }