@andreprado/agentkit 0.1.0-alpha.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +18 -1
  2. package/docs/guides/add-channel.md +251 -7
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +165 -0
  5. package/docs/guides/add-tool.md +10 -3
  6. package/docs/guides/channel-security.md +162 -32
  7. package/docs/guides/connect-discord.md +178 -0
  8. package/docs/guides/connect-slack.md +126 -0
  9. package/docs/guides/connect-telegram.md +61 -1
  10. package/docs/guides/connect-whatsapp-evolution.md +121 -0
  11. package/docs/guides/connect-whatsapp-uazapi.md +139 -0
  12. package/docs/guides/connect-whatsapp-zapster.md +119 -16
  13. package/docs/guides/create-agent.md +31 -4
  14. package/docs/guides/debug-channel.md +159 -0
  15. package/docs/guides/improve-from-production.md +151 -0
  16. package/docs/guides/prepare-deploy.md +32 -14
  17. package/docs/guides/replay-production-traces.md +72 -0
  18. package/docs/guides/run-evals.md +95 -25
  19. package/docs/guides/security-rules.md +9 -5
  20. package/docs/guides/send-feedback.md +135 -0
  21. package/docs/guides/use-jev.md +67 -0
  22. package/docs/guides/use-provider.md +70 -3
  23. package/docs/llms-full.txt +295 -25
  24. package/docs/llms.txt +54 -7
  25. package/package.json +3 -7
  26. package/src/cli/args.ts +23 -2
  27. package/src/cli/cloud-client.ts +121 -9
  28. package/src/cli/commands/channels.ts +1185 -50
  29. package/src/cli/commands/feedback.ts +438 -0
  30. package/src/cli/commands/provider.ts +47 -0
  31. package/src/cli/commands/transcribe.ts +171 -0
  32. package/src/cli/deploy-chat-ui.ts +232 -18
  33. package/src/cli/deploy-readiness.ts +227 -14
  34. package/src/cli/help.ts +68 -8
  35. package/src/cli/index.ts +740 -35
  36. package/src/cli/new-command.ts +41 -0
  37. package/src/cloud/client.ts +4 -3
  38. package/src/cloud/contracts.ts +1 -1
  39. package/src/create-project.ts +18 -35
  40. package/src/index.ts +565 -11
  41. package/src/providers/codex-auth.ts +111 -0
  42. package/src/providers/pi.ts +88 -19
  43. package/src/providers/test.ts +36 -0
  44. package/src/providers/types.ts +8 -0
  45. package/src/runtime/channel-test-harness.ts +21 -1
  46. package/src/runtime/channels/discord.ts +896 -0
  47. package/src/runtime/channels/generic-webhook.ts +974 -0
  48. package/src/runtime/channels/slack.ts +646 -0
  49. package/src/runtime/channels/telegram.ts +466 -23
  50. package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
  51. package/src/runtime/channels/whatsapp-meta.ts +9 -0
  52. package/src/runtime/channels/whatsapp-uazapi.ts +1323 -0
  53. package/src/runtime/channels/whatsapp-zapster.ts +674 -40
  54. package/src/runtime/channels.ts +89 -4
  55. package/src/runtime/chat.ts +70 -44
  56. package/src/runtime/config.ts +489 -20
  57. package/src/runtime/core/manifest.ts +75 -5
  58. package/src/runtime/core/targets.ts +5 -5
  59. package/src/runtime/deploy-readiness.ts +34 -4
  60. package/src/runtime/dev-server.ts +639 -39
  61. package/src/runtime/env.ts +8 -3
  62. package/src/runtime/evals.ts +445 -74
  63. package/src/runtime/improve.ts +868 -0
  64. package/src/runtime/inspect.ts +173 -4
  65. package/src/runtime/integrations/composio.ts +425 -0
  66. package/src/runtime/knowledge/retrieve.ts +25 -5
  67. package/src/runtime/knowledge/schema.ts +45 -1
  68. package/src/runtime/prompt-context.ts +141 -0
  69. package/src/runtime/runtime-contract.ts +71 -7
  70. package/src/runtime/skills.ts +95 -0
  71. package/src/runtime/targets/cloudflare/build.ts +1010 -208
  72. package/src/runtime/targets/container/server.ts +1 -1
  73. package/src/runtime/targets/vps/deploy.ts +26 -9
  74. package/src/runtime/tool-runner.ts +9 -1
  75. package/src/runtime/tools.ts +26 -2
  76. package/src/runtime/transcription.ts +483 -0
  77. package/src/storage/sqlite.ts +7 -2
  78. package/src/templates/blank.ts +37 -9
  79. package/src/templates/dentista.ts +40 -14
  80. package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
  81. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
  82. package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
  83. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
  84. package/src/templates/skills/agentkit-channels/SKILL.md +71 -6
  85. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +9 -2
  86. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +34 -5
  87. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  88. package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
  89. package/src/templates/skills/agentkit-channels/references/telegram.md +39 -4
  90. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
  91. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
  92. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
  93. package/src/templates/skills/agentkit-database/SKILL.md +11 -0
  94. package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
  95. package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
  96. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
  97. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
  98. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
  99. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
  100. package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
  101. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  102. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  103. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  104. package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
  105. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  106. package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
  107. package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
  108. package/src/templates/skills/agentkit-security/SKILL.md +5 -2
  109. package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
  110. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
  111. package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
  112. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
  113. package/src/templates/support.ts +42 -12
  114. package/docs/guides/agentkit-skills-architecture.md +0 -471
  115. package/docs/guides/channels-implementation-map.md +0 -243
  116. package/docs/guides/channels-production-handoff.md +0 -101
  117. package/docs/portable-deploy-release-checklist.md +0 -41
