@andreprado/agentkit 0.1.0-alpha.18 → 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 (56) hide show
  1. package/README.md +3 -0
  2. package/docs/guides/add-channel.md +12 -6
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +4 -2
  5. package/docs/guides/channel-security.md +26 -2
  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 +8 -2
  9. package/docs/guides/improve-from-production.md +151 -0
  10. package/docs/guides/prepare-deploy.md +29 -8
  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 +100 -11
  16. package/docs/llms.txt +17 -1
  17. package/package.json +1 -1
  18. package/src/cli/args.ts +23 -2
  19. package/src/cli/cloud-client.ts +63 -0
  20. package/src/cli/commands/channels.ts +139 -14
  21. package/src/cli/deploy-readiness.ts +5 -2
  22. package/src/cli/help.ts +26 -2
  23. package/src/cli/index.ts +382 -17
  24. package/src/create-project.ts +13 -2
  25. package/src/index.ts +42 -3
  26. package/src/providers/pi.ts +49 -15
  27. package/src/runtime/channel-test-harness.ts +4 -1
  28. package/src/runtime/channels/discord.ts +887 -0
  29. package/src/runtime/channels.ts +15 -0
  30. package/src/runtime/config.ts +35 -3
  31. package/src/runtime/dev-server.ts +149 -8
  32. package/src/runtime/evals.ts +27 -6
  33. package/src/runtime/improve.ts +868 -0
  34. package/src/runtime/knowledge/retrieve.ts +25 -5
  35. package/src/runtime/knowledge/schema.ts +45 -1
  36. package/src/runtime/runtime-contract.ts +54 -0
  37. package/src/runtime/targets/cloudflare/build.ts +248 -193
  38. package/src/runtime/targets/vps/deploy.ts +1 -1
  39. package/src/storage/sqlite.ts +7 -2
  40. package/src/templates/skills/agentkit-capsule/SKILL.md +8 -1
  41. package/src/templates/skills/agentkit-capsule/references/docs-router.md +1 -2
  42. package/src/templates/skills/agentkit-channels/SKILL.md +6 -1
  43. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +2 -1
  44. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  45. package/src/templates/skills/agentkit-deploy/SKILL.md +6 -0
  46. package/src/templates/skills/agentkit-evals/SKILL.md +12 -3
  47. package/src/templates/skills/agentkit-improve/SKILL.md +86 -0
  48. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  49. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  50. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  51. package/src/templates/skills/agentkit-integrations/SKILL.md +1 -0
  52. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  53. package/src/templates/skills/agentkit-provider/SKILL.md +4 -1
  54. package/src/templates/skills/agentkit-security/SKILL.md +3 -2
  55. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +9 -0
  56. package/src/templates/support.ts +4 -2
