@andreprado/agentkit 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +68 -6
- package/docs/guides/add-channel.md +189 -7
- package/docs/guides/add-knowledge.md +144 -0
- package/docs/guides/add-managed-composio.md +163 -0
- package/docs/guides/add-tool.md +1 -1
- package/docs/guides/channel-security.md +128 -32
- package/docs/guides/connect-discord.md +178 -0
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/connect-telegram.md +78 -1
- package/docs/guides/connect-whatsapp-evolution.md +121 -0
- package/docs/guides/connect-whatsapp-uazapi.md +126 -0
- package/docs/guides/connect-whatsapp-zapster.md +112 -8
- package/docs/guides/create-agent.md +45 -4
- package/docs/guides/debug-channel.md +147 -0
- package/docs/guides/improve-from-production.md +151 -0
- package/docs/guides/prepare-deploy.md +47 -17
- package/docs/guides/replay-production-traces.md +72 -0
- package/docs/guides/run-evals.md +147 -20
- package/docs/guides/security-rules.md +7 -6
- package/docs/guides/send-feedback.md +135 -0
- package/docs/guides/use-provider.md +27 -3
- package/docs/llms-full.txt +348 -55
- package/docs/llms.txt +62 -7
- package/package.json +2 -5
- package/src/cli/args.ts +57 -0
- package/src/cli/cloud-client.ts +377 -0
- package/src/cli/commands/channels.ts +1586 -0
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/commands/knowledge.ts +136 -0
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +535 -0
- package/src/cli/deploy-readiness.ts +481 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +236 -0
- package/src/cli/index.ts +1167 -1005
- package/src/cli/process.ts +31 -0
- package/src/cloud/artifact.ts +139 -0
- package/src/cloud/client.ts +80 -0
- package/src/cloud/contracts.ts +63 -0
- package/src/cloud/index.ts +3 -0
- package/src/create-project.ts +21 -6
- package/src/index.ts +517 -8
- package/src/providers/pi.ts +70 -16
- package/src/providers/test.ts +88 -1
- package/src/providers/types.ts +7 -0
- package/src/runtime/channel-buffer.ts +30 -0
- package/src/runtime/channel-test-harness.ts +21 -1
- package/src/runtime/channels/discord.ts +896 -0
- package/src/runtime/channels/generic-webhook.ts +225 -0
- package/src/runtime/channels/slack.ts +646 -0
- package/src/runtime/channels/telegram.ts +466 -23
- package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1327 -0
- package/src/runtime/channels/whatsapp-zapster.ts +677 -40
- package/src/runtime/channels.ts +87 -4
- package/src/runtime/chat.ts +130 -38
- package/src/runtime/config.ts +519 -19
- package/src/runtime/core/manifest.ts +103 -5
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/database.ts +93 -2
- package/src/runtime/db-commands.ts +9 -0
- package/src/runtime/deploy-readiness.ts +46 -4
- package/src/runtime/deploy.ts +1 -1
- package/src/runtime/dev-server.ts +779 -45
- package/src/runtime/env.ts +8 -3
- package/src/runtime/evals.ts +589 -43
- package/src/runtime/improve.ts +868 -0
- package/src/runtime/inspect.ts +194 -4
- package/src/runtime/integrations/composio.ts +423 -0
- package/src/runtime/knowledge/chunk.ts +333 -0
- package/src/runtime/knowledge/config.ts +135 -0
- package/src/runtime/knowledge/embeddings.ts +133 -0
- package/src/runtime/knowledge/ingest.ts +521 -0
- package/src/runtime/knowledge/prompt-policy.ts +30 -0
- package/src/runtime/knowledge/retrieve.ts +303 -0
- package/src/runtime/knowledge/schema.ts +100 -0
- package/src/runtime/knowledge/tool.ts +64 -0
- package/src/runtime/knowledge/vector.ts +258 -0
- package/src/runtime/prompt-context.ts +141 -0
- package/src/runtime/runtime-contract.ts +86 -8
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/spec.ts +152 -0
- package/src/runtime/sync.ts +144 -0
- package/src/runtime/targets/cloudflare/build.ts +1468 -203
- package/src/runtime/targets/container/server.ts +1 -1
- package/src/runtime/targets/vps/deploy.ts +26 -9
- package/src/runtime/tool-runner.ts +9 -1
- package/src/runtime/tools.ts +128 -2
- package/src/runtime/traces.ts +41 -0
- package/src/runtime/transcription.ts +483 -0
- package/src/storage/sqlite.ts +149 -3
- package/src/templates/blank.ts +76 -17
- package/src/templates/dentista.ts +1011 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/skills/agentkit-build-agent/SKILL.md +52 -0
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +21 -0
- package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
- package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
- package/src/templates/skills/agentkit-capsule/SKILL.md +70 -0
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +127 -0
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +65 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +66 -0
- package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +72 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +61 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +77 -0
- package/src/templates/skills/agentkit-database/SKILL.md +45 -0
- package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
- package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +50 -0
- package/src/templates/skills/agentkit-evals/SKILL.md +109 -0
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +29 -0
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +18 -0
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +18 -0
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +27 -0
- package/src/templates/skills/agentkit-improve/SKILL.md +86 -0
- package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
- package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
- package/src/templates/skills/agentkit-integrations/SKILL.md +76 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +43 -0
- package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
- package/src/templates/skills/agentkit-prompts/SKILL.md +47 -0
- package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
- package/src/templates/skills/agentkit-provider/SKILL.md +60 -0
- package/src/templates/skills/agentkit-security/SKILL.md +56 -0
- package/src/templates/skills/agentkit-tools/SKILL.md +37 -0
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
- package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +76 -0
- package/src/templates/support.ts +77 -18
- package/docs/guides/channels-production-handoff.md +0 -99
- package/docs/portable-deploy-release-checklist.md +0 -41
- package/src/runtime/targets/cloudflare/deploy.ts +0 -5475
|
@@ -8,6 +8,8 @@ import { build as esbuild, type Plugin } from "esbuild";
|
|
|
8
8
|
import { loadAgentCapsule, type LoadedAgentCapsule } from "../../config";
|
|
9
9
|
import { buildSharedAgentManifest, type ManifestSchema, type SharedAgentManifest } from "../../core/manifest";
|
|
10
10
|
import { AgentKitError } from "../../errors";
|
|
11
|
+
import { resolveKnowledgeConfig } from "../../knowledge/config";
|
|
12
|
+
import { KNOWLEDGE_SCHEMA_SQL } from "../../knowledge/schema";
|
|
11
13
|
|
|
12
14
|
export type BuildTarget = "cloudflare";
|
|
13
15
|
|
|
@@ -30,10 +32,14 @@ export type AgentManifest = {
|
|
|
30
32
|
runtime: "edge";
|
|
31
33
|
target: "cloudflare";
|
|
32
34
|
provider: SharedAgentManifest["provider"];
|
|
35
|
+
timeZone: SharedAgentManifest["timeZone"];
|
|
33
36
|
prompt: SharedAgentManifest["prompt"];
|
|
34
37
|
secrets: SharedAgentManifest["secrets"];
|
|
35
38
|
tools: SharedAgentManifest["tools"];
|
|
39
|
+
integrations: SharedAgentManifest["integrations"];
|
|
36
40
|
channels: SharedAgentManifest["channels"];
|
|
41
|
+
transcription: SharedAgentManifest["transcription"];
|
|
42
|
+
knowledge: SharedAgentManifest["knowledge"];
|
|
37
43
|
access: SharedAgentManifest["access"];
|
|
38
44
|
storage: {
|
|
39
45
|
driver: "agentkit";
|
|
@@ -145,6 +151,14 @@ function validateCloudflareBuild(capsule: LoadedAgentCapsule): void {
|
|
|
145
151
|
);
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
const knowledge = resolveKnowledgeConfig(capsule.config);
|
|
155
|
+
|
|
156
|
+
if (knowledge.enabled && capsule.config.storage.database?.driver !== "turso") {
|
|
157
|
+
throw new AgentKitError(
|
|
158
|
+
"build_knowledge_database",
|
|
159
|
+
'Cloudflare Knowledge deploy requires storage.database.driver: "turso" so chunks and retrieval state are available to the hosted runtime.',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
function buildWarnings(capsule: LoadedAgentCapsule): string[] {
|
|
@@ -181,10 +195,14 @@ function buildManifest(
|
|
|
181
195
|
runtime: "edge",
|
|
182
196
|
target: "cloudflare",
|
|
183
197
|
provider: shared.provider,
|
|
198
|
+
timeZone: shared.timeZone,
|
|
184
199
|
prompt: shared.prompt,
|
|
185
200
|
secrets: shared.secrets,
|
|
186
201
|
tools: shared.tools,
|
|
202
|
+
integrations: shared.integrations,
|
|
187
203
|
channels: shared.channels,
|
|
204
|
+
transcription: shared.transcription,
|
|
205
|
+
knowledge: shared.knowledge,
|
|
188
206
|
access: shared.access,
|
|
189
207
|
storage: {
|
|
190
208
|
driver: "agentkit",
|
|
@@ -210,12 +228,26 @@ function buildCloudflareFilesManifest(files: SharedAgentManifest["storage"]["fil
|
|
|
210
228
|
|
|
211
229
|
async function loadTursoSchema(capsule: LoadedAgentCapsule): Promise<ManifestSchema | null> {
|
|
212
230
|
const database = capsule.config.storage.database;
|
|
231
|
+
const knowledge = resolveKnowledgeConfig(capsule.config);
|
|
232
|
+
const sources: string[] = [];
|
|
213
233
|
|
|
214
|
-
if (!database || database.driver !== "turso"
|
|
234
|
+
if (!database || database.driver !== "turso") {
|
|
215
235
|
return null;
|
|
216
236
|
}
|
|
217
237
|
|
|
218
|
-
|
|
238
|
+
if (database.schema) {
|
|
239
|
+
sources.push(await readFile(resolve(capsule.root, database.schema), "utf8"));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (knowledge.enabled) {
|
|
243
|
+
sources.push(KNOWLEDGE_SCHEMA_SQL);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (sources.length === 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const source = sources.map((part) => part.trim()).filter(Boolean).join("\n\n") + "\n";
|
|
219
251
|
const digest = sha256(source);
|
|
220
252
|
return { source, sha256: digest };
|
|
221
253
|
}
|
|
@@ -288,8 +320,8 @@ async function listSourceFiles(root: string): Promise<string[]> {
|
|
|
288
320
|
function findImportSpecifiers(source: string): string[] {
|
|
289
321
|
const specifiers: string[] = [];
|
|
290
322
|
const patterns = [
|
|
291
|
-
/\bimport\s+(
|
|
292
|
-
/\bexport\s+[^"'()]*?\s+from\s+["']([^"']+)["']/g,
|
|
323
|
+
/\bimport\s+(?!type\b)(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
|
|
324
|
+
/\bexport\s+(?!type\b)[^"'()]*?\s+from\s+["']([^"']+)["']/g,
|
|
293
325
|
/\brequire\(\s*["']([^"']+)["']\s*\)/g,
|
|
294
326
|
/\bimport\(\s*["']([^"']+)["']\s*\)/g,
|
|
295
327
|
];
|
|
@@ -353,6 +385,26 @@ export function defineTool(tool) {
|
|
|
353
385
|
return tool;
|
|
354
386
|
}
|
|
355
387
|
|
|
388
|
+
export function composioManaged(input) {
|
|
389
|
+
const name = normalizeIntegrationName(input.name ?? "composio", "composio integration name");
|
|
390
|
+
const toolkits = normalizeToolkitList(input.toolkits, "composioManaged.toolkits");
|
|
391
|
+
const tools = input.tools ? normalizeComposioToolMap(input.tools, toolkits) : undefined;
|
|
392
|
+
const tags = input.tags ? normalizeIntegrationTags(input.tags) : undefined;
|
|
393
|
+
const confirmExternalWrites =
|
|
394
|
+
input.confirmExternalWrites === undefined ? undefined : Boolean(input.confirmExternalWrites);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
type: "composio",
|
|
398
|
+
provider: "composio",
|
|
399
|
+
mode: "managed",
|
|
400
|
+
name,
|
|
401
|
+
toolkits,
|
|
402
|
+
...(tools ? { tools } : {}),
|
|
403
|
+
...(tags ? { tags } : {}),
|
|
404
|
+
...(confirmExternalWrites === undefined ? {} : { confirmExternalWrites }),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
356
408
|
export function websiteChannel(input) {
|
|
357
409
|
return {
|
|
358
410
|
type: "website",
|
|
@@ -361,6 +413,7 @@ export function websiteChannel(input) {
|
|
|
361
413
|
access: input.access ?? { mode: "token" },
|
|
362
414
|
secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"], "website channel"),
|
|
363
415
|
...(input.limits ? { limits: input.limits } : {}),
|
|
416
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "website channel") } : {}),
|
|
364
417
|
};
|
|
365
418
|
}
|
|
366
419
|
|
|
@@ -372,6 +425,7 @@ export function telegramChannel(input) {
|
|
|
372
425
|
secrets: normalizeChannelSecrets(input.secrets ?? ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"], "telegram channel"),
|
|
373
426
|
allowedUpdates: input.allowedUpdates ?? ["message"],
|
|
374
427
|
...(input.limits ? { limits: input.limits } : {}),
|
|
428
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "telegram channel") } : {}),
|
|
375
429
|
};
|
|
376
430
|
}
|
|
377
431
|
|
|
@@ -382,12 +436,54 @@ export function whatsappChannel(input) {
|
|
|
382
436
|
name: input.name,
|
|
383
437
|
secrets: normalizeChannelSecrets(input.secrets ?? defaultWhatsappChannelSecrets(input.provider), \`\${input.provider} whatsapp channel\`),
|
|
384
438
|
...(input.limits ? { limits: input.limits } : {}),
|
|
439
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, \`\${input.provider} whatsapp channel\`) } : {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function discordChannel(input) {
|
|
444
|
+
return {
|
|
445
|
+
type: "discord",
|
|
446
|
+
provider: "discord",
|
|
447
|
+
name: input.name,
|
|
448
|
+
secrets: normalizeChannelSecrets(input.secrets ?? ["DISCORD_PUBLIC_KEY"], "discord channel"),
|
|
449
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
450
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "discord channel") } : {}),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function slackChannel(input) {
|
|
455
|
+
return {
|
|
456
|
+
type: "slack",
|
|
457
|
+
provider: "slack",
|
|
458
|
+
name: input.name,
|
|
459
|
+
secrets: normalizeChannelSecrets(input.secrets ?? ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], "slack channel"),
|
|
460
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
461
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "slack channel") } : {}),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function webhookChannel(input) {
|
|
466
|
+
return {
|
|
467
|
+
type: "webhook",
|
|
468
|
+
provider: "generic",
|
|
469
|
+
name: input.name,
|
|
470
|
+
secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBHOOK_SECRET"], "generic webhook channel"),
|
|
471
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
472
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "generic webhook channel") } : {}),
|
|
385
473
|
};
|
|
386
474
|
}
|
|
387
475
|
|
|
388
476
|
function defaultWhatsappChannelSecrets(provider) {
|
|
389
477
|
if (provider === "zapster") {
|
|
390
|
-
return ["ZAPSTER_API_KEY", "
|
|
478
|
+
return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (provider === "uazapi") {
|
|
482
|
+
return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN"];
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (provider === "evolution") {
|
|
486
|
+
return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
|
|
391
487
|
}
|
|
392
488
|
|
|
393
489
|
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
@@ -406,6 +502,135 @@ function normalizeChannelSecrets(secrets, context) {
|
|
|
406
502
|
|
|
407
503
|
return Array.from(uniqueSecrets).sort();
|
|
408
504
|
}
|
|
505
|
+
|
|
506
|
+
function normalizeIntegrationName(value, context) {
|
|
507
|
+
const name = value.trim();
|
|
508
|
+
|
|
509
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
510
|
+
throw new Error(\`\${context} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return name;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function normalizeToolkitList(values, context) {
|
|
517
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
518
|
+
throw new Error(\`\${context} must include at least one toolkit slug.\`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const toolkits = new Set();
|
|
522
|
+
|
|
523
|
+
for (const value of values) {
|
|
524
|
+
if (typeof value !== "string") {
|
|
525
|
+
throw new Error(\`\${context} entries must be toolkit slug strings.\`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const toolkit = value.trim().toLowerCase();
|
|
529
|
+
|
|
530
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(toolkit)) {
|
|
531
|
+
throw new Error(\`\${context} entries must start with a letter and contain only letters, numbers, underscores, and hyphens.\`);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
toolkits.add(toolkit);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return Array.from(toolkits).sort();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function normalizeComposioToolMap(values, toolkits) {
|
|
541
|
+
const toolkitSet = new Set(toolkits);
|
|
542
|
+
const normalized = {};
|
|
543
|
+
|
|
544
|
+
for (const [rawToolkit, rawTools] of Object.entries(values)) {
|
|
545
|
+
const toolkit = rawToolkit.trim().toLowerCase();
|
|
546
|
+
|
|
547
|
+
if (!toolkitSet.has(toolkit)) {
|
|
548
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} must reference a toolkit listed in composioManaged.toolkits.\`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
552
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} must include at least one Composio action slug.\`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const tools = new Set();
|
|
556
|
+
|
|
557
|
+
for (const rawTool of rawTools) {
|
|
558
|
+
if (typeof rawTool !== "string") {
|
|
559
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be Composio action slug strings.\`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const tool = rawTool.trim().toUpperCase();
|
|
563
|
+
|
|
564
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(tool)) {
|
|
565
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be uppercase Composio action slugs.\`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
tools.add(tool);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
normalized[toolkit] = Array.from(tools).sort();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function normalizeIntegrationTags(tags) {
|
|
578
|
+
const normalized = {};
|
|
579
|
+
|
|
580
|
+
if (tags.enable !== undefined) {
|
|
581
|
+
normalized.enable = normalizeTagList(tags.enable, "composioManaged.tags.enable");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (tags.disable !== undefined) {
|
|
585
|
+
normalized.disable = normalizeTagList(tags.disable, "composioManaged.tags.disable");
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return normalized;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function normalizeTagList(values, context) {
|
|
592
|
+
if (!Array.isArray(values)) {
|
|
593
|
+
throw new Error(\`\${context} must be an array of tag strings.\`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return Array.from(new Set(values.map((value) => {
|
|
597
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
598
|
+
throw new Error(\`\${context} entries must be non-empty strings.\`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return value.trim();
|
|
602
|
+
}))).sort();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function normalizeChannelBuffer(input, context) {
|
|
606
|
+
if (input.mode === "off") {
|
|
607
|
+
return { mode: "off" };
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (input.mode !== undefined && input.mode !== "debounce") {
|
|
611
|
+
throw new Error(\`\${context} buffer.mode must be "off" or "debounce".\`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const buffer = {
|
|
615
|
+
mode: "debounce",
|
|
616
|
+
quietWindowMs: input.quietWindowMs ?? 2500,
|
|
617
|
+
maxWaitMs: input.maxWaitMs ?? 12000,
|
|
618
|
+
maxMessages: input.maxMessages ?? 20,
|
|
619
|
+
maxChars: input.maxChars ?? 8000,
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
for (const field of ["quietWindowMs", "maxWaitMs", "maxMessages", "maxChars"]) {
|
|
623
|
+
if (!Number.isInteger(buffer[field]) || buffer[field] <= 0) {
|
|
624
|
+
throw new Error(\`\${context} buffer.\${field} must be a positive integer.\`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (buffer.maxWaitMs < buffer.quietWindowMs) {
|
|
629
|
+
throw new Error(\`\${context} buffer.maxWaitMs must be greater than or equal to buffer.quietWindowMs.\`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return buffer;
|
|
633
|
+
}
|
|
409
634
|
`;
|
|
410
635
|
}
|
|
411
636
|
|
|
@@ -479,14 +704,18 @@ function renderCloudflareWorker(input: { manifest: AgentManifest; instructions:
|
|
|
479
704
|
const instructionsJson = JSON.stringify(input.instructions);
|
|
480
705
|
const configImportPath = JSON.stringify(input.configPath);
|
|
481
706
|
const providerImportPath = JSON.stringify(resolve(moduleDir, "../../../providers/index.ts"));
|
|
707
|
+
const composioIntegrationImportPath = JSON.stringify(resolve(moduleDir, "../../integrations/composio.ts"));
|
|
708
|
+
const promptContextImportPath = JSON.stringify(resolve(moduleDir, "../../prompt-context.ts"));
|
|
482
709
|
|
|
483
710
|
return `import { DurableObject } from "cloudflare:workers";
|
|
484
711
|
import agentConfig from ${configImportPath};
|
|
485
712
|
import { getProviderAdapter } from ${providerImportPath};
|
|
713
|
+
import { MANAGED_COMPOSIO_USER_ID_BINDING, createManagedComposioTools } from ${composioIntegrationImportPath};
|
|
714
|
+
import { appendRuntimePromptContext, buildRuntimeClockContext } from ${promptContextImportPath};
|
|
486
715
|
|
|
487
716
|
const manifest = ${manifestJson};
|
|
488
717
|
const instructions = ${instructionsJson};
|
|
489
|
-
const
|
|
718
|
+
const hostedKnowledgeTool = manifest.knowledge.enabled ? createHostedKnowledgeSearchTool() : null;
|
|
490
719
|
const DEFAULT_TOOL_TIMEOUT_MS = 30000;
|
|
491
720
|
|
|
492
721
|
export class AgentKitConversationStore extends DurableObject {
|
|
@@ -516,6 +745,10 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
516
745
|
status TEXT NOT NULL,
|
|
517
746
|
error_code TEXT,
|
|
518
747
|
error_message TEXT,
|
|
748
|
+
input_tokens INTEGER,
|
|
749
|
+
output_tokens INTEGER,
|
|
750
|
+
total_tokens INTEGER,
|
|
751
|
+
cost_json TEXT,
|
|
519
752
|
created_at TEXT NOT NULL,
|
|
520
753
|
completed_at TEXT
|
|
521
754
|
);
|
|
@@ -528,11 +761,29 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
528
761
|
status TEXT NOT NULL,
|
|
529
762
|
input_json TEXT NOT NULL,
|
|
530
763
|
output_json TEXT,
|
|
764
|
+
rendered_json TEXT,
|
|
531
765
|
error_message TEXT,
|
|
532
766
|
created_at TEXT NOT NULL,
|
|
533
767
|
completed_at TEXT
|
|
534
768
|
);
|
|
535
769
|
\`);
|
|
770
|
+
try {
|
|
771
|
+
this.sql.exec("ALTER TABLE tool_calls ADD COLUMN rendered_json TEXT");
|
|
772
|
+
} catch {
|
|
773
|
+
// Existing Durable Object tables either already have this column or do not need migration.
|
|
774
|
+
}
|
|
775
|
+
for (const statement of [
|
|
776
|
+
"ALTER TABLE runs ADD COLUMN input_tokens INTEGER",
|
|
777
|
+
"ALTER TABLE runs ADD COLUMN output_tokens INTEGER",
|
|
778
|
+
"ALTER TABLE runs ADD COLUMN total_tokens INTEGER",
|
|
779
|
+
"ALTER TABLE runs ADD COLUMN cost_json TEXT",
|
|
780
|
+
]) {
|
|
781
|
+
try {
|
|
782
|
+
this.sql.exec(statement);
|
|
783
|
+
} catch {
|
|
784
|
+
// Existing Durable Object tables either already have this column or do not need migration.
|
|
785
|
+
}
|
|
786
|
+
}
|
|
536
787
|
}
|
|
537
788
|
|
|
538
789
|
async fetch(request) {
|
|
@@ -542,9 +793,173 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
542
793
|
return this.persistMessages(await request.json());
|
|
543
794
|
}
|
|
544
795
|
|
|
796
|
+
if (request.method === "GET" && url.pathname === "/messages") {
|
|
797
|
+
return this.getMessages(url.searchParams.get("conversationId"));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (request.method === "GET" && url.pathname === "/conversations") {
|
|
801
|
+
return this.listConversations();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const conversationTraceMatch = url.pathname.match(/^\\/conversations\\/([^/]+)\\/trace$/);
|
|
805
|
+
|
|
806
|
+
if (request.method === "GET" && conversationTraceMatch) {
|
|
807
|
+
return this.getConversationTrace(decodeURIComponent(conversationTraceMatch[1] || ""));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const conversationMatch = url.pathname.match(/^\\/conversations\\/([^/]+)$/);
|
|
811
|
+
|
|
812
|
+
if (request.method === "GET" && conversationMatch) {
|
|
813
|
+
return this.getConversation(decodeURIComponent(conversationMatch[1] || ""));
|
|
814
|
+
}
|
|
815
|
+
|
|
545
816
|
return Response.json({ error: { code: "not_found", message: "Unknown Durable Object route." } }, { status: 404 });
|
|
546
817
|
}
|
|
547
818
|
|
|
819
|
+
getMessages(conversationId) {
|
|
820
|
+
if (!conversationId) {
|
|
821
|
+
return Response.json({ messages: [] });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const rows = this.sql.exec(
|
|
825
|
+
"SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
826
|
+
String(conversationId),
|
|
827
|
+
).toArray();
|
|
828
|
+
|
|
829
|
+
return Response.json({
|
|
830
|
+
messages: rows
|
|
831
|
+
.filter((row) => row.role === "user" || row.role === "assistant")
|
|
832
|
+
.map((row) => ({
|
|
833
|
+
role: String(row.role),
|
|
834
|
+
content: String(row.content),
|
|
835
|
+
})),
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
listConversations() {
|
|
840
|
+
const rows = this.sql.exec(\`
|
|
841
|
+
SELECT conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at, COUNT(messages.id) AS message_count
|
|
842
|
+
FROM conversations
|
|
843
|
+
LEFT JOIN messages ON messages.conversation_id = conversations.id
|
|
844
|
+
GROUP BY conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at
|
|
845
|
+
ORDER BY conversations.updated_at DESC
|
|
846
|
+
\`).toArray();
|
|
847
|
+
|
|
848
|
+
return Response.json({
|
|
849
|
+
conversations: rows.map((row) => ({
|
|
850
|
+
id: String(row.id),
|
|
851
|
+
agentName: String(row.agent_name),
|
|
852
|
+
title: row.title === null || row.title === undefined ? null : String(row.title),
|
|
853
|
+
createdAt: String(row.created_at),
|
|
854
|
+
updatedAt: String(row.updated_at),
|
|
855
|
+
messageCount: Number(row.message_count || 0),
|
|
856
|
+
})),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
getConversation(conversationId) {
|
|
861
|
+
const conversation = this.readConversation(conversationId);
|
|
862
|
+
|
|
863
|
+
if (!conversation) {
|
|
864
|
+
return Response.json(
|
|
865
|
+
{ error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
|
|
866
|
+
{ status: 404 },
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return Response.json(conversation);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
getConversationTrace(conversationId) {
|
|
874
|
+
const conversation = this.readConversation(conversationId);
|
|
875
|
+
|
|
876
|
+
if (!conversation) {
|
|
877
|
+
return Response.json(
|
|
878
|
+
{ error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
|
|
879
|
+
{ status: 404 },
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const runs = this.sql.exec(
|
|
884
|
+
"SELECT id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
885
|
+
conversationId,
|
|
886
|
+
).toArray();
|
|
887
|
+
|
|
888
|
+
return Response.json({
|
|
889
|
+
...conversation,
|
|
890
|
+
runs: runs.map((run) => ({
|
|
891
|
+
id: String(run.id),
|
|
892
|
+
conversationId: String(run.conversation_id),
|
|
893
|
+
provider: String(run.provider),
|
|
894
|
+
model: String(run.model),
|
|
895
|
+
status: String(run.status),
|
|
896
|
+
errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
|
|
897
|
+
errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
|
|
898
|
+
usage: readStoredUsage(run),
|
|
899
|
+
startedAt: String(run.created_at),
|
|
900
|
+
completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
|
|
901
|
+
toolCalls: this.readToolCalls(String(run.id)),
|
|
902
|
+
})),
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
readConversation(conversationId) {
|
|
907
|
+
if (!conversationId) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const row = this.sql.exec(
|
|
912
|
+
"SELECT id, agent_name, title, created_at, updated_at FROM conversations WHERE id = ?",
|
|
913
|
+
conversationId,
|
|
914
|
+
).one();
|
|
915
|
+
|
|
916
|
+
if (!row) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const messages = this.sql.exec(
|
|
921
|
+
"SELECT id, conversation_id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
922
|
+
conversationId,
|
|
923
|
+
).toArray();
|
|
924
|
+
|
|
925
|
+
return {
|
|
926
|
+
id: String(row.id),
|
|
927
|
+
agentName: String(row.agent_name),
|
|
928
|
+
title: row.title === null || row.title === undefined ? null : String(row.title),
|
|
929
|
+
createdAt: String(row.created_at),
|
|
930
|
+
updatedAt: String(row.updated_at),
|
|
931
|
+
messages: messages.map((message) => ({
|
|
932
|
+
id: String(message.id),
|
|
933
|
+
conversationId: String(message.conversation_id),
|
|
934
|
+
role: String(message.role),
|
|
935
|
+
content: String(message.content),
|
|
936
|
+
createdAt: String(message.created_at),
|
|
937
|
+
})),
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
readToolCalls(runId) {
|
|
942
|
+
const rows = this.sql.exec(
|
|
943
|
+
"SELECT id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, rendered_json, error_message, created_at, completed_at FROM tool_calls WHERE run_id = ? ORDER BY rowid ASC",
|
|
944
|
+
runId,
|
|
945
|
+
).toArray();
|
|
946
|
+
|
|
947
|
+
return rows.map((row) => ({
|
|
948
|
+
id: String(row.id),
|
|
949
|
+
runId: String(row.run_id),
|
|
950
|
+
conversationId: String(row.conversation_id),
|
|
951
|
+
name: String(row.tool_name),
|
|
952
|
+
visibility: String(row.visibility),
|
|
953
|
+
status: String(row.status),
|
|
954
|
+
input: parseStoredJson(row.input_json, {}),
|
|
955
|
+
output: row.output_json === null || row.output_json === undefined ? undefined : parseStoredJson(row.output_json, null),
|
|
956
|
+
rendered: row.rendered_json === null || row.rendered_json === undefined ? undefined : parseStoredJson(row.rendered_json, null),
|
|
957
|
+
errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
|
|
958
|
+
createdAt: String(row.created_at),
|
|
959
|
+
completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
|
|
548
963
|
persistMessages(input) {
|
|
549
964
|
const now = new Date().toISOString();
|
|
550
965
|
const conversationId = String(input.conversationId || crypto.randomUUID());
|
|
@@ -552,6 +967,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
552
967
|
const userMessageId = crypto.randomUUID();
|
|
553
968
|
const assistantMessageId = crypto.randomUUID();
|
|
554
969
|
const title = titleFromMessage(String(input.userMessage || ""));
|
|
970
|
+
const usage = normalizeRuntimeUsage(input.usage);
|
|
555
971
|
|
|
556
972
|
this.sql.exec(
|
|
557
973
|
"INSERT OR IGNORE INTO conversations (id, agent_name, title, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
@@ -571,7 +987,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
571
987
|
now,
|
|
572
988
|
);
|
|
573
989
|
this.sql.exec(
|
|
574
|
-
"INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
990
|
+
"INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
575
991
|
runId,
|
|
576
992
|
conversationId,
|
|
577
993
|
manifest.provider.name,
|
|
@@ -579,6 +995,10 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
579
995
|
String(input.status || "completed"),
|
|
580
996
|
input.errorCode ? String(input.errorCode) : null,
|
|
581
997
|
input.errorMessage ? String(input.errorMessage) : null,
|
|
998
|
+
usage.inputTokens ?? null,
|
|
999
|
+
usage.outputTokens ?? null,
|
|
1000
|
+
usage.totalTokens ?? null,
|
|
1001
|
+
usage.cost && Object.keys(usage.cost).length > 0 ? JSON.stringify(usage.cost) : null,
|
|
582
1002
|
now,
|
|
583
1003
|
now,
|
|
584
1004
|
);
|
|
@@ -596,7 +1016,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
596
1016
|
|
|
597
1017
|
for (const toolCall of input.toolCalls || []) {
|
|
598
1018
|
this.sql.exec(
|
|
599
|
-
"INSERT INTO tool_calls (id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1019
|
+
"INSERT INTO tool_calls (id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, rendered_json, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
600
1020
|
String(toolCall.id || crypto.randomUUID()),
|
|
601
1021
|
runId,
|
|
602
1022
|
conversationId,
|
|
@@ -605,6 +1025,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
605
1025
|
String(toolCall.status || "completed"),
|
|
606
1026
|
JSON.stringify(toolCall.input ?? {}),
|
|
607
1027
|
toolCall.output === undefined ? null : JSON.stringify(toolCall.output),
|
|
1028
|
+
toolCall.rendered === undefined ? null : JSON.stringify(toolCall.rendered),
|
|
608
1029
|
toolCall.errorMessage ? String(toolCall.errorMessage) : null,
|
|
609
1030
|
now,
|
|
610
1031
|
now,
|
|
@@ -621,7 +1042,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
621
1042
|
}
|
|
622
1043
|
|
|
623
1044
|
export default {
|
|
624
|
-
async fetch(request, env) {
|
|
1045
|
+
async fetch(request, env, ctx) {
|
|
625
1046
|
const url = new URL(request.url);
|
|
626
1047
|
|
|
627
1048
|
if (request.method === "GET" && url.pathname === "/_agentkit") {
|
|
@@ -631,19 +1052,23 @@ export default {
|
|
|
631
1052
|
return auth;
|
|
632
1053
|
}
|
|
633
1054
|
|
|
1055
|
+
const hostedTools = createHostedTools(env);
|
|
1056
|
+
|
|
634
1057
|
return Response.json({
|
|
635
1058
|
agent: manifest.name,
|
|
636
1059
|
runtime: manifest.runtime,
|
|
637
1060
|
target: manifest.target,
|
|
638
1061
|
provider: manifest.provider,
|
|
639
1062
|
prompt: manifest.prompt,
|
|
640
|
-
tools:
|
|
1063
|
+
tools: hostedTools.map((tool) => tool.name),
|
|
1064
|
+
integrations: hostedIntegrations(env),
|
|
641
1065
|
channels: manifest.channels.map((channel) => ({
|
|
642
1066
|
name: channel.name,
|
|
643
1067
|
type: channel.type,
|
|
644
1068
|
provider: channel.provider,
|
|
645
1069
|
secrets: channel.secrets,
|
|
646
1070
|
})),
|
|
1071
|
+
knowledge: manifest.knowledge,
|
|
647
1072
|
access: manifest.access,
|
|
648
1073
|
storage: manifest.storage,
|
|
649
1074
|
database: inspectHostedDatabase(),
|
|
@@ -654,6 +1079,15 @@ export default {
|
|
|
654
1079
|
}
|
|
655
1080
|
|
|
656
1081
|
if (request.method === "GET" && url.pathname === "/") {
|
|
1082
|
+
const auth = await authorizeDeployAccess(request, env, {}, {
|
|
1083
|
+
notConfiguredCode: "cloud_access_not_configured",
|
|
1084
|
+
notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
if (auth) {
|
|
1088
|
+
return auth;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
657
1091
|
return Response.json({
|
|
658
1092
|
agent: manifest.name,
|
|
659
1093
|
status: "ready",
|
|
@@ -662,20 +1096,45 @@ export default {
|
|
|
662
1096
|
});
|
|
663
1097
|
}
|
|
664
1098
|
|
|
1099
|
+
if (request.method === "GET" && url.pathname === "/health") {
|
|
1100
|
+
const auth = await authorizeDeployAccess(request, env, {}, {
|
|
1101
|
+
notConfiguredCode: "cloud_access_not_configured",
|
|
1102
|
+
notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (auth) {
|
|
1106
|
+
return auth;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return Response.json({ ok: true });
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
if (isHostedChannelWebhookPath(url.pathname)) {
|
|
1113
|
+
return forwardHostedChannelWebhook(request, env, url);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
665
1116
|
if (request.method === "POST" && (url.pathname === "/chat" || url.pathname === "/v1/chat")) {
|
|
666
|
-
return handleChat(request, env);
|
|
1117
|
+
return handleChat(request, env, ctx);
|
|
667
1118
|
}
|
|
668
1119
|
|
|
669
|
-
if (request.method === "
|
|
670
|
-
return
|
|
1120
|
+
if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
|
|
1121
|
+
return handleConversationRead(request, env, "list");
|
|
671
1122
|
}
|
|
672
1123
|
|
|
673
|
-
|
|
674
|
-
|
|
1124
|
+
const conversationTraceMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)\\/trace$/);
|
|
1125
|
+
|
|
1126
|
+
if (request.method === "GET" && conversationTraceMatch) {
|
|
1127
|
+
return handleConversationRead(request, env, "trace", decodeURIComponent(conversationTraceMatch[1] || ""));
|
|
675
1128
|
}
|
|
676
1129
|
|
|
677
|
-
|
|
678
|
-
|
|
1130
|
+
const conversationMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)$/);
|
|
1131
|
+
|
|
1132
|
+
if (request.method === "GET" && conversationMatch) {
|
|
1133
|
+
return handleConversationRead(request, env, "show", decodeURIComponent(conversationMatch[1] || ""));
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
|
|
1137
|
+
return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
|
|
679
1138
|
}
|
|
680
1139
|
|
|
681
1140
|
if (url.pathname.startsWith("/files/")) {
|
|
@@ -686,69 +1145,47 @@ export default {
|
|
|
686
1145
|
},
|
|
687
1146
|
};
|
|
688
1147
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const configError = requireTurso(env);
|
|
697
|
-
|
|
698
|
-
if (configError) {
|
|
699
|
-
return configError;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const response = await fetch(toTursoBaseUrl(env.TURSO_DATABASE_URL) + "/health", {
|
|
703
|
-
headers: {
|
|
704
|
-
Authorization: \`Bearer \${env.TURSO_AUTH_TOKEN}\`,
|
|
705
|
-
},
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
return Response.json({
|
|
709
|
-
driver: "turso",
|
|
710
|
-
ok: response.ok,
|
|
711
|
-
status: response.status,
|
|
712
|
-
}, { status: response.ok ? 200 : 502 });
|
|
1148
|
+
function isHostedChannelWebhookPath(pathname) {
|
|
1149
|
+
return (
|
|
1150
|
+
/^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta|uazapi|evolution)|discord\\/discord|slack\\/slack|webhook\\/generic)\\/webhook$/.test(pathname) ||
|
|
1151
|
+
/^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack|webhook)\\/webhook$/.test(pathname) ||
|
|
1152
|
+
/^\\/channels\\/[^/]+\\/webhook$/.test(pathname) ||
|
|
1153
|
+
/^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta|uazapi|evolution)\\/webhook$/.test(pathname)
|
|
1154
|
+
);
|
|
713
1155
|
}
|
|
714
1156
|
|
|
715
|
-
async function
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (auth) {
|
|
719
|
-
return auth;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const configError = requireTurso(env);
|
|
723
|
-
|
|
724
|
-
if (configError) {
|
|
725
|
-
return configError;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
let body;
|
|
729
|
-
|
|
730
|
-
try {
|
|
731
|
-
body = await request.json();
|
|
732
|
-
} catch {
|
|
1157
|
+
async function forwardHostedChannelWebhook(request, env, url) {
|
|
1158
|
+
if (!env.AGENTKIT_CLOUD_API_URL) {
|
|
733
1159
|
return Response.json(
|
|
734
|
-
{
|
|
735
|
-
|
|
1160
|
+
{
|
|
1161
|
+
error: {
|
|
1162
|
+
code: "channel_webhook_proxy_not_configured",
|
|
1163
|
+
message: "Hosted channel webhooks require AGENTKIT_CLOUD_API_URL on the deployed Worker.",
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
{ status: 501 },
|
|
736
1167
|
);
|
|
737
1168
|
}
|
|
738
1169
|
|
|
739
|
-
const
|
|
1170
|
+
const target = new URL(\`\${url.pathname}\${url.search}\`, env.AGENTKIT_CLOUD_API_URL);
|
|
1171
|
+
const headers = new Headers(request.headers);
|
|
1172
|
+
headers.delete("host");
|
|
1173
|
+
headers.delete("content-length");
|
|
740
1174
|
|
|
741
|
-
if (
|
|
742
|
-
|
|
1175
|
+
if (env.AGENTKIT_DEPLOY_ID) {
|
|
1176
|
+
headers.set("X-AgentKit-Deploy-ID", env.AGENTKIT_DEPLOY_ID);
|
|
743
1177
|
}
|
|
744
1178
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const result = await executeTursoSql(env, sql, args);
|
|
748
|
-
return Response.json(result);
|
|
749
|
-
} catch (error) {
|
|
750
|
-
return hostedErrorResponse(error);
|
|
1179
|
+
if (env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
1180
|
+
headers.set("X-AgentKit-Runtime-Gate", env.AGENTKIT_RUNTIME_GATE_TOKEN);
|
|
751
1181
|
}
|
|
1182
|
+
|
|
1183
|
+
return fetch(target.href, {
|
|
1184
|
+
method: request.method,
|
|
1185
|
+
headers,
|
|
1186
|
+
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
|
|
1187
|
+
redirect: "manual",
|
|
1188
|
+
});
|
|
752
1189
|
}
|
|
753
1190
|
|
|
754
1191
|
async function handleFile(request, env, rawKey) {
|
|
@@ -878,13 +1315,14 @@ async function handleTool(request, env, toolName) {
|
|
|
878
1315
|
},
|
|
879
1316
|
input: result.input,
|
|
880
1317
|
output: result.output,
|
|
1318
|
+
...(result.rendered ? { rendered: result.rendered } : {}),
|
|
881
1319
|
});
|
|
882
1320
|
} catch (error) {
|
|
883
1321
|
return hostedErrorResponse(error);
|
|
884
1322
|
}
|
|
885
1323
|
}
|
|
886
1324
|
|
|
887
|
-
async function handleChat(request, env) {
|
|
1325
|
+
async function handleChat(request, env, ctx) {
|
|
888
1326
|
let body;
|
|
889
1327
|
|
|
890
1328
|
try {
|
|
@@ -911,47 +1349,58 @@ async function handleChat(request, env) {
|
|
|
911
1349
|
);
|
|
912
1350
|
}
|
|
913
1351
|
|
|
914
|
-
|
|
915
|
-
let content;
|
|
916
|
-
const toolCalls = [];
|
|
917
|
-
|
|
918
|
-
const toolRequest = parseToolRequest(message);
|
|
1352
|
+
let conversationId;
|
|
919
1353
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
input: toolRequest.input,
|
|
925
|
-
env,
|
|
926
|
-
signal: request.signal,
|
|
927
|
-
invocation: "chat",
|
|
928
|
-
});
|
|
929
|
-
toolCalls.push(toolResult);
|
|
930
|
-
content =
|
|
931
|
-
toolResult.visibility === "internal"
|
|
932
|
-
? \`Tool \${toolResult.name}: completed\`
|
|
933
|
-
: \`Tool \${toolResult.name}: \${JSON.stringify(toolResult.output)}\`;
|
|
934
|
-
} catch (error) {
|
|
935
|
-
await persistFailure(env, conversationId, message, error);
|
|
936
|
-
return hostedErrorResponse(error);
|
|
937
|
-
}
|
|
1354
|
+
try {
|
|
1355
|
+
conversationId = normalizeConversationId(body.conversationId) ?? crypto.randomUUID();
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
return hostedErrorResponse(error);
|
|
938
1358
|
}
|
|
939
1359
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
}
|
|
1360
|
+
const previousMessages = await loadConversationMessages(env, conversationId).catch(() => []);
|
|
1361
|
+
let content;
|
|
1362
|
+
let providerResult;
|
|
1363
|
+
const toolCalls = [];
|
|
1364
|
+
|
|
1365
|
+
try {
|
|
1366
|
+
providerResult = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal, toolCalls);
|
|
1367
|
+
content = providerResult.content;
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
const persisted = await persistFailure(env, conversationId, message, error, toolCalls);
|
|
1370
|
+
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1371
|
+
kind: "chat_run",
|
|
1372
|
+
status: "failed",
|
|
1373
|
+
conversationId,
|
|
1374
|
+
runId: persisted?.runId,
|
|
1375
|
+
provider: manifest.provider.name,
|
|
1376
|
+
model: manifest.provider.model,
|
|
1377
|
+
metadata: {
|
|
1378
|
+
error_code: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
|
|
1379
|
+
tool_calls: toolCalls.length,
|
|
1380
|
+
},
|
|
1381
|
+
}));
|
|
1382
|
+
return hostedErrorResponse(error);
|
|
947
1383
|
}
|
|
948
1384
|
|
|
949
1385
|
const persisted = await persist(env, {
|
|
950
1386
|
conversationId,
|
|
951
1387
|
userMessage: message,
|
|
952
1388
|
assistantMessage: content,
|
|
1389
|
+
usage: providerResult.usage,
|
|
953
1390
|
toolCalls,
|
|
954
1391
|
});
|
|
1392
|
+
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1393
|
+
kind: "chat_run",
|
|
1394
|
+
status: "succeeded",
|
|
1395
|
+
conversationId: persisted.conversationId ?? conversationId,
|
|
1396
|
+
runId: persisted.runId,
|
|
1397
|
+
provider: manifest.provider.name,
|
|
1398
|
+
model: manifest.provider.model,
|
|
1399
|
+
usage: providerResult.usage,
|
|
1400
|
+
metadata: {
|
|
1401
|
+
tool_calls: toolCalls.length,
|
|
1402
|
+
},
|
|
1403
|
+
}));
|
|
955
1404
|
|
|
956
1405
|
return Response.json({
|
|
957
1406
|
message: {
|
|
@@ -962,6 +1411,39 @@ async function handleChat(request, env) {
|
|
|
962
1411
|
});
|
|
963
1412
|
}
|
|
964
1413
|
|
|
1414
|
+
async function handleConversationRead(request, env, action, conversationId) {
|
|
1415
|
+
const auth = await authorizeConversationRead(request, env);
|
|
1416
|
+
|
|
1417
|
+
if (auth) {
|
|
1418
|
+
return auth;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (!env.AGENTKIT_CONVERSATIONS) {
|
|
1422
|
+
return Response.json({ error: { code: "conversations_not_configured", message: "Hosted conversation storage is not configured." } }, { status: 503 });
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
if (action === "list") {
|
|
1426
|
+
const stub = hostedConversationStore(env);
|
|
1427
|
+
return stub.fetch("https://agentkit.internal/conversations");
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (!conversationId) {
|
|
1431
|
+
return Response.json({ error: { code: "validation_error", message: "Missing conversation id." } }, { status: 400 });
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const stub = hostedConversationStore(env);
|
|
1435
|
+
const suffix = action === "trace" ? "/trace" : "";
|
|
1436
|
+
const path = \`/conversations/\${encodeURIComponent(conversationId)}\${suffix}\`;
|
|
1437
|
+
const response = await stub.fetch(\`https://agentkit.internal\${path}\`);
|
|
1438
|
+
|
|
1439
|
+
if (response.status !== 404) {
|
|
1440
|
+
return response;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const legacyStub = legacyConversationStore(env, conversationId);
|
|
1444
|
+
return legacyStub.fetch(\`https://agentkit.internal\${path}\`);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
965
1447
|
function readChatMessage(body) {
|
|
966
1448
|
if (typeof body.message === "string") {
|
|
967
1449
|
return body.message.trim();
|
|
@@ -978,74 +1460,472 @@ function isRecord(value) {
|
|
|
978
1460
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
979
1461
|
}
|
|
980
1462
|
|
|
981
|
-
async function callHostedProvider(env,
|
|
1463
|
+
async function callHostedProvider(env, messages, signal, toolCalls = []) {
|
|
982
1464
|
const adapter = getProviderAdapter(manifest.provider);
|
|
1465
|
+
const hostedTools = createHostedTools(env);
|
|
1466
|
+
const clock = buildRuntimeClockContext({
|
|
1467
|
+
timeZone: manifest.timeZone,
|
|
1468
|
+
env,
|
|
1469
|
+
});
|
|
983
1470
|
const result = await adapter.run({
|
|
984
1471
|
agent: {
|
|
985
1472
|
name: manifest.name,
|
|
986
1473
|
runtime: manifest.runtime,
|
|
987
1474
|
provider: manifest.provider,
|
|
988
1475
|
},
|
|
989
|
-
instructions:
|
|
990
|
-
|
|
1476
|
+
instructions: appendRuntimePromptContext(
|
|
1477
|
+
withToolVisibilityInstructions(appendHostedKnowledgePromptPolicy(instructions), hostedTools),
|
|
1478
|
+
{
|
|
1479
|
+
clock,
|
|
1480
|
+
},
|
|
1481
|
+
),
|
|
1482
|
+
messages,
|
|
991
1483
|
tools: hostedTools,
|
|
992
|
-
toolRuntime: createHostedProviderToolRuntime(env, signal),
|
|
1484
|
+
toolRuntime: createHostedProviderToolRuntime(env, signal, clock, toolCalls),
|
|
993
1485
|
env,
|
|
994
1486
|
signal,
|
|
995
1487
|
});
|
|
996
1488
|
|
|
997
|
-
return
|
|
1489
|
+
return {
|
|
1490
|
+
content: result.message.content,
|
|
1491
|
+
usage: result.usage,
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function appendHostedKnowledgePromptPolicy(baseInstructions) {
|
|
1496
|
+
if (!manifest.knowledge.enabled) {
|
|
1497
|
+
return baseInstructions;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return \`\${baseInstructions.trimEnd()}
|
|
1501
|
+
|
|
1502
|
+
Knowledge rules:
|
|
1503
|
+
- Use agentkit_search_knowledge before answering business-specific factual questions about the user's documents, policies, prices, availability, services, procedures, or source-specific claims.
|
|
1504
|
+
- Do not invent prices, policies, legal/clinical claims, availability, or source-specific facts when Knowledge has not returned support for the answer.
|
|
1505
|
+
- Use retrieved Knowledge as context for the final answer, but do not expose raw retrieval JSON, scores, chunk IDs, or internal tool output objects to the user.
|
|
1506
|
+
\`;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function withToolVisibilityInstructions(baseInstructions, tools) {
|
|
1510
|
+
const internalTools = tools
|
|
1511
|
+
.filter((tool) => tool.visibility === "internal")
|
|
1512
|
+
.map((tool) => tool.name)
|
|
1513
|
+
.sort();
|
|
1514
|
+
|
|
1515
|
+
if (internalTools.length === 0) {
|
|
1516
|
+
return baseInstructions;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return \`\${baseInstructions.trimEnd()}
|
|
1520
|
+
|
|
1521
|
+
Tool visibility rules:
|
|
1522
|
+
- Tool outputs marked internal are operational state for the agent, not user-facing content.
|
|
1523
|
+
- Use internal outputs to decide what to do next, but do not reveal raw internal labels, scores, statuses, JSON fields, or tool output objects to the user.
|
|
1524
|
+
- Internal tools: \${internalTools.join(", ")}.
|
|
1525
|
+
\`;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function createHostedKnowledgeSearchTool() {
|
|
1529
|
+
return {
|
|
1530
|
+
name: manifest.knowledge.internalTool || "agentkit_search_knowledge",
|
|
1531
|
+
description: "Search the Agent Capsule's configured Knowledge sources for business-specific facts.",
|
|
1532
|
+
visibility: "internal",
|
|
1533
|
+
inputSchema: {
|
|
1534
|
+
type: "object",
|
|
1535
|
+
properties: {
|
|
1536
|
+
query: { type: "string" },
|
|
1537
|
+
topK: { type: "integer" },
|
|
1538
|
+
},
|
|
1539
|
+
required: ["query"],
|
|
1540
|
+
additionalProperties: false,
|
|
1541
|
+
},
|
|
1542
|
+
outputSchema: {
|
|
1543
|
+
type: "object",
|
|
1544
|
+
properties: {
|
|
1545
|
+
results: {
|
|
1546
|
+
type: "array",
|
|
1547
|
+
items: {
|
|
1548
|
+
type: "object",
|
|
1549
|
+
properties: {
|
|
1550
|
+
content: { type: "string" },
|
|
1551
|
+
score: { type: "number" },
|
|
1552
|
+
source: { type: "object" },
|
|
1553
|
+
},
|
|
1554
|
+
required: ["content", "score", "source"],
|
|
1555
|
+
},
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
required: ["results"],
|
|
1559
|
+
additionalProperties: false,
|
|
1560
|
+
},
|
|
1561
|
+
secrets: manifest.knowledge.embedding.secret ? [manifest.knowledge.embedding.secret] : [],
|
|
1562
|
+
async execute(input, ctx) {
|
|
1563
|
+
const query = typeof input?.query === "string" ? input.query : "";
|
|
1564
|
+
const topK = normalizeKnowledgeTopK(input?.topK ?? manifest.knowledge.retrieval.topK);
|
|
1565
|
+
const results = await searchHostedKnowledge(ctx.database, query, topK, ctx.secrets ?? {});
|
|
1566
|
+
|
|
1567
|
+
return {
|
|
1568
|
+
results: results.map((result) => ({
|
|
1569
|
+
content: result.content,
|
|
1570
|
+
score: result.score,
|
|
1571
|
+
source: result.source,
|
|
1572
|
+
})),
|
|
1573
|
+
};
|
|
1574
|
+
},
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
async function searchHostedKnowledge(database, query, topK, secrets) {
|
|
1579
|
+
const normalizedQuery = String(query || "").trim();
|
|
1580
|
+
|
|
1581
|
+
if (!normalizedQuery) {
|
|
1582
|
+
throw agentKitError("knowledge_query_invalid", "Knowledge search query must be a non-empty string.");
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const hasEmbeddingProvider = manifest.knowledge.embedding.provider !== "none";
|
|
1586
|
+
const lexical =
|
|
1587
|
+
manifest.knowledge.retrieval.hybrid || !hasEmbeddingProvider
|
|
1588
|
+
? await searchHostedKnowledgeLexical(database, normalizedQuery, Math.max(topK * 4, topK))
|
|
1589
|
+
: [];
|
|
1590
|
+
const semantic = hasEmbeddingProvider
|
|
1591
|
+
? await searchHostedKnowledgeSemantic(database, normalizedQuery, topK * 4, secrets)
|
|
1592
|
+
: [];
|
|
1593
|
+
|
|
1594
|
+
return combineHostedKnowledgeResults(lexical, semantic, topK);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
async function searchHostedKnowledgeLexical(database, query, limit) {
|
|
1598
|
+
const tokens = hostedKnowledgeQueryTokens(query);
|
|
1599
|
+
|
|
1600
|
+
if (tokens.length === 0) {
|
|
1601
|
+
return [];
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
try {
|
|
1605
|
+
const result = await database.query(
|
|
1606
|
+
\`
|
|
1607
|
+
SELECT
|
|
1608
|
+
c.id AS chunk_id,
|
|
1609
|
+
c.source_id AS source_id,
|
|
1610
|
+
c.content AS content,
|
|
1611
|
+
c.title AS title,
|
|
1612
|
+
c.section AS section,
|
|
1613
|
+
c.locator AS locator,
|
|
1614
|
+
s.source_key AS source_key,
|
|
1615
|
+
rank
|
|
1616
|
+
FROM agentkit_knowledge_fts
|
|
1617
|
+
JOIN agentkit_knowledge_chunks c ON c.id = agentkit_knowledge_fts.chunk_id
|
|
1618
|
+
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1619
|
+
WHERE agentkit_knowledge_fts MATCH ?
|
|
1620
|
+
ORDER BY rank ASC
|
|
1621
|
+
LIMIT ?
|
|
1622
|
+
\`,
|
|
1623
|
+
[tokens.map((token) => \`"\${token.replace(/"/g, '""')}"\`).join(" OR "), limit],
|
|
1624
|
+
);
|
|
1625
|
+
|
|
1626
|
+
return result.rows.map((row, index) => hostedKnowledgeResultFromRow(row, hostedLexicalScore(index), null));
|
|
1627
|
+
} catch {
|
|
1628
|
+
return searchHostedKnowledgeLexicalFallback(database, query, limit);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
async function searchHostedKnowledgeLexicalFallback(database, query, limit) {
|
|
1633
|
+
const like = \`%\${query.toLowerCase()}%\`;
|
|
1634
|
+
const result = await database.query(
|
|
1635
|
+
\`
|
|
1636
|
+
SELECT
|
|
1637
|
+
c.id AS chunk_id,
|
|
1638
|
+
c.source_id AS source_id,
|
|
1639
|
+
c.content AS content,
|
|
1640
|
+
c.title AS title,
|
|
1641
|
+
c.section AS section,
|
|
1642
|
+
c.locator AS locator,
|
|
1643
|
+
s.source_key AS source_key
|
|
1644
|
+
FROM agentkit_knowledge_chunks c
|
|
1645
|
+
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1646
|
+
WHERE lower(c.content) LIKE ?
|
|
1647
|
+
OR lower(COALESCE(c.title, '')) LIKE ?
|
|
1648
|
+
OR lower(COALESCE(c.section, '')) LIKE ?
|
|
1649
|
+
ORDER BY c.created_at DESC, c.ordinal ASC
|
|
1650
|
+
LIMIT ?
|
|
1651
|
+
\`,
|
|
1652
|
+
[like, like, like, limit],
|
|
1653
|
+
);
|
|
1654
|
+
|
|
1655
|
+
return result.rows.map((row, index) => hostedKnowledgeResultFromRow(row, hostedLexicalScore(index), null));
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
async function searchHostedKnowledgeSemantic(database, query, limit, secrets) {
|
|
1659
|
+
const queryEmbedding = await createHostedKnowledgeEmbedding(query, secrets);
|
|
1660
|
+
|
|
1661
|
+
if (!queryEmbedding) {
|
|
1662
|
+
return [];
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
try {
|
|
1666
|
+
return await searchHostedKnowledgeNativeVector(database, queryEmbedding, limit);
|
|
1667
|
+
} catch {
|
|
1668
|
+
return searchHostedKnowledgeSemanticFallback(database, queryEmbedding, limit);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
async function searchHostedKnowledgeNativeVector(database, queryEmbedding, limit) {
|
|
1673
|
+
const vector = JSON.stringify(queryEmbedding);
|
|
1674
|
+
const result = await database.query(
|
|
1675
|
+
\`
|
|
1676
|
+
SELECT
|
|
1677
|
+
c.id AS chunk_id,
|
|
1678
|
+
c.source_id AS source_id,
|
|
1679
|
+
c.content AS content,
|
|
1680
|
+
c.title AS title,
|
|
1681
|
+
c.section AS section,
|
|
1682
|
+
c.locator AS locator,
|
|
1683
|
+
s.source_key AS source_key,
|
|
1684
|
+
vector_distance_cos(v.embedding, vector32(?)) AS vector_distance
|
|
1685
|
+
FROM vector_top_k('agentkit_knowledge_vectors_embedding_idx', vector32(?), ?) nearest
|
|
1686
|
+
JOIN agentkit_knowledge_vectors v ON v.id = nearest.id
|
|
1687
|
+
JOIN agentkit_knowledge_chunks c ON c.id = v.chunk_id
|
|
1688
|
+
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1689
|
+
ORDER BY vector_distance ASC
|
|
1690
|
+
\`,
|
|
1691
|
+
[vector, vector, limit],
|
|
1692
|
+
);
|
|
1693
|
+
|
|
1694
|
+
return result.rows
|
|
1695
|
+
.map((row) => {
|
|
1696
|
+
const distance = Number(row.vector_distance);
|
|
1697
|
+
const semantic = Number.isFinite(distance) ? 1 - distance : 0;
|
|
1698
|
+
return hostedKnowledgeResultFromRow(row, 0, semantic);
|
|
1699
|
+
})
|
|
1700
|
+
.filter((result) => result.match.semantic !== null && result.match.semantic > 0)
|
|
1701
|
+
.slice(0, limit);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async function searchHostedKnowledgeSemanticFallback(database, queryEmbedding, limit) {
|
|
1705
|
+
const result = await database.query(
|
|
1706
|
+
\`
|
|
1707
|
+
SELECT
|
|
1708
|
+
c.id AS chunk_id,
|
|
1709
|
+
c.source_id AS source_id,
|
|
1710
|
+
c.content AS content,
|
|
1711
|
+
c.title AS title,
|
|
1712
|
+
c.section AS section,
|
|
1713
|
+
c.locator AS locator,
|
|
1714
|
+
c.embedding_json AS embedding_json,
|
|
1715
|
+
s.source_key AS source_key
|
|
1716
|
+
FROM agentkit_knowledge_chunks c
|
|
1717
|
+
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1718
|
+
WHERE c.embedding_json IS NOT NULL
|
|
1719
|
+
ORDER BY c.created_at DESC, c.ordinal ASC
|
|
1720
|
+
\`,
|
|
1721
|
+
);
|
|
1722
|
+
|
|
1723
|
+
return result.rows
|
|
1724
|
+
.map((row) => {
|
|
1725
|
+
const embedding = parseHostedKnowledgeEmbedding(row.embedding_json);
|
|
1726
|
+
const semantic = embedding ? hostedCosineSimilarity(queryEmbedding, embedding) : 0;
|
|
1727
|
+
return hostedKnowledgeResultFromRow(row, 0, semantic);
|
|
1728
|
+
})
|
|
1729
|
+
.filter((result) => result.match.semantic !== null && result.match.semantic > 0)
|
|
1730
|
+
.sort((left, right) => (right.match.semantic ?? 0) - (left.match.semantic ?? 0))
|
|
1731
|
+
.slice(0, limit);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
async function createHostedKnowledgeEmbedding(input, secrets) {
|
|
1735
|
+
const provider = manifest.knowledge.embedding.provider;
|
|
1736
|
+
|
|
1737
|
+
if (provider === "none") {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
if (provider === "test") {
|
|
1742
|
+
return hostedFakeEmbedding(input, manifest.knowledge.embedding.dimensions || 32);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (provider !== "openai") {
|
|
1746
|
+
throw agentKitError("knowledge_embedding_provider_unsupported", "Unsupported knowledge embedding provider.");
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const secretName = manifest.knowledge.embedding.secret || "OPENAI_API_KEY";
|
|
1750
|
+
const apiKey = secrets[secretName];
|
|
1751
|
+
|
|
1752
|
+
if (!apiKey) {
|
|
1753
|
+
throw agentKitError("knowledge_embedding_secret_missing", \`Knowledge embeddings require missing secret "\${secretName}".\`);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
1757
|
+
method: "POST",
|
|
1758
|
+
headers: {
|
|
1759
|
+
"content-type": "application/json",
|
|
1760
|
+
authorization: \`Bearer \${apiKey}\`,
|
|
1761
|
+
},
|
|
1762
|
+
body: JSON.stringify({
|
|
1763
|
+
model: manifest.knowledge.embedding.model || "text-embedding-3-small",
|
|
1764
|
+
input,
|
|
1765
|
+
encoding_format: "float",
|
|
1766
|
+
...(manifest.knowledge.embedding.dimensions ? { dimensions: manifest.knowledge.embedding.dimensions } : {}),
|
|
1767
|
+
}),
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
if (!response.ok) {
|
|
1771
|
+
const text = await response.text().catch(() => "");
|
|
1772
|
+
throw agentKitError(
|
|
1773
|
+
"knowledge_embedding_failed",
|
|
1774
|
+
\`OpenAI embedding request failed with HTTP \${response.status}.\${text ? " " + text.slice(0, 240) : ""}\`,
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const payload = await response.json();
|
|
1779
|
+
const embedding = payload?.data?.[0]?.embedding;
|
|
1780
|
+
|
|
1781
|
+
if (!Array.isArray(embedding) || embedding.some((value) => typeof value !== "number")) {
|
|
1782
|
+
throw agentKitError("knowledge_embedding_failed", "OpenAI embedding response did not contain a numeric vector.");
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
return embedding;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function combineHostedKnowledgeResults(lexical, semantic, topK) {
|
|
1789
|
+
const byChunk = new Map();
|
|
1790
|
+
|
|
1791
|
+
for (const result of lexical) {
|
|
1792
|
+
byChunk.set(result.chunkId, result);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
for (const result of semantic) {
|
|
1796
|
+
const existing = byChunk.get(result.chunkId);
|
|
1797
|
+
|
|
1798
|
+
if (!existing) {
|
|
1799
|
+
byChunk.set(result.chunkId, {
|
|
1800
|
+
...result,
|
|
1801
|
+
score: hostedCombinedScore(0, result.match.semantic),
|
|
1802
|
+
});
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
byChunk.set(result.chunkId, {
|
|
1807
|
+
...existing,
|
|
1808
|
+
score: hostedCombinedScore(existing.match.lexical, result.match.semantic),
|
|
1809
|
+
match: {
|
|
1810
|
+
lexical: existing.match.lexical,
|
|
1811
|
+
semantic: result.match.semantic,
|
|
1812
|
+
},
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return Array.from(byChunk.values())
|
|
1817
|
+
.map((result) => ({
|
|
1818
|
+
...result,
|
|
1819
|
+
score: result.score || hostedCombinedScore(result.match.lexical, result.match.semantic),
|
|
1820
|
+
}))
|
|
1821
|
+
.sort((left, right) => right.score - left.score || left.source.path.localeCompare(right.source.path))
|
|
1822
|
+
.slice(0, topK);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
function hostedKnowledgeResultFromRow(row, lexical, semantic) {
|
|
1826
|
+
return {
|
|
1827
|
+
chunkId: String(row.chunk_id),
|
|
1828
|
+
content: String(row.content),
|
|
1829
|
+
score: hostedCombinedScore(lexical, semantic),
|
|
1830
|
+
source: {
|
|
1831
|
+
id: String(row.source_id),
|
|
1832
|
+
path: String(row.source_key),
|
|
1833
|
+
title: typeof row.title === "string" ? row.title : null,
|
|
1834
|
+
section: typeof row.section === "string" ? row.section : null,
|
|
1835
|
+
locator: typeof row.locator === "string" ? row.locator : null,
|
|
1836
|
+
},
|
|
1837
|
+
match: {
|
|
1838
|
+
lexical,
|
|
1839
|
+
semantic,
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function normalizeKnowledgeTopK(value) {
|
|
1845
|
+
const topK = Number(value);
|
|
1846
|
+
|
|
1847
|
+
if (!Number.isInteger(topK) || topK <= 0 || topK > 50) {
|
|
1848
|
+
throw agentKitError("knowledge_query_invalid", "Knowledge search topK must be a positive integer no larger than 50.");
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
return topK;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function hostedKnowledgeQueryTokens(query) {
|
|
1855
|
+
return Array.from(new Set(String(query).toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [])).slice(0, 12);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function parseHostedKnowledgeEmbedding(value) {
|
|
1859
|
+
if (typeof value !== "string") {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
try {
|
|
1864
|
+
const parsed = JSON.parse(value);
|
|
1865
|
+
return Array.isArray(parsed) && parsed.every((item) => typeof item === "number") ? parsed : null;
|
|
1866
|
+
} catch {
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function hostedCombinedScore(lexical, semantic) {
|
|
1872
|
+
const semanticScore = semantic === null ? 0 : Math.max(0, (semantic + 1) / 2);
|
|
1873
|
+
return Number((lexical * 0.6 + semanticScore * 0.4).toFixed(6));
|
|
998
1874
|
}
|
|
999
1875
|
|
|
1000
|
-
function
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
.map((tool) => tool.name)
|
|
1004
|
-
.sort();
|
|
1876
|
+
function hostedLexicalScore(index) {
|
|
1877
|
+
return Number((1 / (index + 1)).toFixed(6));
|
|
1878
|
+
}
|
|
1005
1879
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1880
|
+
function hostedCosineSimilarity(left, right) {
|
|
1881
|
+
if (left.length !== right.length || left.length === 0) {
|
|
1882
|
+
return 0;
|
|
1008
1883
|
}
|
|
1009
1884
|
|
|
1010
|
-
|
|
1885
|
+
let dot = 0;
|
|
1886
|
+
let leftNorm = 0;
|
|
1887
|
+
let rightNorm = 0;
|
|
1011
1888
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1889
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
1890
|
+
dot += left[index] * right[index];
|
|
1891
|
+
leftNorm += left[index] * left[index];
|
|
1892
|
+
rightNorm += right[index] * right[index];
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if (leftNorm === 0 || rightNorm === 0) {
|
|
1896
|
+
return 0;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
async function hostedFakeEmbedding(input, dimensions) {
|
|
1903
|
+
const values = Array.from({ length: dimensions }, () => 0);
|
|
1904
|
+
const tokens = String(input).toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [];
|
|
1905
|
+
const encoder = new TextEncoder();
|
|
1906
|
+
|
|
1907
|
+
for (const token of tokens) {
|
|
1908
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token)));
|
|
1909
|
+
const slot = hash[0] % dimensions;
|
|
1910
|
+
const sign = hash[1] % 2 === 0 ? 1 : -1;
|
|
1911
|
+
values[slot] += sign * (1 + (hash[2] % 7) / 10);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
const norm = Math.sqrt(values.reduce((sum, value) => sum + value * value, 0));
|
|
1915
|
+
return norm === 0 ? values : values.map((value) => Number((value / norm).toFixed(8)));
|
|
1017
1916
|
}
|
|
1018
1917
|
|
|
1019
|
-
async function persistFailure(env, conversationId, userMessage, error) {
|
|
1020
|
-
|
|
1918
|
+
async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
|
|
1919
|
+
return persist(env, {
|
|
1021
1920
|
conversationId,
|
|
1022
1921
|
userMessage,
|
|
1023
1922
|
status: "failed",
|
|
1024
1923
|
errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
|
|
1025
1924
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
1026
|
-
toolCalls
|
|
1925
|
+
toolCalls,
|
|
1027
1926
|
}).catch(() => undefined);
|
|
1028
1927
|
}
|
|
1029
1928
|
|
|
1030
|
-
function parseToolRequest(content) {
|
|
1031
|
-
let parsed;
|
|
1032
|
-
|
|
1033
|
-
try {
|
|
1034
|
-
parsed = JSON.parse(content);
|
|
1035
|
-
} catch {
|
|
1036
|
-
return null;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (!isRecord(parsed) || typeof parsed.tool !== "string" || parsed.tool.length === 0) {
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
return {
|
|
1044
|
-
name: parsed.tool,
|
|
1045
|
-
input: "input" in parsed ? parsed.input : {},
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
1929
|
function inspectHostedDatabase() {
|
|
1050
1930
|
const schema = manifest.storage.database.driver === "turso" ? manifest.storage.database.schema : null;
|
|
1051
1931
|
|
|
@@ -1064,29 +1944,69 @@ function inspectHostedDatabase() {
|
|
|
1064
1944
|
}
|
|
1065
1945
|
|
|
1066
1946
|
function hostedManagedSecrets() {
|
|
1067
|
-
|
|
1068
|
-
|
|
1947
|
+
const secrets = [];
|
|
1948
|
+
|
|
1949
|
+
if (manifest.storage.database.driver === "turso") {
|
|
1950
|
+
secrets.push("TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL");
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
if (manifest.integrations.some((integration) => integration.provider === "composio" && integration.mode === "managed")) {
|
|
1954
|
+
secrets.push("COMPOSIO_API_KEY");
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
return secrets.sort();
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function createHostedTools(env) {
|
|
1961
|
+
return [
|
|
1962
|
+
...(hostedKnowledgeTool ? [hostedKnowledgeTool] : []),
|
|
1963
|
+
...createManagedComposioTools(agentConfig, { userId: env[MANAGED_COMPOSIO_USER_ID_BINDING] }),
|
|
1964
|
+
...(agentConfig.tools ?? []),
|
|
1965
|
+
];
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
function hostedIntegrations(env) {
|
|
1969
|
+
const userId = env[MANAGED_COMPOSIO_USER_ID_BINDING];
|
|
1970
|
+
|
|
1971
|
+
if (!userId) {
|
|
1972
|
+
return manifest.integrations;
|
|
1069
1973
|
}
|
|
1070
1974
|
|
|
1071
|
-
return
|
|
1975
|
+
return manifest.integrations.map((integration) =>
|
|
1976
|
+
integration.provider === "composio" && integration.mode === "managed"
|
|
1977
|
+
? { ...integration, userId }
|
|
1978
|
+
: integration,
|
|
1979
|
+
);
|
|
1072
1980
|
}
|
|
1073
1981
|
|
|
1074
|
-
function createHostedProviderToolRuntime(env, signal) {
|
|
1982
|
+
function createHostedProviderToolRuntime(env, signal, clock, toolCalls) {
|
|
1983
|
+
const hostedTools = createHostedTools(env);
|
|
1984
|
+
|
|
1075
1985
|
return {
|
|
1076
1986
|
definitions: hostedTools,
|
|
1077
|
-
execute(request) {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1987
|
+
async execute(request) {
|
|
1988
|
+
try {
|
|
1989
|
+
const result = await executeHostedTool({
|
|
1990
|
+
tools: hostedTools,
|
|
1991
|
+
name: request.name,
|
|
1992
|
+
input: request.input,
|
|
1993
|
+
env,
|
|
1994
|
+
signal,
|
|
1995
|
+
invocation: "chat",
|
|
1996
|
+
clock,
|
|
1997
|
+
});
|
|
1998
|
+
toolCalls?.push(result);
|
|
1999
|
+
return result;
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
toolCalls?.push(failedHostedToolCall(hostedTools, request, error));
|
|
2002
|
+
throw error;
|
|
2003
|
+
}
|
|
1085
2004
|
},
|
|
1086
2005
|
};
|
|
1087
2006
|
}
|
|
1088
2007
|
|
|
1089
2008
|
async function executeHostedTool(request) {
|
|
2009
|
+
const hostedTools = request.tools ?? createHostedTools(request.env);
|
|
1090
2010
|
const tool = hostedTools.find((candidate) => candidate.name === request.name);
|
|
1091
2011
|
const id = crypto.randomUUID();
|
|
1092
2012
|
|
|
@@ -1099,6 +2019,16 @@ async function executeHostedTool(request) {
|
|
|
1099
2019
|
|
|
1100
2020
|
const secrets = resolveToolSecrets(tool, request.env);
|
|
1101
2021
|
const database = createHostedDatabaseRunner(request.env);
|
|
2022
|
+
const clock = request.clock ?? buildRuntimeClockContext({
|
|
2023
|
+
timeZone: manifest.timeZone,
|
|
2024
|
+
env: request.env,
|
|
2025
|
+
});
|
|
2026
|
+
const runtime = {
|
|
2027
|
+
environment: "hosted",
|
|
2028
|
+
invocation: request.invocation ?? "tool",
|
|
2029
|
+
target: "cloudflare",
|
|
2030
|
+
database: manifest.storage.database.driver === "turso" ? "turso" : "none",
|
|
2031
|
+
};
|
|
1102
2032
|
const output = await runToolWithTimeout(
|
|
1103
2033
|
tool,
|
|
1104
2034
|
input,
|
|
@@ -1106,12 +2036,8 @@ async function executeHostedTool(request) {
|
|
|
1106
2036
|
secrets,
|
|
1107
2037
|
database,
|
|
1108
2038
|
signal: request.signal,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
invocation: request.invocation ?? "tool",
|
|
1112
|
-
target: "cloudflare",
|
|
1113
|
-
database: manifest.storage.database.driver === "turso" ? "turso" : "none",
|
|
1114
|
-
},
|
|
2039
|
+
clock,
|
|
2040
|
+
runtime,
|
|
1115
2041
|
},
|
|
1116
2042
|
tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
|
|
1117
2043
|
);
|
|
@@ -1120,6 +2046,8 @@ async function executeHostedTool(request) {
|
|
|
1120
2046
|
validateSchema(output, tool.outputSchema, \`tool "\${tool.name}" output\`);
|
|
1121
2047
|
}
|
|
1122
2048
|
|
|
2049
|
+
const rendered = renderHostedToolOutput(tool, input, output, runtime, clock);
|
|
2050
|
+
|
|
1123
2051
|
return {
|
|
1124
2052
|
id,
|
|
1125
2053
|
name: tool.name,
|
|
@@ -1127,9 +2055,135 @@ async function executeHostedTool(request) {
|
|
|
1127
2055
|
status: "completed",
|
|
1128
2056
|
input,
|
|
1129
2057
|
output,
|
|
2058
|
+
...(rendered ? { rendered } : {}),
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
function failedHostedToolCall(tools, request, error) {
|
|
2063
|
+
const tool = tools.find((candidate) => candidate.name === request.name);
|
|
2064
|
+
|
|
2065
|
+
return {
|
|
2066
|
+
id: crypto.randomUUID(),
|
|
2067
|
+
name: String(request.name || ""),
|
|
2068
|
+
visibility: tool?.visibility ?? "user",
|
|
2069
|
+
status: "failed",
|
|
2070
|
+
input: request.input ?? {},
|
|
2071
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
function renderHostedToolOutput(tool, input, output, runtime, clock) {
|
|
2076
|
+
if (!tool.render) {
|
|
2077
|
+
return undefined;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const rendered = tool.render(output, {
|
|
2081
|
+
input,
|
|
2082
|
+
runtime,
|
|
2083
|
+
clock,
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
if (typeof rendered === "string") {
|
|
2087
|
+
return { text: rendered };
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
if (!isRecord(rendered) || typeof rendered.text !== "string" || rendered.text.trim().length === 0) {
|
|
2091
|
+
throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render must return a non-empty text string.\`);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
if (rendered.blocks !== undefined) {
|
|
2095
|
+
if (!Array.isArray(rendered.blocks)) {
|
|
2096
|
+
throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render.blocks must be an array when provided.\`);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
for (const [index, block] of rendered.blocks.entries()) {
|
|
2100
|
+
validateRenderBlock(block, \`tool "\${tool.name}" render.blocks[\${index}]\`);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
return {
|
|
2105
|
+
text: rendered.text,
|
|
2106
|
+
...(rendered.blocks ? { blocks: rendered.blocks } : {}),
|
|
1130
2107
|
};
|
|
1131
2108
|
}
|
|
1132
2109
|
|
|
2110
|
+
function validateRenderBlock(block, label) {
|
|
2111
|
+
if (!isRecord(block)) {
|
|
2112
|
+
throw agentKitError("tool_validation_error", \`\${label} must be an object.\`);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
if (block.type === "text") {
|
|
2116
|
+
if (typeof block.text !== "string" || block.text.trim().length === 0) {
|
|
2117
|
+
throw agentKitError("tool_validation_error", \`\${label}.text must be a non-empty string.\`);
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
if (block.type === "card") {
|
|
2124
|
+
if (typeof block.title !== "string" || block.title.trim().length === 0) {
|
|
2125
|
+
throw agentKitError("tool_validation_error", \`\${label}.title must be a non-empty string.\`);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
validateOptionalString(block.subtitle, \`\${label}.subtitle\`);
|
|
2129
|
+
validateRenderFields(block.fields, \`\${label}.fields\`);
|
|
2130
|
+
validateRenderActions(block.actions, \`\${label}.actions\`);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
throw agentKitError("tool_validation_error", \`\${label}.type must be "text" or "card".\`);
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function validateRenderFields(value, label) {
|
|
2138
|
+
if (value === undefined) {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
if (!Array.isArray(value)) {
|
|
2143
|
+
throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
for (const [index, field] of value.entries()) {
|
|
2147
|
+
if (!isRecord(field)) {
|
|
2148
|
+
throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
validateRequiredString(field.label, \`\${label}[\${index}].label\`);
|
|
2152
|
+
validateRequiredString(field.value, \`\${label}[\${index}].value\`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function validateRenderActions(value, label) {
|
|
2157
|
+
if (value === undefined) {
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
if (!Array.isArray(value)) {
|
|
2162
|
+
throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
for (const [index, action] of value.entries()) {
|
|
2166
|
+
if (!isRecord(action)) {
|
|
2167
|
+
throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
validateRequiredString(action.label, \`\${label}[\${index}].label\`);
|
|
2171
|
+
validateOptionalString(action.href, \`\${label}[\${index}].href\`);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function validateRequiredString(value, label) {
|
|
2176
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2177
|
+
throw agentKitError("tool_validation_error", \`\${label} must be a non-empty string.\`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function validateOptionalString(value, label) {
|
|
2182
|
+
if (value !== undefined && typeof value !== "string") {
|
|
2183
|
+
throw agentKitError("tool_validation_error", \`\${label} must be a string when provided.\`);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1133
2187
|
function resolveToolSecrets(tool, env) {
|
|
1134
2188
|
const secrets = {};
|
|
1135
2189
|
|
|
@@ -1170,6 +2224,7 @@ async function runToolWithTimeout(tool, input, context, timeoutMs) {
|
|
|
1170
2224
|
secrets: context.secrets,
|
|
1171
2225
|
signal: controller.signal,
|
|
1172
2226
|
runtime: context.runtime,
|
|
2227
|
+
clock: context.clock,
|
|
1173
2228
|
database: context.database,
|
|
1174
2229
|
db: context.database,
|
|
1175
2230
|
storage: {
|
|
@@ -1395,6 +2450,20 @@ function allowsNull(schema) {
|
|
|
1395
2450
|
}
|
|
1396
2451
|
|
|
1397
2452
|
async function authorizeChat(request, env, body) {
|
|
2453
|
+
return authorizeDeployAccess(request, env, body, {
|
|
2454
|
+
notConfiguredCode: "chat_access_not_configured",
|
|
2455
|
+
notConfiguredMessage: "Hosted chat requires a deploy access token gate.",
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
async function authorizeConversationRead(request, env) {
|
|
2460
|
+
return authorizeDeployAccess(request, env, {}, {
|
|
2461
|
+
notConfiguredCode: "conversation_access_not_configured",
|
|
2462
|
+
notConfiguredMessage: "Hosted conversation reads require a deploy access token gate.",
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
async function authorizeDeployAccess(request, env, body, options) {
|
|
1398
2467
|
const runtimeGate = await authorizeRuntimeGate(request, env, body);
|
|
1399
2468
|
|
|
1400
2469
|
if (runtimeGate) {
|
|
@@ -1405,52 +2474,28 @@ async function authorizeChat(request, env, body) {
|
|
|
1405
2474
|
return runtimeGate;
|
|
1406
2475
|
}
|
|
1407
2476
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
2477
|
+
const token = deployAccessToken(request, body);
|
|
2478
|
+
const acceptedTokens = [env.AGENTKIT_PRIVATE_TOKEN, env.AGENTKIT_ACCESS_TOKEN].filter(
|
|
2479
|
+
(value) => typeof value === "string" && value.length > 0,
|
|
2480
|
+
);
|
|
1411
2481
|
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
{
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
message: "Private deploy requires AGENTKIT_PRIVATE_TOKEN to be set as a Cloudflare secret.",
|
|
1419
|
-
},
|
|
2482
|
+
if (acceptedTokens.length === 0) {
|
|
2483
|
+
return Response.json(
|
|
2484
|
+
{
|
|
2485
|
+
error: {
|
|
2486
|
+
code: options.notConfiguredCode,
|
|
2487
|
+
message: options.notConfiguredMessage,
|
|
1420
2488
|
},
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const expected = \`Bearer \${env.AGENTKIT_PRIVATE_TOKEN}\`;
|
|
1426
|
-
if (request.headers.get("authorization") !== expected) {
|
|
1427
|
-
return Response.json(
|
|
1428
|
-
{ error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
|
|
1429
|
-
{ status: 401 },
|
|
1430
|
-
);
|
|
1431
|
-
}
|
|
2489
|
+
},
|
|
2490
|
+
{ status: 503 },
|
|
2491
|
+
);
|
|
1432
2492
|
}
|
|
1433
2493
|
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
code: "deploy_not_ready",
|
|
1440
|
-
message: "Token deploy requires AGENTKIT_ACCESS_TOKEN to be set as a Cloudflare secret.",
|
|
1441
|
-
},
|
|
1442
|
-
},
|
|
1443
|
-
{ status: 503 },
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
const expected = \`Bearer \${env.AGENTKIT_ACCESS_TOKEN}\`;
|
|
1448
|
-
if (request.headers.get("authorization") !== expected) {
|
|
1449
|
-
return Response.json(
|
|
1450
|
-
{ error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
|
|
1451
|
-
{ status: 401 },
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
2494
|
+
if (!token || !acceptedTokens.includes(token)) {
|
|
2495
|
+
return Response.json(
|
|
2496
|
+
{ error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
|
|
2497
|
+
{ status: 401 },
|
|
2498
|
+
);
|
|
1454
2499
|
}
|
|
1455
2500
|
|
|
1456
2501
|
return null;
|
|
@@ -1552,8 +2597,7 @@ async function persist(env, input) {
|
|
|
1552
2597
|
return {};
|
|
1553
2598
|
}
|
|
1554
2599
|
|
|
1555
|
-
const
|
|
1556
|
-
const stub = env.AGENTKIT_CONVERSATIONS.get(id);
|
|
2600
|
+
const stub = hostedConversationStore(env);
|
|
1557
2601
|
const response = await stub.fetch("https://agentkit.internal/messages", {
|
|
1558
2602
|
method: "POST",
|
|
1559
2603
|
body: JSON.stringify(input),
|
|
@@ -1566,6 +2610,148 @@ async function persist(env, input) {
|
|
|
1566
2610
|
return response.json();
|
|
1567
2611
|
}
|
|
1568
2612
|
|
|
2613
|
+
function queueRuntimeUsage(ctx, promise) {
|
|
2614
|
+
const safePromise = promise.catch(() => undefined);
|
|
2615
|
+
|
|
2616
|
+
if (ctx && typeof ctx.waitUntil === "function") {
|
|
2617
|
+
ctx.waitUntil(safePromise);
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
void safePromise;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
async function sendRuntimeUsage(env, input) {
|
|
2625
|
+
if (!env.AGENTKIT_CLOUD_API_URL || !env.AGENTKIT_DEPLOY_ID || !env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
const usage = normalizeRuntimeUsage(input.usage);
|
|
2630
|
+
const metadata = isRecord(input.metadata) ? input.metadata : {};
|
|
2631
|
+
const body = {
|
|
2632
|
+
kind: input.kind || "chat_run",
|
|
2633
|
+
status: input.status || "succeeded",
|
|
2634
|
+
...(input.runId ? { run_id: String(input.runId) } : {}),
|
|
2635
|
+
...(input.conversationId ? { conversation_id: String(input.conversationId) } : {}),
|
|
2636
|
+
...(input.provider ? { provider: String(input.provider) } : {}),
|
|
2637
|
+
...(input.model ? { model: String(input.model) } : {}),
|
|
2638
|
+
...(usage.inputTokens !== undefined ? { input_tokens: usage.inputTokens } : {}),
|
|
2639
|
+
...(usage.outputTokens !== undefined ? { output_tokens: usage.outputTokens } : {}),
|
|
2640
|
+
...(usage.totalTokens !== undefined ? { total_tokens: usage.totalTokens } : {}),
|
|
2641
|
+
...(usage.cost && Object.keys(usage.cost).length > 0 ? { cost: usage.cost } : {}),
|
|
2642
|
+
...(usage.cost?.total !== undefined ? { cost_usd: usage.cost.total } : {}),
|
|
2643
|
+
metadata: sanitizeRuntimeUsageMetadata(metadata),
|
|
2644
|
+
};
|
|
2645
|
+
|
|
2646
|
+
const url = new URL(
|
|
2647
|
+
"/v1/runtime/deploys/" + encodeURIComponent(String(env.AGENTKIT_DEPLOY_ID)) + "/usage",
|
|
2648
|
+
env.AGENTKIT_CLOUD_API_URL,
|
|
2649
|
+
);
|
|
2650
|
+
await fetch(url.href, {
|
|
2651
|
+
method: "POST",
|
|
2652
|
+
headers: {
|
|
2653
|
+
Authorization: "Bearer " + env.AGENTKIT_RUNTIME_GATE_TOKEN,
|
|
2654
|
+
"Content-Type": "application/json",
|
|
2655
|
+
},
|
|
2656
|
+
body: JSON.stringify(body),
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function sanitizeRuntimeUsageMetadata(value) {
|
|
2661
|
+
const metadata = {};
|
|
2662
|
+
|
|
2663
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
2664
|
+
if (!/^[A-Za-z0-9_.:-]{1,80}$/.test(key)) {
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
if (typeof raw === "string") {
|
|
2669
|
+
metadata[key] = raw.length > 160 ? raw.slice(0, 160) : raw;
|
|
2670
|
+
} else if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
2671
|
+
metadata[key] = raw;
|
|
2672
|
+
} else if (typeof raw === "boolean") {
|
|
2673
|
+
metadata[key] = raw;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
return metadata;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
async function loadConversationMessages(env, conversationId) {
|
|
2681
|
+
if (!env.AGENTKIT_CONVERSATIONS || !conversationId) {
|
|
2682
|
+
return [];
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
const stub = hostedConversationStore(env);
|
|
2686
|
+
const response = await stub.fetch(
|
|
2687
|
+
\`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
|
|
2688
|
+
);
|
|
2689
|
+
|
|
2690
|
+
if (!response.ok) {
|
|
2691
|
+
return [];
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
const payload = await response.json();
|
|
2695
|
+
const messages = Array.isArray(payload.messages)
|
|
2696
|
+
? payload.messages.filter((message) =>
|
|
2697
|
+
message &&
|
|
2698
|
+
typeof message === "object" &&
|
|
2699
|
+
(message.role === "user" || message.role === "assistant") &&
|
|
2700
|
+
typeof message.content === "string"
|
|
2701
|
+
)
|
|
2702
|
+
: [];
|
|
2703
|
+
|
|
2704
|
+
if (messages.length > 0) {
|
|
2705
|
+
return messages;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
const legacyStub = legacyConversationStore(env, conversationId);
|
|
2709
|
+
const legacyResponse = await legacyStub.fetch(
|
|
2710
|
+
\`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
|
|
2711
|
+
);
|
|
2712
|
+
|
|
2713
|
+
if (!legacyResponse.ok) {
|
|
2714
|
+
return [];
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
const legacyPayload = await legacyResponse.json();
|
|
2718
|
+
return Array.isArray(legacyPayload.messages)
|
|
2719
|
+
? legacyPayload.messages.filter((message) =>
|
|
2720
|
+
message &&
|
|
2721
|
+
typeof message === "object" &&
|
|
2722
|
+
(message.role === "user" || message.role === "assistant") &&
|
|
2723
|
+
typeof message.content === "string"
|
|
2724
|
+
)
|
|
2725
|
+
: [];
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
function hostedConversationStore(env) {
|
|
2729
|
+
const id = env.AGENTKIT_CONVERSATIONS.idFromName("agentkit-conversations");
|
|
2730
|
+
return env.AGENTKIT_CONVERSATIONS.get(id);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
function legacyConversationStore(env, conversationId) {
|
|
2734
|
+
const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
|
|
2735
|
+
return env.AGENTKIT_CONVERSATIONS.get(id);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
function normalizeConversationId(value) {
|
|
2739
|
+
if (value === undefined || value === null) {
|
|
2740
|
+
return null;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
if (typeof value !== "string") {
|
|
2744
|
+
throw agentKitError("validation_error", "conversationId must be a non-empty string when provided.");
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
const conversationId = value.trim();
|
|
2748
|
+
if (!conversationId) {
|
|
2749
|
+
throw agentKitError("validation_error", "conversationId must be a non-empty string when provided.");
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
return conversationId;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
1569
2755
|
function agentKitError(code, message) {
|
|
1570
2756
|
const error = new Error(message);
|
|
1571
2757
|
error.code = code;
|
|
@@ -1823,6 +3009,80 @@ function titleFromMessage(content) {
|
|
|
1823
3009
|
const compact = content.split(/\\r?\\n/, 1)[0].replace(/\\s+/g, " ").trim();
|
|
1824
3010
|
return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
|
|
1825
3011
|
}
|
|
3012
|
+
|
|
3013
|
+
function readStoredUsage(row) {
|
|
3014
|
+
const usage = {};
|
|
3015
|
+
const inputTokens = normalizeRuntimeInteger(row.input_tokens);
|
|
3016
|
+
const outputTokens = normalizeRuntimeInteger(row.output_tokens);
|
|
3017
|
+
const totalTokens = normalizeRuntimeInteger(row.total_tokens);
|
|
3018
|
+
const cost = normalizeRuntimeCost(parseStoredJson(row.cost_json, {}));
|
|
3019
|
+
|
|
3020
|
+
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3021
|
+
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3022
|
+
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3023
|
+
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3024
|
+
|
|
3025
|
+
return usage;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
function normalizeRuntimeUsage(value) {
|
|
3029
|
+
if (!isRecord(value)) {
|
|
3030
|
+
return {};
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
const usage = {};
|
|
3034
|
+
const inputTokens = normalizeRuntimeInteger(value.inputTokens ?? value.input_tokens);
|
|
3035
|
+
const outputTokens = normalizeRuntimeInteger(value.outputTokens ?? value.output_tokens);
|
|
3036
|
+
const totalTokens = normalizeRuntimeInteger(value.totalTokens ?? value.total_tokens);
|
|
3037
|
+
const cost = normalizeRuntimeCost(value.cost);
|
|
3038
|
+
|
|
3039
|
+
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3040
|
+
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3041
|
+
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3042
|
+
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3043
|
+
|
|
3044
|
+
return usage;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function normalizeRuntimeCost(value) {
|
|
3048
|
+
if (!isRecord(value)) {
|
|
3049
|
+
return {};
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
const cost = {};
|
|
3053
|
+
|
|
3054
|
+
for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
3055
|
+
const normalized = normalizeRuntimeNumber(value[field]);
|
|
3056
|
+
|
|
3057
|
+
if (normalized !== undefined) {
|
|
3058
|
+
cost[field] = normalized;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
return cost;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
function normalizeRuntimeInteger(value) {
|
|
3066
|
+
const normalized = normalizeRuntimeNumber(value);
|
|
3067
|
+
return normalized !== undefined && Number.isInteger(normalized) ? normalized : undefined;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
function normalizeRuntimeNumber(value) {
|
|
3071
|
+
const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
3072
|
+
return typeof parsed === "number" && Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function parseStoredJson(value, fallback) {
|
|
3076
|
+
if (typeof value !== "string") {
|
|
3077
|
+
return fallback;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
try {
|
|
3081
|
+
return JSON.parse(value);
|
|
3082
|
+
} catch {
|
|
3083
|
+
return fallback;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
1826
3086
|
`;
|
|
1827
3087
|
}
|
|
1828
3088
|
|
|
@@ -1893,6 +3153,10 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
1893
3153
|
status TEXT NOT NULL,
|
|
1894
3154
|
error_code TEXT,
|
|
1895
3155
|
error_message TEXT,
|
|
3156
|
+
input_tokens INTEGER,
|
|
3157
|
+
output_tokens INTEGER,
|
|
3158
|
+
total_tokens INTEGER,
|
|
3159
|
+
cost_json TEXT,
|
|
1896
3160
|
created_at TEXT NOT NULL,
|
|
1897
3161
|
completed_at TEXT
|
|
1898
3162
|
);
|
|
@@ -1906,6 +3170,7 @@ CREATE TABLE IF NOT EXISTS tool_calls (
|
|
|
1906
3170
|
status TEXT NOT NULL,
|
|
1907
3171
|
input_json TEXT NOT NULL,
|
|
1908
3172
|
output_json TEXT,
|
|
3173
|
+
rendered_json TEXT,
|
|
1909
3174
|
error_message TEXT,
|
|
1910
3175
|
created_at TEXT NOT NULL,
|
|
1911
3176
|
completed_at TEXT
|