@@ -32,10 +32,13 @@ export type AgentManifest = {
32
32
  runtime: "edge";
33
33
  target: "cloudflare";
34
34
  provider: SharedAgentManifest["provider"];
35
+ timeZone: SharedAgentManifest["timeZone"];
35
36
  prompt: SharedAgentManifest["prompt"];
36
37
  secrets: SharedAgentManifest["secrets"];
37
38
  tools: SharedAgentManifest["tools"];
39
+ integrations: SharedAgentManifest["integrations"];
38
40
  channels: SharedAgentManifest["channels"];
41
+ transcription: SharedAgentManifest["transcription"];
39
42
  knowledge: SharedAgentManifest["knowledge"];
40
43
  access: SharedAgentManifest["access"];
41
44
  storage: {
@@ -192,10 +195,13 @@ function buildManifest(
192
195
  runtime: "edge",
193
196
  target: "cloudflare",
194
197
  provider: shared.provider,
198
+ timeZone: shared.timeZone,
195
199
  prompt: shared.prompt,
196
200
  secrets: shared.secrets,
197
201
  tools: shared.tools,
202
+ integrations: shared.integrations,
198
203
  channels: shared.channels,
204
+ transcription: shared.transcription,
199
205
  knowledge: shared.knowledge,
200
206
  access: shared.access,
201
207
  storage: {
@@ -314,8 +320,8 @@ async function listSourceFiles(root: string): Promise<string[]> {
314
320
  function findImportSpecifiers(source: string): string[] {
315
321
  const specifiers: string[] = [];
316
322
  const patterns = [
317
- /\bimport\s+(?:type\s+)?(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
318
- /\bexport\s+[^"'()]*?\s+from\s+["']([^"']+)["']/g,
323
+ /\bimport\s+(?!type\b)(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
324
+ /\bexport\s+(?!type\b)[^"'()]*?\s+from\s+["']([^"']+)["']/g,
319
325
  /\brequire\(\s*["']([^"']+)["']\s*\)/g,
320
326
  /\bimport\(\s*["']([^"']+)["']\s*\)/g,
321
327
  ];
@@ -379,6 +385,26 @@ export function defineTool(tool) {
379
385
  return tool;
380
386
  }
381
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
+
382
408
  export function websiteChannel(input) {
383
409
  return {
384
410
  type: "website",
@@ -414,9 +440,72 @@ export function whatsappChannel(input) {
414
440
  };
415
441
  }
416
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
+ direction: "inbound",
470
+ name: input.name,
471
+ secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBHOOK_SECRET"], "generic webhook channel"),
472
+ ...(input.limits ? { limits: input.limits } : {}),
473
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "generic webhook channel") } : {}),
474
+ ...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "generic webhook channel") } : {}),
475
+ };
476
+ }
477
+
478
+ export function webhookOutputChannel(input) {
479
+ const auth = normalizeGenericWebhookOutputAuth(input.auth ?? { type: "none" }, "generic webhook output channel");
480
+ const requiredSecrets = [
481
+ input.urlSecret,
482
+ ...genericWebhookOutputAuthSecrets(auth),
483
+ ...(input.secrets ?? []),
484
+ ];
485
+
486
+ return {
487
+ type: "webhook",
488
+ provider: "generic",
489
+ direction: "outbound",
490
+ name: input.name,
491
+ urlSecret: normalizeSecretName(input.urlSecret, "generic webhook output channel urlSecret"),
492
+ auth,
493
+ secrets: normalizeChannelSecrets(requiredSecrets, "generic webhook output channel"),
494
+ ...(input.limits ? { limits: input.limits } : {}),
495
+ };
496
+ }
497
+
417
498
  function defaultWhatsappChannelSecrets(provider) {
418
499
  if (provider === "zapster") {
419
- return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
500
+ return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"];
501
+ }
502
+
503
+ if (provider === "uazapi") {
504
+ return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"];
505
+ }
506
+
507
+ if (provider === "evolution") {
508
+ return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
420
509
  }
421
510
 
422
511
  return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
@@ -426,14 +515,199 @@ function normalizeChannelSecrets(secrets, context) {
426
515
  const uniqueSecrets = new Set();
427
516
 
428
517
  for (const secret of secrets) {
429
- if (!/^[A-Z_][A-Z0-9_]*$/.test(secret)) {
430
- throw new Error(\`\${context} secrets must be environment variable names, not secret values.\`);
518
+ uniqueSecrets.add(normalizeSecretName(secret, \`\${context} secrets\`));
519
+ }
520
+
521
+ return Array.from(uniqueSecrets).sort();
522
+ }
523
+
524
+ function normalizeSecretName(secret, context) {
525
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(secret)) {
526
+ if (context.endsWith("secrets")) {
527
+ throw new Error(\`\${context} must be environment variable names, not secret values.\`);
431
528
  }
432
529
 
433
- uniqueSecrets.add(secret);
530
+ throw new Error(\`\${context} must be an environment variable name, not a secret value.\`);
434
531
  }
435
532
 
436
- return Array.from(uniqueSecrets).sort();
533
+ return secret;
534
+ }
535
+
536
+ function normalizeChannelReplyRoute(input, context) {
537
+ const channel = input.channel.trim();
538
+
539
+ if (!/^[a-z][a-z0-9-]*$/.test(channel)) {
540
+ throw new Error(\`\${context} replyTo.channel must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
541
+ }
542
+
543
+ return {
544
+ channel,
545
+ ...(input.recipientFrom ? { recipientFrom: normalizeJsonPath(input.recipientFrom, \`\${context} replyTo.recipientFrom\`) } : {}),
546
+ };
547
+ }
548
+
549
+ function normalizeJsonPath(value, context) {
550
+ const path = value.trim().replace(/^payload\\./, "");
551
+
552
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
553
+ throw new Error(\`\${context} must be a dotted JSON field path such as "phone" or "customer.phone".\`);
554
+ }
555
+
556
+ return path;
557
+ }
558
+
559
+ function normalizeGenericWebhookOutputAuth(auth, context) {
560
+ if (auth.type === "none") {
561
+ return { type: "none" };
562
+ }
563
+
564
+ if (auth.type === "bearer") {
565
+ return {
566
+ type: "bearer",
567
+ tokenSecret: normalizeSecretName(auth.tokenSecret, \`\${context} auth.tokenSecret\`),
568
+ };
569
+ }
570
+
571
+ if (auth.type === "header") {
572
+ if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
573
+ throw new Error(\`\${context} auth.headerName must be an HTTP header name.\`);
574
+ }
575
+
576
+ return {
577
+ type: "header",
578
+ headerName: auth.headerName,
579
+ valueSecret: normalizeSecretName(auth.valueSecret, \`\${context} auth.valueSecret\`),
580
+ };
581
+ }
582
+
583
+ if (auth.type === "hmac") {
584
+ if (auth.headerName !== undefined && !/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
585
+ throw new Error(\`\${context} auth.headerName must be an HTTP header name.\`);
586
+ }
587
+
588
+ return {
589
+ type: "hmac",
590
+ secret: normalizeSecretName(auth.secret, \`\${context} auth.secret\`),
591
+ ...(auth.headerName ? { headerName: auth.headerName } : {}),
592
+ };
593
+ }
594
+
595
+ throw new Error(\`\${context} auth.type must be "none", "bearer", "header", or "hmac".\`);
596
+ }
597
+
598
+ function genericWebhookOutputAuthSecrets(auth) {
599
+ if (auth.type === "bearer") {
600
+ return [auth.tokenSecret];
601
+ }
602
+
603
+ if (auth.type === "header") {
604
+ return [auth.valueSecret];
605
+ }
606
+
607
+ if (auth.type === "hmac") {
608
+ return [auth.secret];
609
+ }
610
+
611
+ return [];
612
+ }
613
+
614
+ function normalizeIntegrationName(value, context) {
615
+ const name = value.trim();
616
+
617
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
618
+ throw new Error(\`\${context} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
619
+ }
620
+
621
+ return name;
622
+ }
623
+
624
+ function normalizeToolkitList(values, context) {
625
+ if (!Array.isArray(values) || values.length === 0) {
626
+ throw new Error(\`\${context} must include at least one toolkit slug.\`);
627
+ }
628
+
629
+ const toolkits = new Set();
630
+
631
+ for (const value of values) {
632
+ if (typeof value !== "string") {
633
+ throw new Error(\`\${context} entries must be toolkit slug strings.\`);
634
+ }
635
+
636
+ const toolkit = value.trim().toLowerCase();
637
+
638
+ if (!/^[a-z][a-z0-9_-]*$/.test(toolkit)) {
639
+ throw new Error(\`\${context} entries must start with a letter and contain only letters, numbers, underscores, and hyphens.\`);
640
+ }
641
+
642
+ toolkits.add(toolkit);
643
+ }
644
+
645
+ return Array.from(toolkits).sort();
646
+ }
647
+
648
+ function normalizeComposioToolMap(values, toolkits) {
649
+ const toolkitSet = new Set(toolkits);
650
+ const normalized = {};
651
+
652
+ for (const [rawToolkit, rawTools] of Object.entries(values)) {
653
+ const toolkit = rawToolkit.trim().toLowerCase();
654
+
655
+ if (!toolkitSet.has(toolkit)) {
656
+ throw new Error(\`composioManaged.tools.\${rawToolkit} must reference a toolkit listed in composioManaged.toolkits.\`);
657
+ }
658
+
659
+ if (!Array.isArray(rawTools) || rawTools.length === 0) {
660
+ throw new Error(\`composioManaged.tools.\${rawToolkit} must include at least one Composio action slug.\`);
661
+ }
662
+
663
+ const tools = new Set();
664
+
665
+ for (const rawTool of rawTools) {
666
+ if (typeof rawTool !== "string") {
667
+ throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be Composio action slug strings.\`);
668
+ }
669
+
670
+ const tool = rawTool.trim().toUpperCase();
671
+
672
+ if (!/^[A-Z][A-Z0-9_]*$/.test(tool)) {
673
+ throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be uppercase Composio action slugs.\`);
674
+ }
675
+
676
+ tools.add(tool);
677
+ }
678
+
679
+ normalized[toolkit] = Array.from(tools).sort();
680
+ }
681
+
682
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
683
+ }
684
+
685
+ function normalizeIntegrationTags(tags) {
686
+ const normalized = {};
687
+
688
+ if (tags.enable !== undefined) {
689
+ normalized.enable = normalizeTagList(tags.enable, "composioManaged.tags.enable");
690
+ }
691
+
692
+ if (tags.disable !== undefined) {
693
+ normalized.disable = normalizeTagList(tags.disable, "composioManaged.tags.disable");
694
+ }
695
+
696
+ return normalized;
697
+ }
698
+
699
+ function normalizeTagList(values, context) {
700
+ if (!Array.isArray(values)) {
701
+ throw new Error(\`\${context} must be an array of tag strings.\`);
702
+ }
703
+
704
+ return Array.from(new Set(values.map((value) => {
705
+ if (typeof value !== "string" || value.trim().length === 0) {
706
+ throw new Error(\`\${context} entries must be non-empty strings.\`);
707
+ }
708
+
709
+ return value.trim();
710
+ }))).sort();
437
711
  }
438
712
 
439
713
  function normalizeChannelBuffer(input, context) {
@@ -538,15 +812,18 @@ function renderCloudflareWorker(input: { manifest: AgentManifest; instructions:
538
812
  const instructionsJson = JSON.stringify(input.instructions);
539
813
  const configImportPath = JSON.stringify(input.configPath);
540
814
  const providerImportPath = JSON.stringify(resolve(moduleDir, "../../../providers/index.ts"));
815
+ const composioIntegrationImportPath = JSON.stringify(resolve(moduleDir, "../../integrations/composio.ts"));
816
+ const promptContextImportPath = JSON.stringify(resolve(moduleDir, "../../prompt-context.ts"));
541
817
 
542
818
  return `import { DurableObject } from "cloudflare:workers";
543
819
  import agentConfig from ${configImportPath};
544
820
  import { getProviderAdapter } from ${providerImportPath};
821
+ import { MANAGED_COMPOSIO_USER_ID_BINDING, createManagedComposioTools } from ${composioIntegrationImportPath};
822
+ import { appendRuntimePromptContext, buildRuntimeClockContext } from ${promptContextImportPath};
545
823
 
546
824
  const manifest = ${manifestJson};
547
825
  const instructions = ${instructionsJson};
548
826
  const hostedKnowledgeTool = manifest.knowledge.enabled ? createHostedKnowledgeSearchTool() : null;
549
- const hostedTools = hostedKnowledgeTool ? [hostedKnowledgeTool, ...(agentConfig.tools ?? [])] : (agentConfig.tools ?? []);
550
827
  const DEFAULT_TOOL_TIMEOUT_MS = 30000;
551
828
 
552
829
  export class AgentKitConversationStore extends DurableObject {
@@ -576,6 +853,10 @@ export class AgentKitConversationStore extends DurableObject {
576
853
  status TEXT NOT NULL,
577
854
  error_code TEXT,
578
855
  error_message TEXT,
856
+ input_tokens INTEGER,
857
+ output_tokens INTEGER,
858
+ total_tokens INTEGER,
859
+ cost_json TEXT,
579
860
  created_at TEXT NOT NULL,
580
861
  completed_at TEXT
581
862
  );
@@ -588,11 +869,29 @@ export class AgentKitConversationStore extends DurableObject {
588
869
  status TEXT NOT NULL,
589
870
  input_json TEXT NOT NULL,
590
871
  output_json TEXT,
872
+ rendered_json TEXT,
591
873
  error_message TEXT,
592
874
  created_at TEXT NOT NULL,
593
875
  completed_at TEXT
594
876
  );
595
877
  \`);
878
+ try {
879
+ this.sql.exec("ALTER TABLE tool_calls ADD COLUMN rendered_json TEXT");
880
+ } catch {
881
+ // Existing Durable Object tables either already have this column or do not need migration.
882
+ }
883
+ for (const statement of [
884
+ "ALTER TABLE runs ADD COLUMN input_tokens INTEGER",
885
+ "ALTER TABLE runs ADD COLUMN output_tokens INTEGER",
886
+ "ALTER TABLE runs ADD COLUMN total_tokens INTEGER",
887
+ "ALTER TABLE runs ADD COLUMN cost_json TEXT",
888
+ ]) {
889
+ try {
890
+ this.sql.exec(statement);
891
+ } catch {
892
+ // Existing Durable Object tables either already have this column or do not need migration.
893
+ }
894
+ }
596
895
  }
597
896
 
598
897
  async fetch(request) {
@@ -606,6 +905,22 @@ export class AgentKitConversationStore extends DurableObject {
606
905
  return this.getMessages(url.searchParams.get("conversationId"));
607
906
  }
608
907
 
908
+ if (request.method === "GET" && url.pathname === "/conversations") {
909
+ return this.listConversations();
910
+ }
911
+
912
+ const conversationTraceMatch = url.pathname.match(/^\\/conversations\\/([^/]+)\\/trace$/);
913
+
914
+ if (request.method === "GET" && conversationTraceMatch) {
915
+ return this.getConversationTrace(decodeURIComponent(conversationTraceMatch[1] || ""));
916
+ }
917
+
918
+ const conversationMatch = url.pathname.match(/^\\/conversations\\/([^/]+)$/);
919
+
920
+ if (request.method === "GET" && conversationMatch) {
921
+ return this.getConversation(decodeURIComponent(conversationMatch[1] || ""));
922
+ }
923
+
609
924
  return Response.json({ error: { code: "not_found", message: "Unknown Durable Object route." } }, { status: 404 });
610
925
  }
611
926
 
@@ -629,6 +944,130 @@ export class AgentKitConversationStore extends DurableObject {
629
944
  });
630
945
  }
631
946
 
947
+ listConversations() {
948
+ const rows = this.sql.exec(\`
949
+ SELECT conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at, COUNT(messages.id) AS message_count
950
+ FROM conversations
951
+ LEFT JOIN messages ON messages.conversation_id = conversations.id
952
+ GROUP BY conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at
953
+ ORDER BY conversations.updated_at DESC
954
+ \`).toArray();
955
+
956
+ return Response.json({
957
+ conversations: rows.map((row) => ({
958
+ id: String(row.id),
959
+ agentName: String(row.agent_name),
960
+ title: row.title === null || row.title === undefined ? null : String(row.title),
961
+ createdAt: String(row.created_at),
962
+ updatedAt: String(row.updated_at),
963
+ messageCount: Number(row.message_count || 0),
964
+ })),
965
+ });
966
+ }
967
+
968
+ getConversation(conversationId) {
969
+ const conversation = this.readConversation(conversationId);
970
+
971
+ if (!conversation) {
972
+ return Response.json(
973
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
974
+ { status: 404 },
975
+ );
976
+ }
977
+
978
+ return Response.json(conversation);
979
+ }
980
+
981
+ getConversationTrace(conversationId) {
982
+ const conversation = this.readConversation(conversationId);
983
+
984
+ if (!conversation) {
985
+ return Response.json(
986
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
987
+ { status: 404 },
988
+ );
989
+ }
990
+
991
+ const runs = this.sql.exec(
992
+ "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",
993
+ conversationId,
994
+ ).toArray();
995
+
996
+ return Response.json({
997
+ ...conversation,
998
+ runs: runs.map((run) => ({
999
+ id: String(run.id),
1000
+ conversationId: String(run.conversation_id),
1001
+ provider: String(run.provider),
1002
+ model: String(run.model),
1003
+ status: String(run.status),
1004
+ errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
1005
+ errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
1006
+ usage: readStoredUsage(run),
1007
+ startedAt: String(run.created_at),
1008
+ completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
1009
+ toolCalls: this.readToolCalls(String(run.id)),
1010
+ })),
1011
+ });
1012
+ }
1013
+
1014
+ readConversation(conversationId) {
1015
+ if (!conversationId) {
1016
+ return null;
1017
+ }
1018
+
1019
+ const row = this.sql.exec(
1020
+ "SELECT id, agent_name, title, created_at, updated_at FROM conversations WHERE id = ?",
1021
+ conversationId,
1022
+ ).one();
1023
+
1024
+ if (!row) {
1025
+ return null;
1026
+ }
1027
+
1028
+ const messages = this.sql.exec(
1029
+ "SELECT id, conversation_id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
1030
+ conversationId,
1031
+ ).toArray();
1032
+
1033
+ return {
1034
+ id: String(row.id),
1035
+ agentName: String(row.agent_name),
1036
+ title: row.title === null || row.title === undefined ? null : String(row.title),
1037
+ createdAt: String(row.created_at),
1038
+ updatedAt: String(row.updated_at),
1039
+ messages: messages.map((message) => ({
1040
+ id: String(message.id),
1041
+ conversationId: String(message.conversation_id),
1042
+ role: String(message.role),
1043
+ content: String(message.content),
1044
+ createdAt: String(message.created_at),
1045
+ })),
1046
+ };
1047
+ }
1048
+
1049
+ readToolCalls(runId) {
1050
+ const rows = this.sql.exec(
1051
+ "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",
1052
+ runId,
1053
+ ).toArray();
1054
+
1055
+ return rows.map((row) => ({
1056
+ id: String(row.id),
1057
+ runId: String(row.run_id),
1058
+ conversationId: String(row.conversation_id),
1059
+ name: String(row.tool_name),
1060
+ visibility: String(row.visibility),
1061
+ status: String(row.status),
1062
+ input: parseStoredJson(row.input_json, {}),
1063
+ output: row.output_json === null || row.output_json === undefined ? undefined : parseStoredJson(row.output_json, null),
1064
+ rendered: row.rendered_json === null || row.rendered_json === undefined ? undefined : parseStoredJson(row.rendered_json, null),
1065
+ errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
1066
+ createdAt: String(row.created_at),
1067
+ completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
1068
+ }));
1069
+ }
1070
+
632
1071
  persistMessages(input) {
633
1072
  const now = new Date().toISOString();
634
1073
  const conversationId = String(input.conversationId || crypto.randomUUID());
@@ -636,6 +1075,7 @@ export class AgentKitConversationStore extends DurableObject {
636
1075
  const userMessageId = crypto.randomUUID();
637
1076
  const assistantMessageId = crypto.randomUUID();
638
1077
  const title = titleFromMessage(String(input.userMessage || ""));
1078
+ const usage = normalizeRuntimeUsage(input.usage);
639
1079
 
640
1080
  this.sql.exec(
641
1081
  "INSERT OR IGNORE INTO conversations (id, agent_name, title, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
@@ -655,7 +1095,7 @@ export class AgentKitConversationStore extends DurableObject {
655
1095
  now,
656
1096
  );
657
1097
  this.sql.exec(
658
- "INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1098
+ "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
659
1099
  runId,
660
1100
  conversationId,
661
1101
  manifest.provider.name,
@@ -663,6 +1103,10 @@ export class AgentKitConversationStore extends DurableObject {
663
1103
  String(input.status || "completed"),
664
1104
  input.errorCode ? String(input.errorCode) : null,
665
1105
  input.errorMessage ? String(input.errorMessage) : null,
1106
+ usage.inputTokens ?? null,
1107
+ usage.outputTokens ?? null,
1108
+ usage.totalTokens ?? null,
1109
+ usage.cost && Object.keys(usage.cost).length > 0 ? JSON.stringify(usage.cost) : null,
666
1110
  now,
667
1111
  now,
668
1112
  );
@@ -680,7 +1124,7 @@ export class AgentKitConversationStore extends DurableObject {
680
1124
 
681
1125
  for (const toolCall of input.toolCalls || []) {
682
1126
  this.sql.exec(
683
- "INSERT INTO tool_calls (id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1127
+ "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
684
1128
  String(toolCall.id || crypto.randomUUID()),
685
1129
  runId,
686
1130
  conversationId,
@@ -689,6 +1133,7 @@ export class AgentKitConversationStore extends DurableObject {
689
1133
  String(toolCall.status || "completed"),
690
1134
  JSON.stringify(toolCall.input ?? {}),
691
1135
  toolCall.output === undefined ? null : JSON.stringify(toolCall.output),
1136
+ toolCall.rendered === undefined ? null : JSON.stringify(toolCall.rendered),
692
1137
  toolCall.errorMessage ? String(toolCall.errorMessage) : null,
693
1138
  now,
694
1139
  now,
@@ -705,7 +1150,7 @@ export class AgentKitConversationStore extends DurableObject {
705
1150
  }
706
1151
 
707
1152
  export default {
708
- async fetch(request, env) {
1153
+ async fetch(request, env, ctx) {
709
1154
  const url = new URL(request.url);
710
1155
 
711
1156
  if (request.method === "GET" && url.pathname === "/_agentkit") {
@@ -715,13 +1160,16 @@ export default {
715
1160
  return auth;
716
1161
  }
717
1162
 
1163
+ const hostedTools = createHostedTools(env);
1164
+
718
1165
  return Response.json({
719
1166
  agent: manifest.name,
720
1167
  runtime: manifest.runtime,
721
1168
  target: manifest.target,
722
1169
  provider: manifest.provider,
723
1170
  prompt: manifest.prompt,
724
- tools: manifest.tools.map((tool) => tool.name),
1171
+ tools: hostedTools.map((tool) => tool.name),
1172
+ integrations: hostedIntegrations(env),
725
1173
  channels: manifest.channels.map((channel) => ({
726
1174
  name: channel.name,
727
1175
  type: channel.type,
@@ -739,6 +1187,15 @@ export default {
739
1187
  }
740
1188
 
741
1189
  if (request.method === "GET" && url.pathname === "/") {
1190
+ const auth = await authorizeDeployAccess(request, env, {}, {
1191
+ notConfiguredCode: "cloud_access_not_configured",
1192
+ notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
1193
+ });
1194
+
1195
+ if (auth) {
1196
+ return auth;
1197
+ }
1198
+
742
1199
  return Response.json({
743
1200
  agent: manifest.name,
744
1201
  status: "ready",
@@ -748,7 +1205,16 @@ export default {
748
1205
  }
749
1206
 
750
1207
  if (request.method === "GET" && url.pathname === "/health") {
751
- return Response.json({ status: "ok", agent: manifest.name });
1208
+ const auth = await authorizeDeployAccess(request, env, {}, {
1209
+ notConfiguredCode: "cloud_access_not_configured",
1210
+ notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
1211
+ });
1212
+
1213
+ if (auth) {
1214
+ return auth;
1215
+ }
1216
+
1217
+ return Response.json({ ok: true });
752
1218
  }
753
1219
 
754
1220
  if (isHostedChannelWebhookPath(url.pathname)) {
@@ -756,19 +1222,27 @@ export default {
756
1222
  }
757
1223
 
758
1224
  if (request.method === "POST" && (url.pathname === "/chat" || url.pathname === "/v1/chat")) {
759
- return handleChat(request, env);
1225
+ return handleChat(request, env, ctx);
760
1226
  }
761
1227
 
762
- if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
763
- return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
1228
+ if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
1229
+ return handleConversationRead(request, env, "list");
1230
+ }
1231
+
1232
+ const conversationTraceMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)\\/trace$/);
1233
+
1234
+ if (request.method === "GET" && conversationTraceMatch) {
1235
+ return handleConversationRead(request, env, "trace", decodeURIComponent(conversationTraceMatch[1] || ""));
764
1236
  }
765
1237
 
766
- if (request.method === "GET" && url.pathname === "/_agentkit/turso/health") {
767
- return handleTursoHealth(request, env);
1238
+ const conversationMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)$/);
1239
+
1240
+ if (request.method === "GET" && conversationMatch) {
1241
+ return handleConversationRead(request, env, "show", decodeURIComponent(conversationMatch[1] || ""));
768
1242
  }
769
1243
 
770
- if (request.method === "POST" && url.pathname === "/_agentkit/turso/query") {
771
- return handleTursoQuery(request, env);
1244
+ if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
1245
+ return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
772
1246
  }
773
1247
 
774
1248
  if (url.pathname.startsWith("/files/")) {
@@ -781,8 +1255,10 @@ export default {
781
1255
 
782
1256
  function isHostedChannelWebhookPath(pathname) {
783
1257
  return (
784
- /^\\/channels\\/[^/]+\\/(?:website|telegram)\\/webhook$/.test(pathname) ||
785
- /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
1258
+ /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta|uazapi|evolution)|discord\\/discord|slack\\/slack|webhook\\/generic)\\/webhook$/.test(pathname) ||
1259
+ /^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack|webhook)\\/webhook$/.test(pathname) ||
1260
+ /^\\/channels\\/[^/]+\\/webhook$/.test(pathname) ||
1261
+ /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta|uazapi|evolution)\\/webhook$/.test(pathname)
786
1262
  );
787
1263
  }
788
1264
 
@@ -820,71 +1296,6 @@ async function forwardHostedChannelWebhook(request, env, url) {
820
1296
  });
821
1297
  }
822
1298
 
823
- async function handleTursoHealth(request, env) {
824
- const auth = authorizeAdmin(request, env);
825
-
826
- if (auth) {
827
- return auth;
828
- }
829
-
830
- const configError = requireTurso(env);
831
-
832
- if (configError) {
833
- return configError;
834
- }
835
-
836
- const response = await fetch(toTursoBaseUrl(env.TURSO_DATABASE_URL) + "/health", {
837
- headers: {
838
- Authorization: \`Bearer \${env.TURSO_AUTH_TOKEN}\`,
839
- },
840
- });
841
-
842
- return Response.json({
843
- driver: "turso",
844
- ok: response.ok,
845
- status: response.status,
846
- }, { status: response.ok ? 200 : 502 });
847
- }
848
-
849
- async function handleTursoQuery(request, env) {
850
- const auth = authorizeAdmin(request, env);
851
-
852
- if (auth) {
853
- return auth;
854
- }
855
-
856
- const configError = requireTurso(env);
857
-
858
- if (configError) {
859
- return configError;
860
- }
861
-
862
- let body;
863
-
864
- try {
865
- body = await request.json();
866
- } catch {
867
- return Response.json(
868
- { error: { code: "validation_error", message: "Expected JSON body with sql and optional args fields." } },
869
- { status: 400 },
870
- );
871
- }
872
-
873
- const sql = String(body.sql || "").trim();
874
-
875
- if (!sql) {
876
- return Response.json({ error: { code: "validation_error", message: "sql must be non-empty." } }, { status: 400 });
877
- }
878
-
879
- const args = Array.isArray(body.args) ? body.args : [];
880
- try {
881
- const result = await executeTursoSql(env, sql, args);
882
- return Response.json(result);
883
- } catch (error) {
884
- return hostedErrorResponse(error);
885
- }
886
- }
887
-
888
1299
  async function handleFile(request, env, rawKey) {
889
1300
  const auth = authorizeAdmin(request, env);
890
1301
 
@@ -1012,13 +1423,14 @@ async function handleTool(request, env, toolName) {
1012
1423
  },
1013
1424
  input: result.input,
1014
1425
  output: result.output,
1426
+ ...(result.rendered ? { rendered: result.rendered } : {}),
1015
1427
  });
1016
1428
  } catch (error) {
1017
1429
  return hostedErrorResponse(error);
1018
1430
  }
1019
1431
  }
1020
1432
 
1021
- async function handleChat(request, env) {
1433
+ async function handleChat(request, env, ctx) {
1022
1434
  let body;
1023
1435
 
1024
1436
  try {
@@ -1055,45 +1467,48 @@ async function handleChat(request, env) {
1055
1467
 
1056
1468
  const previousMessages = await loadConversationMessages(env, conversationId).catch(() => []);
1057
1469
  let content;
1470
+ let providerResult;
1058
1471
  const toolCalls = [];
1059
1472
 
1060
- const toolRequest = parseToolRequest(message);
1061
-
1062
- if (toolRequest) {
1063
- try {
1064
- const toolResult = await executeHostedTool({
1065
- name: toolRequest.name,
1066
- input: toolRequest.input,
1067
- env,
1068
- signal: request.signal,
1069
- invocation: "chat",
1070
- });
1071
- toolCalls.push(toolResult);
1072
- content =
1073
- toolResult.visibility === "internal"
1074
- ? \`Tool \${toolResult.name}: completed\`
1075
- : \`Tool \${toolResult.name}: \${JSON.stringify(toolResult.output)}\`;
1076
- } catch (error) {
1077
- await persistFailure(env, conversationId, message, error);
1078
- return hostedErrorResponse(error);
1079
- }
1080
- }
1081
-
1082
- if (content === undefined) {
1083
- try {
1084
- content = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal);
1085
- } catch (error) {
1086
- await persistFailure(env, conversationId, message, error);
1087
- return hostedErrorResponse(error);
1088
- }
1473
+ try {
1474
+ providerResult = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal, toolCalls);
1475
+ content = providerResult.content;
1476
+ } catch (error) {
1477
+ const persisted = await persistFailure(env, conversationId, message, error, toolCalls);
1478
+ queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
1479
+ kind: "chat_run",
1480
+ status: "failed",
1481
+ conversationId,
1482
+ runId: persisted?.runId,
1483
+ provider: manifest.provider.name,
1484
+ model: manifest.provider.model,
1485
+ metadata: {
1486
+ error_code: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
1487
+ tool_calls: toolCalls.length,
1488
+ },
1489
+ }));
1490
+ return hostedErrorResponse(error);
1089
1491
  }
1090
1492
 
1091
1493
  const persisted = await persist(env, {
1092
1494
  conversationId,
1093
1495
  userMessage: message,
1094
1496
  assistantMessage: content,
1497
+ usage: providerResult.usage,
1095
1498
  toolCalls,
1096
1499
  });
1500
+ queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
1501
+ kind: "chat_run",
1502
+ status: "succeeded",
1503
+ conversationId: persisted.conversationId ?? conversationId,
1504
+ runId: persisted.runId,
1505
+ provider: manifest.provider.name,
1506
+ model: manifest.provider.model,
1507
+ usage: providerResult.usage,
1508
+ metadata: {
1509
+ tool_calls: toolCalls.length,
1510
+ },
1511
+ }));
1097
1512
 
1098
1513
  return Response.json({
1099
1514
  message: {
@@ -1104,6 +1519,39 @@ async function handleChat(request, env) {
1104
1519
  });
1105
1520
  }
1106
1521
 
1522
+ async function handleConversationRead(request, env, action, conversationId) {
1523
+ const auth = await authorizeConversationRead(request, env);
1524
+
1525
+ if (auth) {
1526
+ return auth;
1527
+ }
1528
+
1529
+ if (!env.AGENTKIT_CONVERSATIONS) {
1530
+ return Response.json({ error: { code: "conversations_not_configured", message: "Hosted conversation storage is not configured." } }, { status: 503 });
1531
+ }
1532
+
1533
+ if (action === "list") {
1534
+ const stub = hostedConversationStore(env);
1535
+ return stub.fetch("https://agentkit.internal/conversations");
1536
+ }
1537
+
1538
+ if (!conversationId) {
1539
+ return Response.json({ error: { code: "validation_error", message: "Missing conversation id." } }, { status: 400 });
1540
+ }
1541
+
1542
+ const stub = hostedConversationStore(env);
1543
+ const suffix = action === "trace" ? "/trace" : "";
1544
+ const path = \`/conversations/\${encodeURIComponent(conversationId)}\${suffix}\`;
1545
+ const response = await stub.fetch(\`https://agentkit.internal\${path}\`);
1546
+
1547
+ if (response.status !== 404) {
1548
+ return response;
1549
+ }
1550
+
1551
+ const legacyStub = legacyConversationStore(env, conversationId);
1552
+ return legacyStub.fetch(\`https://agentkit.internal\${path}\`);
1553
+ }
1554
+
1107
1555
  function readChatMessage(body) {
1108
1556
  if (typeof body.message === "string") {
1109
1557
  return body.message.trim();
@@ -1120,23 +1568,36 @@ function isRecord(value) {
1120
1568
  return typeof value === "object" && value !== null && !Array.isArray(value);
1121
1569
  }
1122
1570
 
1123
- async function callHostedProvider(env, messages, signal) {
1571
+ async function callHostedProvider(env, messages, signal, toolCalls = []) {
1124
1572
  const adapter = getProviderAdapter(manifest.provider);
1573
+ const hostedTools = createHostedTools(env);
1574
+ const clock = buildRuntimeClockContext({
1575
+ timeZone: manifest.timeZone,
1576
+ env,
1577
+ });
1125
1578
  const result = await adapter.run({
1126
1579
  agent: {
1127
1580
  name: manifest.name,
1128
1581
  runtime: manifest.runtime,
1129
1582
  provider: manifest.provider,
1130
1583
  },
1131
- instructions: withToolVisibilityInstructions(appendHostedKnowledgePromptPolicy(instructions), hostedTools),
1584
+ instructions: appendRuntimePromptContext(
1585
+ withToolVisibilityInstructions(appendHostedKnowledgePromptPolicy(instructions), hostedTools),
1586
+ {
1587
+ clock,
1588
+ },
1589
+ ),
1132
1590
  messages,
1133
1591
  tools: hostedTools,
1134
- toolRuntime: createHostedProviderToolRuntime(env, signal),
1592
+ toolRuntime: createHostedProviderToolRuntime(env, signal, clock, toolCalls),
1135
1593
  env,
1136
1594
  signal,
1137
1595
  });
1138
1596
 
1139
- return result.message.content;
1597
+ return {
1598
+ content: result.message.content,
1599
+ usage: result.usage,
1600
+ };
1140
1601
  }
1141
1602
 
1142
1603
  function appendHostedKnowledgePromptPolicy(baseInstructions) {
@@ -1562,36 +2023,17 @@ async function hostedFakeEmbedding(input, dimensions) {
1562
2023
  return norm === 0 ? values : values.map((value) => Number((value / norm).toFixed(8)));
1563
2024
  }
1564
2025
 
1565
- async function persistFailure(env, conversationId, userMessage, error) {
1566
- await persist(env, {
2026
+ async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
2027
+ return persist(env, {
1567
2028
  conversationId,
1568
2029
  userMessage,
1569
2030
  status: "failed",
1570
2031
  errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
1571
2032
  errorMessage: error instanceof Error ? error.message : String(error),
1572
- toolCalls: [],
2033
+ toolCalls,
1573
2034
  }).catch(() => undefined);
1574
2035
  }
1575
2036
 
1576
- function parseToolRequest(content) {
1577
- let parsed;
1578
-
1579
- try {
1580
- parsed = JSON.parse(content);
1581
- } catch {
1582
- return null;
1583
- }
1584
-
1585
- if (!isRecord(parsed) || typeof parsed.tool !== "string" || parsed.tool.length === 0) {
1586
- return null;
1587
- }
1588
-
1589
- return {
1590
- name: parsed.tool,
1591
- input: "input" in parsed ? parsed.input : {},
1592
- };
1593
- }
1594
-
1595
2037
  function inspectHostedDatabase() {
1596
2038
  const schema = manifest.storage.database.driver === "turso" ? manifest.storage.database.schema : null;
1597
2039
 
@@ -1610,29 +2052,69 @@ function inspectHostedDatabase() {
1610
2052
  }
1611
2053
 
1612
2054
  function hostedManagedSecrets() {
1613
- if (manifest.storage.database.driver !== "turso") {
1614
- return [];
2055
+ const secrets = [];
2056
+
2057
+ if (manifest.storage.database.driver === "turso") {
2058
+ secrets.push("TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL");
2059
+ }
2060
+
2061
+ if (manifest.integrations.some((integration) => integration.provider === "composio" && integration.mode === "managed")) {
2062
+ secrets.push("COMPOSIO_API_KEY");
2063
+ }
2064
+
2065
+ return secrets.sort();
2066
+ }
2067
+
2068
+ function createHostedTools(env) {
2069
+ return [
2070
+ ...(hostedKnowledgeTool ? [hostedKnowledgeTool] : []),
2071
+ ...createManagedComposioTools(agentConfig, { userId: env[MANAGED_COMPOSIO_USER_ID_BINDING] }),
2072
+ ...(agentConfig.tools ?? []),
2073
+ ];
2074
+ }
2075
+
2076
+ function hostedIntegrations(env) {
2077
+ const userId = env[MANAGED_COMPOSIO_USER_ID_BINDING];
2078
+
2079
+ if (!userId) {
2080
+ return manifest.integrations;
1615
2081
  }
1616
2082
 
1617
- return ["TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL"];
2083
+ return manifest.integrations.map((integration) =>
2084
+ integration.provider === "composio" && integration.mode === "managed"
2085
+ ? { ...integration, userId }
2086
+ : integration,
2087
+ );
1618
2088
  }
1619
2089
 
1620
- function createHostedProviderToolRuntime(env, signal) {
2090
+ function createHostedProviderToolRuntime(env, signal, clock, toolCalls) {
2091
+ const hostedTools = createHostedTools(env);
2092
+
1621
2093
  return {
1622
2094
  definitions: hostedTools,
1623
- execute(request) {
1624
- return executeHostedTool({
1625
- name: request.name,
1626
- input: request.input,
1627
- env,
1628
- signal,
1629
- invocation: "chat",
1630
- });
2095
+ async execute(request) {
2096
+ try {
2097
+ const result = await executeHostedTool({
2098
+ tools: hostedTools,
2099
+ name: request.name,
2100
+ input: request.input,
2101
+ env,
2102
+ signal,
2103
+ invocation: "chat",
2104
+ clock,
2105
+ });
2106
+ toolCalls?.push(result);
2107
+ return result;
2108
+ } catch (error) {
2109
+ toolCalls?.push(failedHostedToolCall(hostedTools, request, error));
2110
+ throw error;
2111
+ }
1631
2112
  },
1632
2113
  };
1633
2114
  }
1634
2115
 
1635
2116
  async function executeHostedTool(request) {
2117
+ const hostedTools = request.tools ?? createHostedTools(request.env);
1636
2118
  const tool = hostedTools.find((candidate) => candidate.name === request.name);
1637
2119
  const id = crypto.randomUUID();
1638
2120
 
@@ -1642,9 +2124,20 @@ async function executeHostedTool(request) {
1642
2124
 
1643
2125
  const input = normalizeOptionalNulls(request.input ?? {}, tool.inputSchema);
1644
2126
  validateSchema(input, tool.inputSchema, \`tool "\${tool.name}" input\`);
2127
+ assertToolInvocationAuthorized(tool, request.invocation ?? "tool");
1645
2128
 
1646
2129
  const secrets = resolveToolSecrets(tool, request.env);
1647
2130
  const database = createHostedDatabaseRunner(request.env);
2131
+ const clock = request.clock ?? buildRuntimeClockContext({
2132
+ timeZone: manifest.timeZone,
2133
+ env: request.env,
2134
+ });
2135
+ const runtime = {
2136
+ environment: "hosted",
2137
+ invocation: request.invocation ?? "tool",
2138
+ target: "cloudflare",
2139
+ database: manifest.storage.database.driver === "turso" ? "turso" : "none",
2140
+ };
1648
2141
  const output = await runToolWithTimeout(
1649
2142
  tool,
1650
2143
  input,
@@ -1652,12 +2145,8 @@ async function executeHostedTool(request) {
1652
2145
  secrets,
1653
2146
  database,
1654
2147
  signal: request.signal,
1655
- runtime: {
1656
- environment: "hosted",
1657
- invocation: request.invocation ?? "tool",
1658
- target: "cloudflare",
1659
- database: manifest.storage.database.driver === "turso" ? "turso" : "none",
1660
- },
2148
+ clock,
2149
+ runtime,
1661
2150
  },
1662
2151
  tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
1663
2152
  );
@@ -1666,6 +2155,8 @@ async function executeHostedTool(request) {
1666
2155
  validateSchema(output, tool.outputSchema, \`tool "\${tool.name}" output\`);
1667
2156
  }
1668
2157
 
2158
+ const rendered = renderHostedToolOutput(tool, input, output, runtime, clock);
2159
+
1669
2160
  return {
1670
2161
  id,
1671
2162
  name: tool.name,
@@ -1673,9 +2164,152 @@ async function executeHostedTool(request) {
1673
2164
  status: "completed",
1674
2165
  input,
1675
2166
  output,
2167
+ ...(rendered ? { rendered } : {}),
1676
2168
  };
1677
2169
  }
1678
2170
 
2171
+ function assertToolInvocationAuthorized(tool, invocation) {
2172
+ if (invocation === "tool") {
2173
+ return;
2174
+ }
2175
+
2176
+ const privilegedPermissions = (tool.permissions ?? []).filter((permission) => !permission.endsWith(":read"));
2177
+
2178
+ if (privilegedPermissions.length === 0) {
2179
+ return;
2180
+ }
2181
+
2182
+ throw agentKitError(
2183
+ "tool_authorization_required",
2184
+ \`Tool "\${tool.name}" requires an explicit operator invocation because it declares privileged permissions: \${privilegedPermissions.join(", ")}.\`,
2185
+ );
2186
+ }
2187
+
2188
+ function failedHostedToolCall(tools, request, error) {
2189
+ const tool = tools.find((candidate) => candidate.name === request.name);
2190
+
2191
+ return {
2192
+ id: crypto.randomUUID(),
2193
+ name: String(request.name || ""),
2194
+ visibility: tool?.visibility ?? "user",
2195
+ status: "failed",
2196
+ input: request.input ?? {},
2197
+ errorMessage: error instanceof Error ? error.message : String(error),
2198
+ };
2199
+ }
2200
+
2201
+ function renderHostedToolOutput(tool, input, output, runtime, clock) {
2202
+ if (!tool.render) {
2203
+ return undefined;
2204
+ }
2205
+
2206
+ const rendered = tool.render(output, {
2207
+ input,
2208
+ runtime,
2209
+ clock,
2210
+ });
2211
+
2212
+ if (typeof rendered === "string") {
2213
+ return { text: rendered };
2214
+ }
2215
+
2216
+ if (!isRecord(rendered) || typeof rendered.text !== "string" || rendered.text.trim().length === 0) {
2217
+ throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render must return a non-empty text string.\`);
2218
+ }
2219
+
2220
+ if (rendered.blocks !== undefined) {
2221
+ if (!Array.isArray(rendered.blocks)) {
2222
+ throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render.blocks must be an array when provided.\`);
2223
+ }
2224
+
2225
+ for (const [index, block] of rendered.blocks.entries()) {
2226
+ validateRenderBlock(block, \`tool "\${tool.name}" render.blocks[\${index}]\`);
2227
+ }
2228
+ }
2229
+
2230
+ return {
2231
+ text: rendered.text,
2232
+ ...(rendered.blocks ? { blocks: rendered.blocks } : {}),
2233
+ };
2234
+ }
2235
+
2236
+ function validateRenderBlock(block, label) {
2237
+ if (!isRecord(block)) {
2238
+ throw agentKitError("tool_validation_error", \`\${label} must be an object.\`);
2239
+ }
2240
+
2241
+ if (block.type === "text") {
2242
+ if (typeof block.text !== "string" || block.text.trim().length === 0) {
2243
+ throw agentKitError("tool_validation_error", \`\${label}.text must be a non-empty string.\`);
2244
+ }
2245
+
2246
+ return;
2247
+ }
2248
+
2249
+ if (block.type === "card") {
2250
+ if (typeof block.title !== "string" || block.title.trim().length === 0) {
2251
+ throw agentKitError("tool_validation_error", \`\${label}.title must be a non-empty string.\`);
2252
+ }
2253
+
2254
+ validateOptionalString(block.subtitle, \`\${label}.subtitle\`);
2255
+ validateRenderFields(block.fields, \`\${label}.fields\`);
2256
+ validateRenderActions(block.actions, \`\${label}.actions\`);
2257
+ return;
2258
+ }
2259
+
2260
+ throw agentKitError("tool_validation_error", \`\${label}.type must be "text" or "card".\`);
2261
+ }
2262
+
2263
+ function validateRenderFields(value, label) {
2264
+ if (value === undefined) {
2265
+ return;
2266
+ }
2267
+
2268
+ if (!Array.isArray(value)) {
2269
+ throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
2270
+ }
2271
+
2272
+ for (const [index, field] of value.entries()) {
2273
+ if (!isRecord(field)) {
2274
+ throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
2275
+ }
2276
+
2277
+ validateRequiredString(field.label, \`\${label}[\${index}].label\`);
2278
+ validateRequiredString(field.value, \`\${label}[\${index}].value\`);
2279
+ }
2280
+ }
2281
+
2282
+ function validateRenderActions(value, label) {
2283
+ if (value === undefined) {
2284
+ return;
2285
+ }
2286
+
2287
+ if (!Array.isArray(value)) {
2288
+ throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
2289
+ }
2290
+
2291
+ for (const [index, action] of value.entries()) {
2292
+ if (!isRecord(action)) {
2293
+ throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
2294
+ }
2295
+
2296
+ validateRequiredString(action.label, \`\${label}[\${index}].label\`);
2297
+ validateOptionalString(action.href, \`\${label}[\${index}].href\`);
2298
+ }
2299
+ }
2300
+
2301
+ function validateRequiredString(value, label) {
2302
+ if (typeof value !== "string" || value.trim().length === 0) {
2303
+ throw agentKitError("tool_validation_error", \`\${label} must be a non-empty string.\`);
2304
+ }
2305
+ }
2306
+
2307
+ function validateOptionalString(value, label) {
2308
+ if (value !== undefined && typeof value !== "string") {
2309
+ throw agentKitError("tool_validation_error", \`\${label} must be a string when provided.\`);
2310
+ }
2311
+ }
2312
+
1679
2313
  function resolveToolSecrets(tool, env) {
1680
2314
  const secrets = {};
1681
2315
 
@@ -1716,6 +2350,7 @@ async function runToolWithTimeout(tool, input, context, timeoutMs) {
1716
2350
  secrets: context.secrets,
1717
2351
  signal: controller.signal,
1718
2352
  runtime: context.runtime,
2353
+ clock: context.clock,
1719
2354
  database: context.database,
1720
2355
  db: context.database,
1721
2356
  storage: {
@@ -1941,6 +2576,20 @@ function allowsNull(schema) {
1941
2576
  }
1942
2577
 
1943
2578
  async function authorizeChat(request, env, body) {
2579
+ return authorizeDeployAccess(request, env, body, {
2580
+ notConfiguredCode: "chat_access_not_configured",
2581
+ notConfiguredMessage: "Hosted chat requires a deploy access token gate.",
2582
+ });
2583
+ }
2584
+
2585
+ async function authorizeConversationRead(request, env) {
2586
+ return authorizeDeployAccess(request, env, {}, {
2587
+ notConfiguredCode: "conversation_access_not_configured",
2588
+ notConfiguredMessage: "Hosted conversation reads require a deploy access token gate.",
2589
+ });
2590
+ }
2591
+
2592
+ async function authorizeDeployAccess(request, env, body, options) {
1944
2593
  const runtimeGate = await authorizeRuntimeGate(request, env, body);
1945
2594
 
1946
2595
  if (runtimeGate) {
@@ -1951,52 +2600,28 @@ async function authorizeChat(request, env, body) {
1951
2600
  return runtimeGate;
1952
2601
  }
1953
2602
 
1954
- if (manifest.access.mode === "public") {
1955
- return null;
1956
- }
2603
+ const token = deployAccessToken(request, body);
2604
+ const acceptedTokens = [env.AGENTKIT_PRIVATE_TOKEN, env.AGENTKIT_ACCESS_TOKEN].filter(
2605
+ (value) => typeof value === "string" && value.length > 0,
2606
+ );
1957
2607
 
1958
- if (manifest.access.mode === "private") {
1959
- if (!env.AGENTKIT_PRIVATE_TOKEN) {
1960
- return Response.json(
1961
- {
1962
- error: {
1963
- code: "deploy_not_ready",
1964
- message: "Private deploy requires AGENTKIT_PRIVATE_TOKEN to be set as a Cloudflare secret.",
1965
- },
2608
+ if (acceptedTokens.length === 0) {
2609
+ return Response.json(
2610
+ {
2611
+ error: {
2612
+ code: options.notConfiguredCode,
2613
+ message: options.notConfiguredMessage,
1966
2614
  },
1967
- { status: 503 },
1968
- );
1969
- }
1970
-
1971
- const expected = \`Bearer \${env.AGENTKIT_PRIVATE_TOKEN}\`;
1972
- if (request.headers.get("authorization") !== expected) {
1973
- return Response.json(
1974
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
1975
- { status: 401 },
1976
- );
1977
- }
2615
+ },
2616
+ { status: 503 },
2617
+ );
1978
2618
  }
1979
2619
 
1980
- if (manifest.access.mode === "token") {
1981
- if (!env.AGENTKIT_ACCESS_TOKEN) {
1982
- return Response.json(
1983
- {
1984
- error: {
1985
- code: "deploy_not_ready",
1986
- message: "Token deploy requires AGENTKIT_ACCESS_TOKEN to be set as a Cloudflare secret.",
1987
- },
1988
- },
1989
- { status: 503 },
1990
- );
1991
- }
1992
-
1993
- const expected = \`Bearer \${env.AGENTKIT_ACCESS_TOKEN}\`;
1994
- if (request.headers.get("authorization") !== expected) {
1995
- return Response.json(
1996
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
1997
- { status: 401 },
1998
- );
1999
- }
2620
+ if (!token || !acceptedTokens.includes(token)) {
2621
+ return Response.json(
2622
+ { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2623
+ { status: 401 },
2624
+ );
2000
2625
  }
2001
2626
 
2002
2627
  return null;
@@ -2098,8 +2723,7 @@ async function persist(env, input) {
2098
2723
  return {};
2099
2724
  }
2100
2725
 
2101
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(input.conversationId);
2102
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2726
+ const stub = hostedConversationStore(env);
2103
2727
  const response = await stub.fetch("https://agentkit.internal/messages", {
2104
2728
  method: "POST",
2105
2729
  body: JSON.stringify(input),
@@ -2112,13 +2736,79 @@ async function persist(env, input) {
2112
2736
  return response.json();
2113
2737
  }
2114
2738
 
2739
+ function queueRuntimeUsage(ctx, promise) {
2740
+ const safePromise = promise.catch(() => undefined);
2741
+
2742
+ if (ctx && typeof ctx.waitUntil === "function") {
2743
+ ctx.waitUntil(safePromise);
2744
+ return;
2745
+ }
2746
+
2747
+ void safePromise;
2748
+ }
2749
+
2750
+ async function sendRuntimeUsage(env, input) {
2751
+ if (!env.AGENTKIT_CLOUD_API_URL || !env.AGENTKIT_DEPLOY_ID || !env.AGENTKIT_RUNTIME_GATE_TOKEN) {
2752
+ return;
2753
+ }
2754
+
2755
+ const usage = normalizeRuntimeUsage(input.usage);
2756
+ const metadata = isRecord(input.metadata) ? input.metadata : {};
2757
+ const body = {
2758
+ kind: input.kind || "chat_run",
2759
+ status: input.status || "succeeded",
2760
+ ...(input.runId ? { run_id: String(input.runId) } : {}),
2761
+ ...(input.conversationId ? { conversation_id: String(input.conversationId) } : {}),
2762
+ ...(input.provider ? { provider: String(input.provider) } : {}),
2763
+ ...(input.model ? { model: String(input.model) } : {}),
2764
+ ...(usage.inputTokens !== undefined ? { input_tokens: usage.inputTokens } : {}),
2765
+ ...(usage.outputTokens !== undefined ? { output_tokens: usage.outputTokens } : {}),
2766
+ ...(usage.totalTokens !== undefined ? { total_tokens: usage.totalTokens } : {}),
2767
+ ...(usage.cost && Object.keys(usage.cost).length > 0 ? { cost: usage.cost } : {}),
2768
+ ...(usage.cost?.total !== undefined ? { cost_usd: usage.cost.total } : {}),
2769
+ metadata: sanitizeRuntimeUsageMetadata(metadata),
2770
+ };
2771
+
2772
+ const url = new URL(
2773
+ "/v1/runtime/deploys/" + encodeURIComponent(String(env.AGENTKIT_DEPLOY_ID)) + "/usage",
2774
+ env.AGENTKIT_CLOUD_API_URL,
2775
+ );
2776
+ await fetch(url.href, {
2777
+ method: "POST",
2778
+ headers: {
2779
+ Authorization: "Bearer " + env.AGENTKIT_RUNTIME_GATE_TOKEN,
2780
+ "Content-Type": "application/json",
2781
+ },
2782
+ body: JSON.stringify(body),
2783
+ });
2784
+ }
2785
+
2786
+ function sanitizeRuntimeUsageMetadata(value) {
2787
+ const metadata = {};
2788
+
2789
+ for (const [key, raw] of Object.entries(value)) {
2790
+ if (!/^[A-Za-z0-9_.:-]{1,80}$/.test(key)) {
2791
+ continue;
2792
+ }
2793
+
2794
+ if (typeof raw === "string") {
2795
+ metadata[key] = raw.length > 160 ? raw.slice(0, 160) : raw;
2796
+ } else if (typeof raw === "number" && Number.isFinite(raw)) {
2797
+ metadata[key] = raw;
2798
+ } else if (typeof raw === "boolean") {
2799
+ metadata[key] = raw;
2800
+ }
2801
+ }
2802
+
2803
+ return metadata;
2804
+ }
2805
+
2115
2806
  async function loadConversationMessages(env, conversationId) {
2116
2807
  if (!env.AGENTKIT_CONVERSATIONS || !conversationId) {
2117
2808
  return [];
2118
2809
  }
2119
2810
 
2120
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2121
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2811
+ const stub = hostedConversationStore(env);
2122
2812
  const response = await stub.fetch(
2123
2813
  \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2124
2814
  );
@@ -2128,7 +2818,7 @@ async function loadConversationMessages(env, conversationId) {
2128
2818
  }
2129
2819
 
2130
2820
  const payload = await response.json();
2131
- return Array.isArray(payload.messages)
2821
+ const messages = Array.isArray(payload.messages)
2132
2822
  ? payload.messages.filter((message) =>
2133
2823
  message &&
2134
2824
  typeof message === "object" &&
@@ -2136,6 +2826,39 @@ async function loadConversationMessages(env, conversationId) {
2136
2826
  typeof message.content === "string"
2137
2827
  )
2138
2828
  : [];
2829
+
2830
+ if (messages.length > 0) {
2831
+ return messages;
2832
+ }
2833
+
2834
+ const legacyStub = legacyConversationStore(env, conversationId);
2835
+ const legacyResponse = await legacyStub.fetch(
2836
+ \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2837
+ );
2838
+
2839
+ if (!legacyResponse.ok) {
2840
+ return [];
2841
+ }
2842
+
2843
+ const legacyPayload = await legacyResponse.json();
2844
+ return Array.isArray(legacyPayload.messages)
2845
+ ? legacyPayload.messages.filter((message) =>
2846
+ message &&
2847
+ typeof message === "object" &&
2848
+ (message.role === "user" || message.role === "assistant") &&
2849
+ typeof message.content === "string"
2850
+ )
2851
+ : [];
2852
+ }
2853
+
2854
+ function hostedConversationStore(env) {
2855
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName("agentkit-conversations");
2856
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2857
+ }
2858
+
2859
+ function legacyConversationStore(env, conversationId) {
2860
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2861
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2139
2862
  }
2140
2863
 
2141
2864
  function normalizeConversationId(value) {
@@ -2412,6 +3135,80 @@ function titleFromMessage(content) {
2412
3135
  const compact = content.split(/\\r?\\n/, 1)[0].replace(/\\s+/g, " ").trim();
2413
3136
  return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
2414
3137
  }
3138
+
3139
+ function readStoredUsage(row) {
3140
+ const usage = {};
3141
+ const inputTokens = normalizeRuntimeInteger(row.input_tokens);
3142
+ const outputTokens = normalizeRuntimeInteger(row.output_tokens);
3143
+ const totalTokens = normalizeRuntimeInteger(row.total_tokens);
3144
+ const cost = normalizeRuntimeCost(parseStoredJson(row.cost_json, {}));
3145
+
3146
+ if (inputTokens !== undefined) usage.inputTokens = inputTokens;
3147
+ if (outputTokens !== undefined) usage.outputTokens = outputTokens;
3148
+ if (totalTokens !== undefined) usage.totalTokens = totalTokens;
3149
+ if (Object.keys(cost).length > 0) usage.cost = cost;
3150
+
3151
+ return usage;
3152
+ }
3153
+
3154
+ function normalizeRuntimeUsage(value) {
3155
+ if (!isRecord(value)) {
3156
+ return {};
3157
+ }
3158
+
3159
+ const usage = {};
3160
+ const inputTokens = normalizeRuntimeInteger(value.inputTokens ?? value.input_tokens);
3161
+ const outputTokens = normalizeRuntimeInteger(value.outputTokens ?? value.output_tokens);
3162
+ const totalTokens = normalizeRuntimeInteger(value.totalTokens ?? value.total_tokens);
3163
+ const cost = normalizeRuntimeCost(value.cost);
3164
+
3165
+ if (inputTokens !== undefined) usage.inputTokens = inputTokens;
3166
+ if (outputTokens !== undefined) usage.outputTokens = outputTokens;
3167
+ if (totalTokens !== undefined) usage.totalTokens = totalTokens;
3168
+ if (Object.keys(cost).length > 0) usage.cost = cost;
3169
+
3170
+ return usage;
3171
+ }
3172
+
3173
+ function normalizeRuntimeCost(value) {
3174
+ if (!isRecord(value)) {
3175
+ return {};
3176
+ }
3177
+
3178
+ const cost = {};
3179
+
3180
+ for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
3181
+ const normalized = normalizeRuntimeNumber(value[field]);
3182
+
3183
+ if (normalized !== undefined) {
3184
+ cost[field] = normalized;
3185
+ }
3186
+ }
3187
+
3188
+ return cost;
3189
+ }
3190
+
3191
+ function normalizeRuntimeInteger(value) {
3192
+ const normalized = normalizeRuntimeNumber(value);
3193
+ return normalized !== undefined && Number.isInteger(normalized) ? normalized : undefined;
3194
+ }
3195
+
3196
+ function normalizeRuntimeNumber(value) {
3197
+ const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
3198
+ return typeof parsed === "number" && Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
3199
+ }
3200
+
3201
+ function parseStoredJson(value, fallback) {
3202
+ if (typeof value !== "string") {
3203
+ return fallback;
3204
+ }
3205
+
3206
+ try {
3207
+ return JSON.parse(value);
3208
+ } catch {
3209
+ return fallback;
3210
+ }
3211
+ }
2415
3212
  `;
2416
3213
  }
2417
3214
 
@@ -2482,6 +3279,10 @@ CREATE TABLE IF NOT EXISTS runs (
2482
3279
  status TEXT NOT NULL,
2483
3280
  error_code TEXT,
2484
3281
  error_message TEXT,
3282
+ input_tokens INTEGER,
3283
+ output_tokens INTEGER,
3284
+ total_tokens INTEGER,
3285
+ cost_json TEXT,
2485
3286
  created_at TEXT NOT NULL,
2486
3287
  completed_at TEXT
2487
3288
  );
@@ -2495,6 +3296,7 @@ CREATE TABLE IF NOT EXISTS tool_calls (
2495
3296
  status TEXT NOT NULL,
2496
3297
  input_json TEXT NOT NULL,
2497
3298
  output_json TEXT,
3299
+ rendered_json TEXT,
2498
3300
  error_message TEXT,
2499
3301
  created_at TEXT NOT NULL,
2500
3302
  completed_at TEXT