@andreprado/agentkit 0.1.0-alpha.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/docs/guides/add-channel.md +251 -7
- package/docs/guides/add-knowledge.md +10 -0
- package/docs/guides/add-managed-composio.md +165 -0
- package/docs/guides/add-tool.md +10 -3
- package/docs/guides/channel-security.md +162 -32
- package/docs/guides/connect-discord.md +178 -0
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/connect-telegram.md +61 -1
- package/docs/guides/connect-whatsapp-evolution.md +121 -0
- package/docs/guides/connect-whatsapp-uazapi.md +139 -0
- package/docs/guides/connect-whatsapp-zapster.md +119 -16
- package/docs/guides/create-agent.md +31 -4
- package/docs/guides/debug-channel.md +159 -0
- package/docs/guides/improve-from-production.md +151 -0
- package/docs/guides/prepare-deploy.md +32 -14
- package/docs/guides/replay-production-traces.md +72 -0
- package/docs/guides/run-evals.md +95 -25
- package/docs/guides/security-rules.md +9 -5
- package/docs/guides/send-feedback.md +135 -0
- package/docs/guides/use-jev.md +67 -0
- package/docs/guides/use-provider.md +70 -3
- package/docs/llms-full.txt +295 -25
- package/docs/llms.txt +54 -7
- package/package.json +3 -7
- package/src/cli/args.ts +23 -2
- package/src/cli/cloud-client.ts +121 -9
- package/src/cli/commands/channels.ts +856 -36
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/commands/provider.ts +47 -0
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/deploy-chat-ui.ts +232 -18
- package/src/cli/deploy-readiness.ts +227 -14
- package/src/cli/help.ts +67 -9
- package/src/cli/index.ts +740 -35
- package/src/cli/new-command.ts +41 -0
- package/src/cloud/client.ts +4 -3
- package/src/cloud/contracts.ts +1 -1
- package/src/create-project.ts +18 -35
- package/src/index.ts +565 -11
- package/src/providers/codex-auth.ts +111 -0
- package/src/providers/pi.ts +88 -19
- package/src/providers/test.ts +36 -0
- package/src/providers/types.ts +8 -0
- package/src/runtime/channel-test-harness.ts +21 -1
- package/src/runtime/channels/discord.ts +904 -0
- package/src/runtime/channels/generic-webhook.ts +682 -0
- package/src/runtime/channels/net-guard.ts +480 -0
- package/src/runtime/channels/provider-fetch.ts +54 -0
- package/src/runtime/channels/slack.ts +652 -0
- package/src/runtime/channels/telegram.ts +379 -15
- package/src/runtime/channels/whatsapp-evolution.ts +1330 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1192 -0
- package/src/runtime/channels/whatsapp-zapster.ts +702 -40
- package/src/runtime/channels.ts +83 -3
- package/src/runtime/chat.ts +70 -44
- package/src/runtime/config.ts +512 -20
- package/src/runtime/core/manifest.ts +75 -5
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/deploy-readiness.ts +34 -4
- package/src/runtime/dev-server.ts +639 -39
- package/src/runtime/env.ts +8 -3
- package/src/runtime/evals.ts +445 -74
- package/src/runtime/improve.ts +868 -0
- package/src/runtime/inspect.ts +173 -4
- package/src/runtime/integrations/composio.ts +425 -0
- package/src/runtime/knowledge/embeddings.ts +45 -7
- package/src/runtime/knowledge/ingest.ts +69 -6
- package/src/runtime/knowledge/retrieve.ts +25 -5
- package/src/runtime/knowledge/schema.ts +45 -1
- package/src/runtime/knowledge/vector.ts +30 -30
- package/src/runtime/prompt-context.ts +141 -0
- package/src/runtime/runtime-contract.ts +71 -7
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +1010 -208
- package/src/runtime/targets/container/server.ts +1 -1
- package/src/runtime/targets/vps/deploy.ts +26 -9
- package/src/runtime/tool-runner.ts +9 -1
- package/src/runtime/tools.ts +26 -2
- package/src/runtime/transcription.ts +483 -0
- package/src/storage/sqlite.ts +7 -2
- package/src/templates/blank.ts +37 -9
- package/src/templates/dentista.ts +40 -14
- package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
- package/src/templates/skills/agentkit-channels/SKILL.md +66 -1
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
- package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
- package/src/templates/skills/agentkit-database/SKILL.md +11 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
- package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
- package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
- package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
- package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
- package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
- package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
- package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
- package/src/templates/skills/agentkit-security/SKILL.md +5 -2
- package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
- package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
- package/src/templates/support.ts +42 -12
- package/docs/guides/agentkit-skills-architecture.md +0 -471
- package/docs/guides/channels-implementation-map.md +0 -243
- package/docs/guides/channels-production-handoff.md +0 -101
- 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+(
|
|
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", "
|
|
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
|
-
|
|
430
|
-
|
|
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
|
-
|
|
530
|
+
throw new Error(\`\${context} must be an environment variable name, not a secret value.\`);
|
|
434
531
|
}
|
|
435
532
|
|
|
436
|
-
return
|
|
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:
|
|
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
|
-
|
|
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 === "
|
|
763
|
-
return
|
|
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
|
-
|
|
767
|
-
|
|
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
|
|
771
|
-
return
|
|
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\\/[^/]+\\/
|
|
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
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1614
|
-
|
|
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
|
|
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
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
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
|
-
|
|
1656
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
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 (
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
{
|
|
1962
|
-
|
|
1963
|
-
|
|
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
|
-
|
|
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 (
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|