@andreprado/agentkit 0.1.0-alpha.17 → 0.1.0-alpha.19

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 (64) hide show
  1. package/README.md +3 -0
  2. package/docs/guides/add-channel.md +14 -8
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +43 -17
  5. package/docs/guides/channel-security.md +60 -39
  6. package/docs/guides/connect-discord.md +178 -0
  7. package/docs/guides/create-agent.md +13 -0
  8. package/docs/guides/debug-channel.md +147 -0
  9. package/docs/guides/improve-from-production.md +151 -0
  10. package/docs/guides/prepare-deploy.md +30 -14
  11. package/docs/guides/replay-production-traces.md +72 -0
  12. package/docs/guides/run-evals.md +18 -0
  13. package/docs/guides/security-rules.md +5 -5
  14. package/docs/guides/use-provider.md +11 -1
  15. package/docs/llms-full.txt +106 -15
  16. package/docs/llms.txt +22 -4
  17. package/package.json +1 -3
  18. package/src/cli/args.ts +23 -2
  19. package/src/cli/cloud-client.ts +75 -0
  20. package/src/cli/commands/channels.ts +139 -14
  21. package/src/cli/deploy-chat-ui.ts +146 -3
  22. package/src/cli/deploy-readiness.ts +57 -1
  23. package/src/cli/help.ts +32 -8
  24. package/src/cli/index.ts +447 -17
  25. package/src/create-project.ts +13 -2
  26. package/src/index.ts +46 -3
  27. package/src/providers/pi.ts +49 -15
  28. package/src/runtime/channel-test-harness.ts +4 -1
  29. package/src/runtime/channels/discord.ts +887 -0
  30. package/src/runtime/channels.ts +15 -0
  31. package/src/runtime/config.ts +39 -3
  32. package/src/runtime/core/manifest.ts +2 -0
  33. package/src/runtime/dev-server.ts +149 -8
  34. package/src/runtime/evals.ts +27 -6
  35. package/src/runtime/improve.ts +868 -0
  36. package/src/runtime/inspect.ts +1 -0
  37. package/src/runtime/integrations/composio.ts +168 -2
  38. package/src/runtime/knowledge/retrieve.ts +25 -5
  39. package/src/runtime/knowledge/schema.ts +45 -1
  40. package/src/runtime/runtime-contract.ts +54 -0
  41. package/src/runtime/targets/cloudflare/build.ts +479 -194
  42. package/src/runtime/targets/vps/deploy.ts +1 -1
  43. package/src/storage/sqlite.ts +7 -2
  44. package/src/templates/skills/agentkit-capsule/SKILL.md +8 -1
  45. package/src/templates/skills/agentkit-capsule/references/docs-router.md +1 -2
  46. package/src/templates/skills/agentkit-channels/SKILL.md +6 -1
  47. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +2 -1
  48. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  49. package/src/templates/skills/agentkit-deploy/SKILL.md +6 -0
  50. package/src/templates/skills/agentkit-evals/SKILL.md +12 -3
  51. package/src/templates/skills/agentkit-improve/SKILL.md +86 -0
  52. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  53. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  54. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  55. package/src/templates/skills/agentkit-integrations/SKILL.md +12 -2
  56. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  57. package/src/templates/skills/agentkit-provider/SKILL.md +4 -1
  58. package/src/templates/skills/agentkit-security/SKILL.md +3 -2
  59. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +9 -0
  60. package/src/templates/support.ts +4 -2
  61. package/docs/guides/agentkit-skills-architecture.md +0 -472
  62. package/docs/guides/channels-implementation-map.md +0 -243
  63. package/docs/guides/channels-production-handoff.md +0 -118
  64. package/docs/portable-deploy-release-checklist.md +0 -41
