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