@@ -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) {
@@ -873,7 +893,7 @@ export class AgentKitConversationStore extends DurableObject {
873
893
 
874
894
  readToolCalls(runId) {
875
895
  const rows = this.sql.exec(
876
- "SELECT id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, error_message, created_at, completed_at FROM tool_calls WHERE run_id = ? ORDER BY rowid ASC",
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",
877
897
  runId,
878
898
  ).toArray();
879
899
 
@@ -886,6 +906,7 @@ export class AgentKitConversationStore extends DurableObject {
886
906
  status: String(row.status),
887
907
  input: parseStoredJson(row.input_json, {}),
888
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),
889
910
  errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
890
911
  createdAt: String(row.created_at),
891
912
  completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
@@ -943,7 +964,7 @@ export class AgentKitConversationStore extends DurableObject {
943
964
 
944
965
  for (const toolCall of input.toolCalls || []) {
945
966
  this.sql.exec(
946
- "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
947
968
  String(toolCall.id || crypto.randomUUID()),
948
969
  runId,
949
970
  conversationId,
@@ -952,6 +973,7 @@ export class AgentKitConversationStore extends DurableObject {
952
973
  String(toolCall.status || "completed"),
953
974
  JSON.stringify(toolCall.input ?? {}),
954
975
  toolCall.output === undefined ? null : JSON.stringify(toolCall.output),
976
+ toolCall.rendered === undefined ? null : JSON.stringify(toolCall.rendered),
955
977
  toolCall.errorMessage ? String(toolCall.errorMessage) : null,
956
978
  now,
957
979
  now,
@@ -1005,6 +1027,15 @@ export default {
1005
1027
  }
1006
1028
 
1007
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
+
1008
1039
  return Response.json({
1009
1040
  agent: manifest.name,
1010
1041
  status: "ready",
@@ -1014,7 +1045,16 @@ export default {
1014
1045
  }
1015
1046
 
1016
1047
  if (request.method === "GET" && url.pathname === "/health") {
1017
- 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 });
1018
1058
  }
1019
1059
 
1020
1060
  if (isHostedChannelWebhookPath(url.pathname)) {
@@ -1045,14 +1085,6 @@ export default {
1045
1085
  return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
1046
1086
  }
1047
1087
 
1048
- if (request.method === "GET" && url.pathname === "/_agentkit/turso/health") {
1049
- return handleTursoHealth(request, env);
1050
- }
1051
-
1052
- if (request.method === "POST" && url.pathname === "/_agentkit/turso/query") {
1053
- return handleTursoQuery(request, env);
1054
- }
1055
-
1056
1088
  if (url.pathname.startsWith("/files/")) {
1057
1089
  return handleFile(request, env, url.pathname.slice("/files/".length));
1058
1090
  }
@@ -1063,8 +1095,8 @@ export default {
1063
1095
 
1064
1096
  function isHostedChannelWebhookPath(pathname) {
1065
1097
  return (
1066
- /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta))\\/webhook$/.test(pathname) ||
1067
- /^\\/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) ||
1068
1100
  /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
1069
1101
  );
1070
1102
  }
@@ -1103,71 +1135,6 @@ async function forwardHostedChannelWebhook(request, env, url) {
1103
1135
  });
1104
1136
  }
1105
1137
 
1106
- async function handleTursoHealth(request, env) {
1107
- const auth = authorizeAdmin(request, env);
1108
-
1109
- if (auth) {
1110
- return auth;
1111
- }
1112
-
1113
- const configError = requireTurso(env);
1114
-
1115
- if (configError) {
1116
- return configError;
1117
- }
1118
-
1119
- const response = await fetch(toTursoBaseUrl(env.TURSO_DATABASE_URL) + "/health", {
1120
- headers: {
1121
- Authorization: \`Bearer \${env.TURSO_AUTH_TOKEN}\`,
1122
- },
1123
- });
1124
-
1125
- return Response.json({
1126
- driver: "turso",
1127
- ok: response.ok,
1128
- status: response.status,
1129
- }, { status: response.ok ? 200 : 502 });
1130
- }
1131
-
1132
- async function handleTursoQuery(request, env) {
1133
- const auth = authorizeAdmin(request, env);
1134
-
1135
- if (auth) {
1136
- return auth;
1137
- }
1138
-
1139
- const configError = requireTurso(env);
1140
-
1141
- if (configError) {
1142
- return configError;
1143
- }
1144
-
1145
- let body;
1146
-
1147
- try {
1148
- body = await request.json();
1149
- } catch {
1150
- return Response.json(
1151
- { error: { code: "validation_error", message: "Expected JSON body with sql and optional args fields." } },
1152
- { status: 400 },
1153
- );
1154
- }
1155
-
1156
- const sql = String(body.sql || "").trim();
1157
-
1158
- if (!sql) {
1159
- return Response.json({ error: { code: "validation_error", message: "sql must be non-empty." } }, { status: 400 });
1160
- }
1161
-
1162
- const args = Array.isArray(body.args) ? body.args : [];
1163
- try {
1164
- const result = await executeTursoSql(env, sql, args);
1165
- return Response.json(result);
1166
- } catch (error) {
1167
- return hostedErrorResponse(error);
1168
- }
1169
- }
1170
-
1171
1138
  async function handleFile(request, env, rawKey) {
1172
1139
  const auth = authorizeAdmin(request, env);
1173
1140
 
@@ -1295,6 +1262,7 @@ async function handleTool(request, env, toolName) {
1295
1262
  },
1296
1263
  input: result.input,
1297
1264
  output: result.output,
1265
+ ...(result.rendered ? { rendered: result.rendered } : {}),
1298
1266
  });
1299
1267
  } catch (error) {
1300
1268
  return hostedErrorResponse(error);
@@ -1340,35 +1308,12 @@ async function handleChat(request, env) {
1340
1308
  let content;
1341
1309
  const toolCalls = [];
1342
1310
 
1343
- const toolRequest = parseToolRequest(message);
1344
-
1345
- if (toolRequest) {
1346
- try {
1347
- const toolResult = await executeHostedTool({
1348
- name: toolRequest.name,
1349
- input: toolRequest.input,
1350
- env,
1351
- signal: request.signal,
1352
- invocation: "chat",
1353
- });
1354
- toolCalls.push(toolResult);
1355
- content =
1356
- toolResult.visibility === "internal"
1357
- ? \`Tool \${toolResult.name}: completed\`
1358
- : \`Tool \${toolResult.name}: \${JSON.stringify(toolResult.output)}\`;
1359
- } catch (error) {
1360
- await persistFailure(env, conversationId, message, error);
1361
- return hostedErrorResponse(error);
1362
- }
1363
- }
1364
-
1365
- if (content === undefined) {
1366
- try {
1367
- content = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal);
1368
- } catch (error) {
1369
- await persistFailure(env, conversationId, message, error);
1370
- return hostedErrorResponse(error);
1371
- }
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);
1372
1317
  }
1373
1318
 
1374
1319
  const persisted = await persist(env, {
@@ -1388,7 +1333,7 @@ async function handleChat(request, env) {
1388
1333
  }
1389
1334
 
1390
1335
  async function handleConversationRead(request, env, action, conversationId) {
1391
- const auth = await authorizeChat(request, env, {});
1336
+ const auth = await authorizeConversationRead(request, env);
1392
1337
 
1393
1338
  if (auth) {
1394
1339
  return auth;
@@ -1436,7 +1381,7 @@ function isRecord(value) {
1436
1381
  return typeof value === "object" && value !== null && !Array.isArray(value);
1437
1382
  }
1438
1383
 
1439
- async function callHostedProvider(env, messages, signal) {
1384
+ async function callHostedProvider(env, messages, signal, toolCalls = []) {
1440
1385
  const adapter = getProviderAdapter(manifest.provider);
1441
1386
  const hostedTools = createHostedTools(env);
1442
1387
  const clock = buildRuntimeClockContext({
@@ -1457,12 +1402,14 @@ async function callHostedProvider(env, messages, signal) {
1457
1402
  ),
1458
1403
  messages,
1459
1404
  tools: hostedTools,
1460
- toolRuntime: createHostedProviderToolRuntime(env, signal, clock),
1405
+ toolRuntime: createHostedProviderToolRuntime(env, signal, clock, toolCalls),
1461
1406
  env,
1462
1407
  signal,
1463
1408
  });
1464
1409
 
1465
- return result.message.content;
1410
+ return {
1411
+ content: result.message.content,
1412
+ };
1466
1413
  }
1467
1414
 
1468
1415
  function appendHostedKnowledgePromptPolicy(baseInstructions) {
@@ -1888,36 +1835,17 @@ async function hostedFakeEmbedding(input, dimensions) {
1888
1835
  return norm === 0 ? values : values.map((value) => Number((value / norm).toFixed(8)));
1889
1836
  }
1890
1837
 
1891
- async function persistFailure(env, conversationId, userMessage, error) {
1838
+ async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
1892
1839
  await persist(env, {
1893
1840
  conversationId,
1894
1841
  userMessage,
1895
1842
  status: "failed",
1896
1843
  errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
1897
1844
  errorMessage: error instanceof Error ? error.message : String(error),
1898
- toolCalls: [],
1845
+ toolCalls,
1899
1846
  }).catch(() => undefined);
1900
1847
  }
1901
1848
 
1902
- function parseToolRequest(content) {
1903
- let parsed;
1904
-
1905
- try {
1906
- parsed = JSON.parse(content);
1907
- } catch {
1908
- return null;
1909
- }
1910
-
1911
- if (!isRecord(parsed) || typeof parsed.tool !== "string" || parsed.tool.length === 0) {
1912
- return null;
1913
- }
1914
-
1915
- return {
1916
- name: parsed.tool,
1917
- input: "input" in parsed ? parsed.input : {},
1918
- };
1919
- }
1920
-
1921
1849
  function inspectHostedDatabase() {
1922
1850
  const schema = manifest.storage.database.driver === "turso" ? manifest.storage.database.schema : null;
1923
1851
 
@@ -1971,21 +1899,28 @@ function hostedIntegrations(env) {
1971
1899
  );
1972
1900
  }
1973
1901
 
1974
- function createHostedProviderToolRuntime(env, signal, clock) {
1902
+ function createHostedProviderToolRuntime(env, signal, clock, toolCalls) {
1975
1903
  const hostedTools = createHostedTools(env);
1976
1904
 
1977
1905
  return {
1978
1906
  definitions: hostedTools,
1979
- execute(request) {
1980
- return executeHostedTool({
1981
- tools: hostedTools,
1982
- name: request.name,
1983
- input: request.input,
1984
- env,
1985
- signal,
1986
- invocation: "chat",
1987
- clock,
1988
- });
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
+ }
1989
1924
  },
1990
1925
  };
1991
1926
  }
@@ -2008,6 +1943,12 @@ async function executeHostedTool(request) {
2008
1943
  timeZone: manifest.timeZone,
2009
1944
  env: request.env,
2010
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
+ };
2011
1952
  const output = await runToolWithTimeout(
2012
1953
  tool,
2013
1954
  input,
@@ -2016,12 +1957,7 @@ async function executeHostedTool(request) {
2016
1957
  database,
2017
1958
  signal: request.signal,
2018
1959
  clock,
2019
- runtime: {
2020
- environment: "hosted",
2021
- invocation: request.invocation ?? "tool",
2022
- target: "cloudflare",
2023
- database: manifest.storage.database.driver === "turso" ? "turso" : "none",
2024
- },
1960
+ runtime,
2025
1961
  },
2026
1962
  tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
2027
1963
  );
@@ -2030,6 +1966,8 @@ async function executeHostedTool(request) {
2030
1966
  validateSchema(output, tool.outputSchema, \`tool "\${tool.name}" output\`);
2031
1967
  }
2032
1968
 
1969
+ const rendered = renderHostedToolOutput(tool, input, output, runtime, clock);
1970
+
2033
1971
  return {
2034
1972
  id,
2035
1973
  name: tool.name,
@@ -2037,9 +1975,135 @@ async function executeHostedTool(request) {
2037
1975
  status: "completed",
2038
1976
  input,
2039
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 } : {}),
2040
2027
  };
2041
2028
  }
2042
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
+
2043
2107
  function resolveToolSecrets(tool, env) {
2044
2108
  const secrets = {};
2045
2109
 
@@ -2306,6 +2370,20 @@ function allowsNull(schema) {
2306
2370
  }
2307
2371
 
2308
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) {
2309
2387
  const runtimeGate = await authorizeRuntimeGate(request, env, body);
2310
2388
 
2311
2389
  if (runtimeGate) {
@@ -2316,52 +2394,28 @@ async function authorizeChat(request, env, body) {
2316
2394
  return runtimeGate;
2317
2395
  }
2318
2396
 
2319
- if (manifest.access.mode === "public") {
2320
- return null;
2321
- }
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
+ );
2322
2401
 
2323
- if (manifest.access.mode === "private") {
2324
- if (!env.AGENTKIT_PRIVATE_TOKEN) {
2325
- return Response.json(
2326
- {
2327
- error: {
2328
- code: "deploy_not_ready",
2329
- message: "Private deploy requires AGENTKIT_PRIVATE_TOKEN to be set as a Cloudflare secret.",
2330
- },
2402
+ if (acceptedTokens.length === 0) {
2403
+ return Response.json(
2404
+ {
2405
+ error: {
2406
+ code: options.notConfiguredCode,
2407
+ message: options.notConfiguredMessage,
2331
2408
  },
2332
- { status: 503 },
2333
- );
2334
- }
2335
-
2336
- const expected = \`Bearer \${env.AGENTKIT_PRIVATE_TOKEN}\`;
2337
- if (request.headers.get("authorization") !== expected) {
2338
- return Response.json(
2339
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2340
- { status: 401 },
2341
- );
2342
- }
2409
+ },
2410
+ { status: 503 },
2411
+ );
2343
2412
  }
2344
2413
 
2345
- if (manifest.access.mode === "token") {
2346
- if (!env.AGENTKIT_ACCESS_TOKEN) {
2347
- return Response.json(
2348
- {
2349
- error: {
2350
- code: "deploy_not_ready",
2351
- message: "Token deploy requires AGENTKIT_ACCESS_TOKEN to be set as a Cloudflare secret.",
2352
- },
2353
- },
2354
- { status: 503 },
2355
- );
2356
- }
2357
-
2358
- const expected = \`Bearer \${env.AGENTKIT_ACCESS_TOKEN}\`;
2359
- if (request.headers.get("authorization") !== expected) {
2360
- return Response.json(
2361
- { error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
2362
- { status: 401 },
2363
- );
2364
- }
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
+ );
2365
2419
  }
2366
2420
 
2367
2421
  return null;
@@ -2903,6 +2957,7 @@ CREATE TABLE IF NOT EXISTS tool_calls (
2903
2957
  status TEXT NOT NULL,
2904
2958
  input_json TEXT NOT NULL,
2905
2959
  output_json TEXT,
2960
+ rendered_json TEXT,
2906
2961
  error_message TEXT,
2907
2962
  created_at TEXT NOT NULL,
2908
2963
  completed_at TEXT
@@ -118,7 +118,7 @@ function renderCompose(agentName: string): string {
118
118
  - agentkit_data:/data
119
119
  - agentkit_files:/capsule/.agentkit/files
120
120
  healthcheck:
121
- test: ["CMD", "bun", "-e", "const r=await fetch('http://127.0.0.1:4123/health'); process.exit(r.ok?0:1)"]
121
+ test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4123/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
122
122
  interval: 30s
123
123
  timeout: 5s
124
124
  retries: 3
@@ -6,7 +6,7 @@ import type { DatabaseArgs, DatabaseResult, DatabaseRow, DatabaseStatement } fro
6
6
  import type { AgentMessageRole, ProviderRunResult } from "../providers";
7
7
  import type { LoadedAgentCapsule } from "../runtime/config";
8
8
  import { AgentKitError } from "../runtime/errors";
9
- import { KNOWLEDGE_MIGRATION_ID, KNOWLEDGE_SCHEMA_SQL } from "../runtime/knowledge/schema";
9
+ import { applyKnowledgeSchema, KNOWLEDGE_MIGRATION_ID, KNOWLEDGE_SCHEMA_SQL } from "../runtime/knowledge/schema";
10
10
 
11
11
  export type ConversationSummary = {
12
12
  id: string;
@@ -806,7 +806,12 @@ export class SqliteAgentKitStore {
806
806
  continue;
807
807
  }
808
808
 
809
- this.db.exec(migration.sql);
809
+ if (migration.id === KNOWLEDGE_MIGRATION_ID) {
810
+ applyKnowledgeSchema(this.db);
811
+ } else {
812
+ this.db.exec(migration.sql);
813
+ }
814
+
810
815
  this.db
811
816
  .prepare("INSERT INTO agentkit_migrations (id, applied_at) VALUES ($id, $appliedAt)")
812
817
  .run({ $id: migration.id, $appliedAt: new Date().toISOString() });