@@ -320,8 +320,8 @@ async function listSourceFiles(root: string): Promise<string[]> {
320
320
  function findImportSpecifiers(source: string): string[] {
321
321
  const specifiers: string[] = [];
322
322
  const patterns = [
323
- /\bimport\s+(?:type\s+)?(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
324
- /\bexport\s+[^"'()]*?\s+from\s+["']([^"']+)["']/g,
323
+ /\bimport\s+(?!type\b)(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
324
+ /\bexport\s+(?!type\b)[^"'()]*?\s+from\s+["']([^"']+)["']/g,
325
325
  /\brequire\(\s*["']([^"']+)["']\s*\)/g,
326
326
  /\bimport\(\s*["']([^"']+)["']\s*\)/g,
327
327
  ];
@@ -390,6 +390,8 @@ export function composioManaged(input) {
390
390
  const toolkits = normalizeToolkitList(input.toolkits, "composioManaged.toolkits");
391
391
  const tools = input.tools ? normalizeComposioToolMap(input.tools, toolkits) : undefined;
392
392
  const tags = input.tags ? normalizeIntegrationTags(input.tags) : undefined;
393
+ const confirmExternalWrites =
394
+ input.confirmExternalWrites === undefined ? undefined : Boolean(input.confirmExternalWrites);
393
395
 
394
396
  return {
395
397
  type: "composio",
@@ -399,6 +401,7 @@ export function composioManaged(input) {
399
401
  toolkits,
400
402
  ...(tools ? { tools } : {}),
401
403
  ...(tags ? { tags } : {}),
404
+ ...(confirmExternalWrites === undefined ? {} : { confirmExternalWrites }),
402
405
  };
403
406
  }
404
407
 
@@ -437,6 +440,17 @@ export function whatsappChannel(input) {
437
440
  };
438
441
  }
439
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
+
440
454
  function defaultWhatsappChannelSecrets(provider) {
441
455
  if (provider === "zapster") {
442
456
  return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"];
@@ -713,11 +727,17 @@ export class AgentKitConversationStore extends DurableObject {
713
727
  status TEXT NOT NULL,
714
728
  input_json TEXT NOT NULL,
715
729
  output_json TEXT,
730
+ rendered_json TEXT,
716
731
  error_message TEXT,
717
732
  created_at TEXT NOT NULL,
718
733
  completed_at TEXT
719
734
  );
720
735
  \`);
736
+ try {
737
+ this.sql.exec("ALTER TABLE tool_calls ADD COLUMN rendered_json TEXT");
738
+ } catch {
739
+ // Existing Durable Object tables either already have this column or do not need migration.
740
+ }
721
741
  }
722
742
 
723
743
  async fetch(request) {
@@ -731,6 +751,22 @@ export class AgentKitConversationStore extends DurableObject {
731
751
  return this.getMessages(url.searchParams.get("conversationId"));
732
752
  }
733
753
 
754
+ if (request.method === "GET" && url.pathname === "/conversations") {
755
+ return this.listConversations();
756
+ }
757
+
758
+ const conversationTraceMatch = url.pathname.match(/^\\/conversations\\/([^/]+)\\/trace$/);
759
+
760
+ if (request.method === "GET" && conversationTraceMatch) {
761
+ return this.getConversationTrace(decodeURIComponent(conversationTraceMatch[1] || ""));
762
+ }
763
+
764
+ const conversationMatch = url.pathname.match(/^\\/conversations\\/([^/]+)$/);
765
+
766
+ if (request.method === "GET" && conversationMatch) {
767
+ return this.getConversation(decodeURIComponent(conversationMatch[1] || ""));
768
+ }
769
+
734
770
  return Response.json({ error: { code: "not_found", message: "Unknown Durable Object route." } }, { status: 404 });
735
771
  }
736
772
 
@@ -754,6 +790,129 @@ export class AgentKitConversationStore extends DurableObject {
754
790
  });
755
791
  }
756
792
 
793
+ listConversations() {
794
+ const rows = this.sql.exec(\`
795
+ SELECT conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at, COUNT(messages.id) AS message_count
796
+ FROM conversations
797
+ LEFT JOIN messages ON messages.conversation_id = conversations.id
798
+ GROUP BY conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at
799
+ ORDER BY conversations.updated_at DESC
800
+ \`).toArray();
801
+
802
+ return Response.json({
803
+ conversations: rows.map((row) => ({
804
+ id: String(row.id),
805
+ agentName: String(row.agent_name),
806
+ title: row.title === null || row.title === undefined ? null : String(row.title),
807
+ createdAt: String(row.created_at),
808
+ updatedAt: String(row.updated_at),
809
+ messageCount: Number(row.message_count || 0),
810
+ })),
811
+ });
812
+ }
813
+
814
+ getConversation(conversationId) {
815
+ const conversation = this.readConversation(conversationId);
816
+
817
+ if (!conversation) {
818
+ return Response.json(
819
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
820
+ { status: 404 },
821
+ );
822
+ }
823
+
824
+ return Response.json(conversation);
825
+ }
826
+
827
+ getConversationTrace(conversationId) {
828
+ const conversation = this.readConversation(conversationId);
829
+
830
+ if (!conversation) {
831
+ return Response.json(
832
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
833
+ { status: 404 },
834
+ );
835
+ }
836
+
837
+ const runs = this.sql.exec(
838
+ "SELECT id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
839
+ conversationId,
840
+ ).toArray();
841
+
842
+ return Response.json({
843
+ ...conversation,
844
+ runs: runs.map((run) => ({
845
+ id: String(run.id),
846
+ conversationId: String(run.conversation_id),
847
+ provider: String(run.provider),
848
+ model: String(run.model),
849
+ status: String(run.status),
850
+ errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
851
+ errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
852
+ startedAt: String(run.created_at),
853
+ completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
854
+ toolCalls: this.readToolCalls(String(run.id)),
855
+ })),
856
+ });
857
+ }
858
+
859
+ readConversation(conversationId) {
860
+ if (!conversationId) {
861
+ return null;
862
+ }
863
+
864
+ const row = this.sql.exec(
865
+ "SELECT id, agent_name, title, created_at, updated_at FROM conversations WHERE id = ?",
866
+ conversationId,
867
+ ).one();
868
+
869
+ if (!row) {
870
+ return null;
871
+ }
872
+
873
+ const messages = this.sql.exec(
874
+ "SELECT id, conversation_id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
875
+ conversationId,
876
+ ).toArray();
877
+
878
+ return {
879
+ id: String(row.id),
880
+ agentName: String(row.agent_name),
881
+ title: row.title === null || row.title === undefined ? null : String(row.title),
882
+ createdAt: String(row.created_at),
883
+ updatedAt: String(row.updated_at),
884
+ messages: messages.map((message) => ({
885
+ id: String(message.id),
886
+ conversationId: String(message.conversation_id),
887
+ role: String(message.role),
888
+ content: String(message.content),
889
+ createdAt: String(message.created_at),
890
+ })),
891
+ };
892
+ }
893
+
894
+ readToolCalls(runId) {
895
+ const rows = this.sql.exec(
896
+ "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",
897
+ runId,
898
+ ).toArray();
899
+
900
+ return rows.map((row) => ({
901
+ id: String(row.id),
902
+ runId: String(row.run_id),
903
+ conversationId: String(row.conversation_id),
904
+ name: String(row.tool_name),
905
+ visibility: String(row.visibility),
906
+ status: String(row.status),
907
+ input: parseStoredJson(row.input_json, {}),
908
+ output: row.output_json === null || row.output_json === undefined ? undefined : parseStoredJson(row.output_json, null),
909
+ rendered: row.rendered_json === null || row.rendered_json === undefined ? undefined : parseStoredJson(row.rendered_json, null),
910
+ errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
911
+ createdAt: String(row.created_at),
912
+ completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
913
+ }));
914
+ }
915
+
757
916
  persistMessages(input) {
758
917
  const now = new Date().toISOString();
759
918
  const conversationId = String(input.conversationId || crypto.randomUUID());
@@ -805,7 +964,7 @@ export class AgentKitConversationStore extends DurableObject {
805
964
 
806
965
  for (const toolCall of input.toolCalls || []) {
807
966
  this.sql.exec(
808
- "INSERT INTO tool_calls (id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
967
+ "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
809
968
  String(toolCall.id || crypto.randomUUID()),
810
969
  runId,
811
970
  conversationId,
@@ -814,6 +973,7 @@ export class AgentKitConversationStore extends DurableObject {
814
973
  String(toolCall.status || "completed"),
815
974
  JSON.stringify(toolCall.input ?? {}),
816
975
  toolCall.output === undefined ? null : JSON.stringify(toolCall.output),
976
+ toolCall.rendered === undefined ? null : JSON.stringify(toolCall.rendered),
817
977
  toolCall.errorMessage ? String(toolCall.errorMessage) : null,
818
978
  now,
819
979
  now,
@@ -867,6 +1027,15 @@ export default {
867
1027
  }
868
1028
 
869
1029
  if (request.method === "GET" && url.pathname === "/") {
1030
+ const auth = await authorizeDeployAccess(request, env, {}, {
1031
+ notConfiguredCode: "cloud_access_not_configured",
1032
+ notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
1033
+ });
1034
+
1035
+ if (auth) {
1036
+ return auth;
1037
+ }
1038
+
870
1039
  return Response.json({
871
1040
  agent: manifest.name,
872
1041
  status: "ready",
@@ -876,7 +1045,16 @@ export default {
876
1045
  }
877
1046
 
878
1047
  if (request.method === "GET" && url.pathname === "/health") {
879
- return Response.json({ status: "ok", agent: manifest.name });
1048
+ const auth = await authorizeDeployAccess(request, env, {}, {
1049
+ notConfiguredCode: "cloud_access_not_configured",
1050
+ notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
1051
+ });
1052
+
1053
+ if (auth) {
1054
+ return auth;
1055
+ }
1056
+
1057
+ return Response.json({ ok: true });
880
1058
  }
881
1059
 
882
1060
  if (isHostedChannelWebhookPath(url.pathname)) {
@@ -887,16 +1065,24 @@ export default {
887
1065
  return handleChat(request, env);
888
1066
  }
889
1067
 
890
- if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
891
- return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
1068
+ if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
1069
+ return handleConversationRead(request, env, "list");
892
1070
  }
893
1071
 
894
- if (request.method === "GET" && url.pathname === "/_agentkit/turso/health") {
895
- return handleTursoHealth(request, env);
1072
+ const conversationTraceMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)\\/trace$/);
1073
+
1074
+ if (request.method === "GET" && conversationTraceMatch) {
1075
+ return handleConversationRead(request, env, "trace", decodeURIComponent(conversationTraceMatch[1] || ""));
896
1076
  }
897
1077
 
898
- if (request.method === "POST" && url.pathname === "/_agentkit/turso/query") {
899
- return handleTursoQuery(request, env);
1078
+ const conversationMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)$/);
1079
+
1080
+ if (request.method === "GET" && conversationMatch) {
1081
+ return handleConversationRead(request, env, "show", decodeURIComponent(conversationMatch[1] || ""));
1082
+ }
1083
+
1084
+ if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
1085
+ return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
900
1086
  }
901
1087
 
902
1088
  if (url.pathname.startsWith("/files/")) {
@@ -909,8 +1095,8 @@ export default {
909
1095
 
910
1096
  function isHostedChannelWebhookPath(pathname) {
911
1097
  return (
912
- /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta))\\/webhook$/.test(pathname) ||
913
- /^\\/channels\\/[^/]+\\/(?:website|telegram)\\/webhook$/.test(pathname) ||
1098
+ /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta)|discord\\/discord)\\/webhook$/.test(pathname) ||
1099
+ /^\\/channels\\/[^/]+\\/(?:website|telegram|discord)\\/webhook$/.test(pathname) ||
914
1100
  /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
915
1101
  );
916
1102
  }
@@ -949,71 +1135,6 @@ async function forwardHostedChannelWebhook(request, env, url) {
949
1135
  });
950
1136
  }
951
1137
 
952
- async function handleTursoHealth(request, env) {
953
- const auth = authorizeAdmin(request, env);
954
-
955
- if (auth) {
956
- return auth;
957
- }
958
-
959
- const configError = requireTurso(env);
960
-
961
- if (configError) {
962
- return configError;
963
- }
964
-
965
- const response = await fetch(toTursoBaseUrl(env.TURSO_DATABASE_URL) + "/health", {
966
- headers: {
967
- Authorization: \`Bearer \${env.TURSO_AUTH_TOKEN}\`,
968
- },
969
- });
970
-
971
- return Response.json({
972
- driver: "turso",
973
- ok: response.ok,
974
- status: response.status,
975
- }, { status: response.ok ? 200 : 502 });
976
- }
977
-
978
- async function handleTursoQuery(request, env) {
979
- const auth = authorizeAdmin(request, env);
980
-
981
- if (auth) {
982
- return auth;
983
- }
984
-
985
- const configError = requireTurso(env);
986
-
987
- if (configError) {
988
- return configError;
989
- }
990
-
991
- let body;
992
-
993
- try {
994
- body = await request.json();
995
- } catch {
996
- return Response.json(
997
- { error: { code: "validation_error", message: "Expected JSON body with sql and optional args fields." } },
998
- { status: 400 },
999
- );
1000
- }
1001
-
1002
- const sql = String(body.sql || "").trim();
1003
-
1004
- if (!sql) {
1005
- return Response.json({ error: { code: "validation_error", message: "sql must be non-empty." } }, { status: 400 });
1006
- }
1007
-
1008
- const args = Array.isArray(body.args) ? body.args : [];
1009
- try {
1010
- const result = await executeTursoSql(env, sql, args);
1011
- return Response.json(result);
1012
- } catch (error) {
1013
- return hostedErrorResponse(error);
1014
- }
1015
- }
1016
-
1017
1138
  async function handleFile(request, env, rawKey) {
1018
1139
  const auth = authorizeAdmin(request, env);
1019
1140
 
@@ -1141,6 +1262,7 @@ async function handleTool(request, env, toolName) {
1141
1262
  },
1142
1263
  input: result.input,
1143
1264
  output: result.output,
1265
+ ...(result.rendered ? { rendered: result.rendered } : {}),
1144
1266
  });
1145
1267
  } catch (error) {
1146
1268
  return hostedErrorResponse(error);
@@ -1186,35 +1308,12 @@ async function handleChat(request, env) {
1186
1308
  let content;
1187
1309
  const toolCalls = [];
1188
1310
 
1189
- const toolRequest = parseToolRequest(message);
1190
-
1191
- if (toolRequest) {
1192
- try {
1193
- const toolResult = await executeHostedTool({
1194
- name: toolRequest.name,
1195
- input: toolRequest.input,
1196
- env,
1197
- signal: request.signal,
1198
- invocation: "chat",
1199
- });
1200
- toolCalls.push(toolResult);
1201
- content =
1202
- toolResult.visibility === "internal"
1203
- ? \`Tool \${toolResult.name}: completed\`
1204
- : \`Tool \${toolResult.name}: \${JSON.stringify(toolResult.output)}\`;
1205
- } catch (error) {
1206
- await persistFailure(env, conversationId, message, error);
1207
- return hostedErrorResponse(error);
1208
- }
1209
- }
1210
-
1211
- if (content === undefined) {
1212
- try {
1213
- content = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal);
1214
- } catch (error) {
1215
- await persistFailure(env, conversationId, message, error);
1216
- return hostedErrorResponse(error);
1217
- }
1311
+ try {
1312
+ const providerResult = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal, toolCalls);
1313
+ content = providerResult.content;
1314
+ } catch (error) {
1315
+ await persistFailure(env, conversationId, message, error, toolCalls);
1316
+ return hostedErrorResponse(error);
1218
1317
  }
1219
1318
 
1220
1319
  const persisted = await persist(env, {
@@ -1233,6 +1332,39 @@ async function handleChat(request, env) {
1233
1332
  });
1234
1333
  }
1235
1334
 
1335
+ async function handleConversationRead(request, env, action, conversationId) {
1336
+ const auth = await authorizeConversationRead(request, env);
1337
+
1338
+ if (auth) {
1339
+ return auth;
1340
+ }
1341
+
1342
+ if (!env.AGENTKIT_CONVERSATIONS) {
1343
+ return Response.json({ error: { code: "conversations_not_configured", message: "Hosted conversation storage is not configured." } }, { status: 503 });
1344
+ }
1345
+
1346
+ if (action === "list") {
1347
+ const stub = hostedConversationStore(env);
1348
+ return stub.fetch("https://agentkit.internal/conversations");
1349
+ }
1350
+
1351
+ if (!conversationId) {
1352
+ return Response.json({ error: { code: "validation_error", message: "Missing conversation id." } }, { status: 400 });
1353
+ }
1354
+
1355
+ const stub = hostedConversationStore(env);
1356
+ const suffix = action === "trace" ? "/trace" : "";
1357
+ const path = \`/conversations/\${encodeURIComponent(conversationId)}\${suffix}\`;
1358
+ const response = await stub.fetch(\`https://agentkit.internal\${path}\`);
1359
+
1360
+ if (response.status !== 404) {
1361
+ return response;
1362
+ }
1363
+
1364
+ const legacyStub = legacyConversationStore(env, conversationId);
1365
+ return legacyStub.fetch(\`https://agentkit.internal\${path}\`);
1366
+ }
1367
+
1236
1368
  function readChatMessage(body) {
1237
1369
  if (typeof body.message === "string") {
1238
1370
  return body.message.trim();
@@ -1249,7 +1381,7 @@ function isRecord(value) {
1249
1381
  return typeof value === "object" && value !== null && !Array.isArray(value);
1250
1382
  }
1251
1383
 
1252
- async function callHostedProvider(env, messages, signal) {
1384
+ async function callHostedProvider(env, messages, signal, toolCalls = []) {
1253
1385
  const adapter = getProviderAdapter(manifest.provider);
1254
1386
  const hostedTools = createHostedTools(env);
1255
1387
  const clock = buildRuntimeClockContext({
@@ -1270,12 +1402,14 @@ async function callHostedProvider(env, messages, signal) {
1270
1402
  ),
1271
1403
  messages,
1272
1404
  tools: hostedTools,
1273
- toolRuntime: createHostedProviderToolRuntime(env, signal, clock),
1405
+ toolRuntime: createHostedProviderToolRuntime(env, signal, clock, toolCalls),
1274
1406
  env,
1275
1407
  signal,
1276
1408
  });
1277
1409
 
1278
- return result.message.content;
1410
+ return {
1411
+ content: result.message.content,
1412
+ };
1279
1413
  }
1280
1414
 
1281
1415
  function appendHostedKnowledgePromptPolicy(baseInstructions) {
@@ -1701,36 +1835,17 @@ async function hostedFakeEmbedding(input, dimensions) {
1701
1835
  return norm === 0 ? values : values.map((value) => Number((value / norm).toFixed(8)));
1702
1836
  }
1703
1837
 
1704
- async function persistFailure(env, conversationId, userMessage, error) {
1838
+ async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
1705
1839
  await persist(env, {
1706
1840
  conversationId,
1707
1841
  userMessage,
1708
1842
  status: "failed",
1709
1843
  errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
1710
1844
  errorMessage: error instanceof Error ? error.message : String(error),
1711
- toolCalls: [],
1845
+ toolCalls,
1712
1846
  }).catch(() => undefined);
1713
1847
  }
1714
1848
 
1715
- function parseToolRequest(content) {
1716
- let parsed;
1717
-
1718
- try {
1719
- parsed = JSON.parse(content);
1720
- } catch {
1721
- return null;
1722
- }
1723
-
1724
- if (!isRecord(parsed) || typeof parsed.tool !== "string" || parsed.tool.length === 0) {
1725
- return null;
1726
- }
1727
-
1728
- return {
1729
- name: parsed.tool,
1730
- input: "input" in parsed ? parsed.input : {},
1731
- };
1732
- }
1733
-
1734
1849
  function inspectHostedDatabase() {
1735
1850
  const schema = manifest.storage.database.driver === "turso" ? manifest.storage.database.schema : null;
1736
1851
 
@@ -1784,21 +1899,28 @@ function hostedIntegrations(env) {
1784
1899
  );
1785
1900
  }
1786
1901
 
1787
- function createHostedProviderToolRuntime(env, signal, clock) {
1902
+ function createHostedProviderToolRuntime(env, signal, clock, toolCalls) {
1788
1903
  const hostedTools = createHostedTools(env);
1789
1904
 
1790
1905
  return {
1791
1906
  definitions: hostedTools,
1792
- execute(request) {
1793
- return executeHostedTool({
1794
- tools: hostedTools,
1795
- name: request.name,
1796
- input: request.input,
1797
- env,
1798
- signal,
1799
- invocation: "chat",
1800
- clock,
1801
- });
1907
+ async execute(request) {
1908
+ try {
1909
+ const result = await executeHostedTool({
1910
+ tools: hostedTools,
1911
+ name: request.name,
1912
+ input: request.input,
1913
+ env,
1914
+ signal,
1915
+ invocation: "chat",
1916
+ clock,
1917
+ });
1918
+ toolCalls?.push(result);
1919
+ return result;
1920
+ } catch (error) {
1921
+ toolCalls?.push(failedHostedToolCall(hostedTools, request, error));
1922
+ throw error;
1923
+ }
1802
1924
  },
1803
1925
  };
1804
1926
  }
@@ -1821,6 +1943,12 @@ async function executeHostedTool(request) {
1821
1943
  timeZone: manifest.timeZone,
1822
1944
  env: request.env,
1823
1945
  });
1946
+ const runtime = {
1947
+ environment: "hosted",
1948
+ invocation: request.invocation ?? "tool",
1949
+ target: "cloudflare",
1950
+ database: manifest.storage.database.driver === "turso" ? "turso" : "none",
1951
+ };
1824
1952
  const output = await runToolWithTimeout(
1825
1953
  tool,
1826
1954
  input,
@@ -1829,12 +1957,7 @@ async function executeHostedTool(request) {
1829
1957
  database,
1830
1958
  signal: request.signal,
1831
1959
  clock,
1832
- runtime: {
1833
- environment: "hosted",
1834
- invocation: request.invocation ?? "tool",
1835
- target: "cloudflare",
1836
- database: manifest.storage.database.driver === "turso" ? "turso" : "none",
1837
- },
1960
+ runtime,
1838
1961
  },
1839
1962
  tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
1840
1963
  );
@@ -1843,6 +1966,8 @@ async function executeHostedTool(request) {
1843
1966
  validateSchema(output, tool.outputSchema, \`tool "\${tool.name}" output\`);
1844
1967
  }
1845
1968
 
1969
+ const rendered = renderHostedToolOutput(tool, input, output, runtime, clock);
1970
+
1846
1971
  return {
1847
1972
  id,
1848
1973
  name: tool.name,
@@ -1850,9 +1975,135 @@ async function executeHostedTool(request) {
1850
1975
  status: "completed",
1851
1976
  input,
1852
1977
  output,
1978
+ ...(rendered ? { rendered } : {}),
1979
+ };
1980
+ }
1981
+
1982
+ function failedHostedToolCall(tools, request, error) {
1983
+ const tool = tools.find((candidate) => candidate.name === request.name);
1984
+
1985
+ return {
1986
+ id: crypto.randomUUID(),
1987
+ name: String(request.name || ""),
1988
+ visibility: tool?.visibility ?? "user",
1989
+ status: "failed",
1990
+ input: request.input ?? {},
1991
+ errorMessage: error instanceof Error ? error.message : String(error),
1992
+ };
1993
+ }
1994
+
1995
+ function renderHostedToolOutput(tool, input, output, runtime, clock) {
1996
+ if (!tool.render) {
1997
+ return undefined;
1998
+ }
1999
+
2000
+ const rendered = tool.render(output, {
2001
+ input,
2002
+ runtime,
2003
+ clock,
2004
+ });
2005
+
2006
+ if (typeof rendered === "string") {
2007
+ return { text: rendered };
2008
+ }
2009
+
2010
+ if (!isRecord(rendered) || typeof rendered.text !== "string" || rendered.text.trim().length === 0) {
2011
+ throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render must return a non-empty text string.\`);
2012
+ }
2013
+
2014
+ if (rendered.blocks !== undefined) {
2015
+ if (!Array.isArray(rendered.blocks)) {
2016
+ throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render.blocks must be an array when provided.\`);
2017
+ }
2018
+
2019
+ for (const [index, block] of rendered.blocks.entries()) {
2020
+ validateRenderBlock(block, \`tool "\${tool.name}" render.blocks[\${index}]\`);
2021
+ }
2022
+ }
2023
+
2024
+ return {
2025
+ text: rendered.text,
2026
+ ...(rendered.blocks ? { blocks: rendered.blocks } : {}),
1853
2027
  };
1854
2028
  }
1855
2029
 
2030
+ function validateRenderBlock(block, label) {
2031
+ if (!isRecord(block)) {
2032
+ throw agentKitError("tool_validation_error", \`\${label} must be an object.\`);
2033
+ }
2034
+
2035
+ if (block.type === "text") {
2036
+ if (typeof block.text !== "string" || block.text.trim().length === 0) {
2037
+ throw agentKitError("tool_validation_error", \`\${label}.text must be a non-empty string.\`);
2038
+ }
2039
+
2040
+ return;
2041
+ }
2042
+
2043
+ if (block.type === "card") {
2044
+ if (typeof block.title !== "string" || block.title.trim().length === 0) {
2045
+ throw agentKitError("tool_validation_error", \`\${label}.title must be a non-empty string.\`);
2046
+ }
2047
+
2048
+ validateOptionalString(block.subtitle, \`\${label}.subtitle\`);
2049
+ validateRenderFields(block.fields, \`\${label}.fields\`);
2050
+ validateRenderActions(block.actions, \`\${label}.actions\`);
2051
+ return;
2052
+ }
2053
+
2054
+ throw agentKitError("tool_validation_error", \`\${label}.type must be "text" or "card".\`);
2055
+ }
2056
+
2057
+ function validateRenderFields(value, label) {
2058
+ if (value === undefined) {
2059
+ return;
2060
+ }
2061
+
2062
+ if (!Array.isArray(value)) {
2063
+ throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
2064
+ }
2065
+
2066
+ for (const [index, field] of value.entries()) {
2067
+ if (!isRecord(field)) {
2068
+ throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
2069
+ }
2070
+
2071
+ validateRequiredString(field.label, \`\${label}[\${index}].label\`);
2072
+ validateRequiredString(field.value, \`\${label}[\${index}].value\`);
2073
+ }
2074
+ }
2075
+
2076
+ function validateRenderActions(value, label) {
2077
+ if (value === undefined) {
2078
+ return;
2079
+ }
2080
+
2081
+ if (!Array.isArray(value)) {
2082
+ throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
2083
+ }
2084
+
2085
+ for (const [index, action] of value.entries()) {
2086
+ if (!isRecord(action)) {
2087
+ throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
2088
+ }
2089
+
2090
+ validateRequiredString(action.label, \`\${label}[\${index}].label\`);
2091
+ validateOptionalString(action.href, \`\${label}[\${index}].href\`);
2092
+ }
2093
+ }
2094
+
2095
+ function validateRequiredString(value, label) {
2096
+ if (typeof value !== "string" || value.trim().length === 0) {
2097
+ throw agentKitError("tool_validation_error", \`\${label} must be a non-empty string.\`);
2098
+ }
2099
+ }
2100
+
2101
+ function validateOptionalString(value, label) {
2102
+ if (value !== undefined && typeof value !== "string") {
2103
+ throw agentKitError("tool_validation_error", \`\${label} must be a string when provided.\`);
2104
+ }
2105
+ }
2106
+
1856
2107
  function resolveToolSecrets(tool, env) {
1857
2108
  const secrets = {};
1858
2109
 
@@ -2119,6 +2370,20 @@ function allowsNull(schema) {
2119
2370
  }
2120
2371
 
2121
2372
  async function authorizeChat(request, env, body) {
2373
+ return authorizeDeployAccess(request, env, body, {
2374
+ notConfiguredCode: "chat_access_not_configured",
2375
+ notConfiguredMessage: "Hosted chat requires a deploy access token gate.",
2376
+ });
2377
+ }
2378
+
2379
+ async function authorizeConversationRead(request, env) {
2380
+ return authorizeDeployAccess(request, env, {}, {
2381
+ notConfiguredCode: "conversation_access_not_configured",
2382
+ notConfiguredMessage: "Hosted conversation reads require a deploy access token gate.",
2383
+ });
2384
+ }
2385
+
2386
+ async function authorizeDeployAccess(request, env, body, options) {
2122
2387
  const runtimeGate = await authorizeRuntimeGate(request, env, body);
2123
2388
 
2124
2389
  if (runtimeGate) {
@@ -2129,52 +2394,28 @@ async function authorizeChat(request, env, body) {
2129
2394
  return runtimeGate;
2130
2395
  }
2131
2396
 
2132
- if (manifest.access.mode === "public") {
2133
- return null;
2134
- }
2397
+ const token = deployAccessToken(request, body);
2398
+ const acceptedTokens = [env.AGENTKIT_PRIVATE_TOKEN, env.AGENTKIT_ACCESS_TOKEN].filter(
2399
+ (value) => typeof value === "string" && value.length > 0,
2400
+ );
2135
2401
 
2136
- if (manifest.access.mode === "private") {
2137
- if (!env.AGENTKIT_PRIVATE_TOKEN) {
2138
- return Response.json(
2139
- {
2140
- error: {
2141
- code: "deploy_not_ready",
2142
- message: "Private deploy requires AGENTKIT_PRIVATE_TOKEN to be set as a Cloudflare secret.",
2143
- },
2402
+ if (acceptedTokens.length === 0) {
2403
+ return Response.json(
2404
+ {
2405
+ error: {
2406
+ code: options.notConfiguredCode,
2407
+ message: options.notConfiguredMessage,
2144
2408
  },
2145
- { status: 503 },
2146
- );
2147
- }
2148
-
2149
- const expected = \`Bearer \${env.AGENTKIT_PRIVATE_TOKEN}\`;
2150
- if (request.headers.get("authorization") !== expected) {
2151
- return Response.json(
2152
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2153
- { status: 401 },
2154
- );
2155
- }
2409
+ },
2410
+ { status: 503 },
2411
+ );
2156
2412
  }
2157
2413
 
2158
- if (manifest.access.mode === "token") {
2159
- if (!env.AGENTKIT_ACCESS_TOKEN) {
2160
- return Response.json(
2161
- {
2162
- error: {
2163
- code: "deploy_not_ready",
2164
- message: "Token deploy requires AGENTKIT_ACCESS_TOKEN to be set as a Cloudflare secret.",
2165
- },
2166
- },
2167
- { status: 503 },
2168
- );
2169
- }
2170
-
2171
- const expected = \`Bearer \${env.AGENTKIT_ACCESS_TOKEN}\`;
2172
- if (request.headers.get("authorization") !== expected) {
2173
- return Response.json(
2174
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2175
- { status: 401 },
2176
- );
2177
- }
2414
+ if (!token || !acceptedTokens.includes(token)) {
2415
+ return Response.json(
2416
+ { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2417
+ { status: 401 },
2418
+ );
2178
2419
  }
2179
2420
 
2180
2421
  return null;
@@ -2276,8 +2517,7 @@ async function persist(env, input) {
2276
2517
  return {};
2277
2518
  }
2278
2519
 
2279
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(input.conversationId);
2280
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2520
+ const stub = hostedConversationStore(env);
2281
2521
  const response = await stub.fetch("https://agentkit.internal/messages", {
2282
2522
  method: "POST",
2283
2523
  body: JSON.stringify(input),
@@ -2295,8 +2535,7 @@ async function loadConversationMessages(env, conversationId) {
2295
2535
  return [];
2296
2536
  }
2297
2537
 
2298
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2299
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2538
+ const stub = hostedConversationStore(env);
2300
2539
  const response = await stub.fetch(
2301
2540
  \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2302
2541
  );
@@ -2306,7 +2545,7 @@ async function loadConversationMessages(env, conversationId) {
2306
2545
  }
2307
2546
 
2308
2547
  const payload = await response.json();
2309
- return Array.isArray(payload.messages)
2548
+ const messages = Array.isArray(payload.messages)
2310
2549
  ? payload.messages.filter((message) =>
2311
2550
  message &&
2312
2551
  typeof message === "object" &&
@@ -2314,6 +2553,39 @@ async function loadConversationMessages(env, conversationId) {
2314
2553
  typeof message.content === "string"
2315
2554
  )
2316
2555
  : [];
2556
+
2557
+ if (messages.length > 0) {
2558
+ return messages;
2559
+ }
2560
+
2561
+ const legacyStub = legacyConversationStore(env, conversationId);
2562
+ const legacyResponse = await legacyStub.fetch(
2563
+ \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2564
+ );
2565
+
2566
+ if (!legacyResponse.ok) {
2567
+ return [];
2568
+ }
2569
+
2570
+ const legacyPayload = await legacyResponse.json();
2571
+ return Array.isArray(legacyPayload.messages)
2572
+ ? legacyPayload.messages.filter((message) =>
2573
+ message &&
2574
+ typeof message === "object" &&
2575
+ (message.role === "user" || message.role === "assistant") &&
2576
+ typeof message.content === "string"
2577
+ )
2578
+ : [];
2579
+ }
2580
+
2581
+ function hostedConversationStore(env) {
2582
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName("agentkit-conversations");
2583
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2584
+ }
2585
+
2586
+ function legacyConversationStore(env, conversationId) {
2587
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2588
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2317
2589
  }
2318
2590
 
2319
2591
  function normalizeConversationId(value) {
@@ -2590,6 +2862,18 @@ function titleFromMessage(content) {
2590
2862
  const compact = content.split(/\\r?\\n/, 1)[0].replace(/\\s+/g, " ").trim();
2591
2863
  return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
2592
2864
  }
2865
+
2866
+ function parseStoredJson(value, fallback) {
2867
+ if (typeof value !== "string") {
2868
+ return fallback;
2869
+ }
2870
+
2871
+ try {
2872
+ return JSON.parse(value);
2873
+ } catch {
2874
+ return fallback;
2875
+ }
2876
+ }
2593
2877
  `;
2594
2878
  }
2595
2879
 
@@ -2673,6 +2957,7 @@ CREATE TABLE IF NOT EXISTS tool_calls (
2673
2957
  status TEXT NOT NULL,
2674
2958
  input_json TEXT NOT NULL,
2675
2959
  output_json TEXT,
2960
+ rendered_json TEXT,
2676
2961
  error_message TEXT,
2677
2962
  created_at TEXT NOT NULL,
2678
2963
  completed_at TEXT