@andreprado/agentkit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -77
- package/docs/guides/add-channel.md +14 -92
- package/docs/guides/add-knowledge.md +0 -21
- package/docs/guides/add-tool.md +5 -11
- package/docs/guides/channel-security.md +3 -207
- package/docs/guides/connect-discord.md +7 -172
- package/docs/guides/connect-slack.md +6 -121
- package/docs/guides/connect-telegram.md +6 -165
- package/docs/guides/connect-whatsapp-evolution.md +6 -116
- package/docs/guides/connect-whatsapp-uazapi.md +6 -134
- package/docs/guides/connect-whatsapp-zapster.md +6 -202
- package/docs/guides/create-agent.md +5 -14
- package/docs/guides/debug-channel.md +4 -156
- package/docs/guides/improve-local.md +13 -0
- package/docs/guides/local-only-migration.md +35 -0
- package/docs/guides/replay-local-traces.md +11 -0
- package/docs/guides/run-evals.md +2 -4
- package/docs/guides/security-rules.md +5 -154
- package/docs/guides/use-jev.md +3 -6
- package/docs/guides/use-provider.md +0 -3
- package/docs/guides/write-feedback.md +10 -0
- package/docs/llms-full.txt +27 -448
- package/docs/llms.txt +8 -44
- package/package.json +2 -4
- package/src/cli/commands/channels.ts +8 -1613
- package/src/cli/commands/feedback.ts +8 -86
- package/src/cli/constants.ts +0 -3
- package/src/cli/flags.ts +0 -28
- package/src/cli/help.ts +16 -92
- package/src/cli/index.ts +15 -1091
- package/src/index.ts +6 -158
- package/src/providers/pi.ts +2 -0
- package/src/runtime/channels/discord.ts +16 -8
- package/src/runtime/channels/generic-webhook.ts +21 -313
- package/src/runtime/channels/net-guard.ts +480 -0
- package/src/runtime/channels/provider-fetch.ts +54 -0
- package/src/runtime/channels/slack.ts +9 -3
- package/src/runtime/channels/telegram.ts +43 -10
- package/src/runtime/channels/whatsapp-evolution.ts +33 -60
- package/src/runtime/channels/whatsapp-uazapi.ts +33 -164
- package/src/runtime/channels/whatsapp-zapster.ts +35 -7
- package/src/runtime/chat.ts +5 -3
- package/src/runtime/config.ts +36 -147
- package/src/runtime/database.ts +2 -2
- package/src/runtime/dev-server.ts +8 -8
- package/src/runtime/env.ts +11 -0
- package/src/runtime/improve.ts +2 -262
- package/src/runtime/inspect.ts +13 -73
- package/src/runtime/knowledge/embeddings.ts +45 -7
- package/src/runtime/knowledge/ingest.ts +70 -7
- package/src/runtime/knowledge/tool.ts +16 -2
- package/src/runtime/knowledge/vector.ts +31 -31
- package/src/runtime/tool-runner.ts +5 -3
- package/src/runtime/tools.ts +10 -14
- package/src/storage/sqlite.ts +11 -32
- package/src/templates/blank.ts +15 -102
- package/src/templates/common.ts +60 -0
- package/src/templates/dentista.ts +7 -74
- package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
- package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
- package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
- package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
- package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
- package/src/templates/skills/agentkit-database/SKILL.md +2 -4
- package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
- package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
- package/src/templates/skills/agentkit-provider/SKILL.md +0 -1
- package/src/templates/skills/agentkit-security/SKILL.md +1 -3
- package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
- package/src/templates/support.ts +8 -92
- package/docs/guides/add-managed-composio.md +0 -165
- package/docs/guides/improve-from-production.md +0 -151
- package/docs/guides/prepare-deploy.md +0 -227
- package/docs/guides/replay-production-traces.md +0 -72
- package/docs/guides/send-feedback.md +0 -135
- package/src/cli/cloud-client.ts +0 -377
- package/src/cli/deploy-chat-ui.ts +0 -606
- package/src/cli/deploy-readiness.ts +0 -561
- package/src/cloud/artifact.ts +0 -139
- package/src/cloud/client.ts +0 -80
- package/src/cloud/contracts.ts +0 -63
- package/src/cloud/index.ts +0 -3
- package/src/runtime/build.ts +0 -43
- package/src/runtime/core/deploy-state.ts +0 -54
- package/src/runtime/core/manifest.ts +0 -283
- package/src/runtime/core/targets.ts +0 -133
- package/src/runtime/deploy-readiness.ts +0 -135
- package/src/runtime/deploy.ts +0 -1
- package/src/runtime/integrations/composio.ts +0 -425
- package/src/runtime/targets/cloudflare/build.ts +0 -3319
- package/src/runtime/targets/container/build.ts +0 -146
- package/src/runtime/targets/container/server.ts +0 -33
- package/src/runtime/targets/vps/deploy.ts +0 -223
- package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
- package/src/templates/skills/agentkit-integrations/SKILL.md +0 -98
|
@@ -1,3319 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
|
|
6
|
-
import { build as esbuild, type Plugin } from "esbuild";
|
|
7
|
-
|
|
8
|
-
import { loadAgentCapsule, type LoadedAgentCapsule } from "../../config";
|
|
9
|
-
import { buildSharedAgentManifest, type ManifestSchema, type SharedAgentManifest } from "../../core/manifest";
|
|
10
|
-
import { AgentKitError } from "../../errors";
|
|
11
|
-
import { resolveKnowledgeConfig } from "../../knowledge/config";
|
|
12
|
-
import { KNOWLEDGE_SCHEMA_SQL } from "../../knowledge/schema";
|
|
13
|
-
|
|
14
|
-
export type BuildTarget = "cloudflare";
|
|
15
|
-
|
|
16
|
-
export type AgentBuildResult = {
|
|
17
|
-
target: BuildTarget;
|
|
18
|
-
root: string;
|
|
19
|
-
buildDir: string;
|
|
20
|
-
manifestPath: string;
|
|
21
|
-
workerPath: string;
|
|
22
|
-
wranglerPath: string;
|
|
23
|
-
durableObjectSchemaPath: string;
|
|
24
|
-
tursoSchemaPath: string | null;
|
|
25
|
-
bundleSha256: string;
|
|
26
|
-
warnings: string[];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type AgentManifest = {
|
|
30
|
-
schema_version: SharedAgentManifest["schema_version"];
|
|
31
|
-
name: SharedAgentManifest["name"];
|
|
32
|
-
runtime: "edge";
|
|
33
|
-
target: "cloudflare";
|
|
34
|
-
provider: SharedAgentManifest["provider"];
|
|
35
|
-
timeZone: SharedAgentManifest["timeZone"];
|
|
36
|
-
prompt: SharedAgentManifest["prompt"];
|
|
37
|
-
secrets: SharedAgentManifest["secrets"];
|
|
38
|
-
tools: SharedAgentManifest["tools"];
|
|
39
|
-
integrations: SharedAgentManifest["integrations"];
|
|
40
|
-
channels: SharedAgentManifest["channels"];
|
|
41
|
-
transcription: SharedAgentManifest["transcription"];
|
|
42
|
-
knowledge: SharedAgentManifest["knowledge"];
|
|
43
|
-
access: SharedAgentManifest["access"];
|
|
44
|
-
storage: {
|
|
45
|
-
driver: "agentkit";
|
|
46
|
-
database: SharedAgentManifest["storage"]["database"];
|
|
47
|
-
files:
|
|
48
|
-
| {
|
|
49
|
-
driver: "none";
|
|
50
|
-
}
|
|
51
|
-
| {
|
|
52
|
-
driver: "r2";
|
|
53
|
-
binding: "AGENTKIT_FILES";
|
|
54
|
-
bucket: string;
|
|
55
|
-
prefix: string;
|
|
56
|
-
maxUploadBytes: number;
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export type AgentManifestWithBundle = AgentManifest & {
|
|
62
|
-
bundle: {
|
|
63
|
-
content_type: "application/javascript";
|
|
64
|
-
sha256: string;
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const CLOUDFLARE_BUILD_DIR = ".agentkit/build/cloudflare";
|
|
69
|
-
const AGENTKIT_BUILD_DIR = ".agentkit/build";
|
|
70
|
-
const CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-27";
|
|
71
|
-
const CLOUDFLARE_COMPATIBILITY_FLAGS = ["nodejs_compat"] as const;
|
|
72
|
-
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
73
|
-
|
|
74
|
-
export async function buildCloudflareAgentCapsule(
|
|
75
|
-
cwd = process.cwd(),
|
|
76
|
-
): Promise<AgentBuildResult> {
|
|
77
|
-
const capsule = await loadAgentCapsule(cwd);
|
|
78
|
-
validateCloudflareBuild(capsule);
|
|
79
|
-
await assertEdgeCompatibleImports(capsule);
|
|
80
|
-
|
|
81
|
-
const tursoSchema = await loadTursoSchema(capsule);
|
|
82
|
-
const manifest = buildManifest(capsule, tursoSchema);
|
|
83
|
-
const warnings = buildWarnings(capsule);
|
|
84
|
-
const agentkitBuildDir = join(capsule.root, AGENTKIT_BUILD_DIR);
|
|
85
|
-
const cloudflareBuildDir = join(capsule.root, CLOUDFLARE_BUILD_DIR);
|
|
86
|
-
const manifestPath = join(agentkitBuildDir, "agent.manifest.json");
|
|
87
|
-
const workerEntryPath = join(cloudflareBuildDir, "worker.entry.ts");
|
|
88
|
-
const workerPath = join(cloudflareBuildDir, "worker.js");
|
|
89
|
-
const wranglerPath = join(cloudflareBuildDir, "wrangler.jsonc");
|
|
90
|
-
const durableObjectSchemaPath = join(cloudflareBuildDir, "durable-object-schema.sql");
|
|
91
|
-
const tursoSchemaPath = tursoSchema ? join(cloudflareBuildDir, "turso-schema.sql") : null;
|
|
92
|
-
const bundleConfig = await writeEdgeBundleConfig(capsule);
|
|
93
|
-
|
|
94
|
-
await mkdir(agentkitBuildDir, { recursive: true });
|
|
95
|
-
await mkdir(cloudflareBuildDir, { recursive: true });
|
|
96
|
-
try {
|
|
97
|
-
const workerEntrySource = renderCloudflareWorker({
|
|
98
|
-
manifest,
|
|
99
|
-
instructions: capsule.instructions,
|
|
100
|
-
configPath: bundleConfig.configPath,
|
|
101
|
-
});
|
|
102
|
-
await writeFile(workerEntryPath, workerEntrySource);
|
|
103
|
-
await bundleCloudflareWorker(workerEntryPath, workerPath, bundleConfig.shimPath);
|
|
104
|
-
} finally {
|
|
105
|
-
await Promise.all(bundleConfig.cleanupPaths.map((path) => unlink(path).catch(() => undefined)));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const workerSource = await readFile(workerPath, "utf8");
|
|
109
|
-
const bundleSha256 = sha256(workerSource);
|
|
110
|
-
const manifestWithBundle: AgentManifestWithBundle = {
|
|
111
|
-
...manifest,
|
|
112
|
-
bundle: {
|
|
113
|
-
content_type: "application/javascript",
|
|
114
|
-
sha256: bundleSha256,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
await writeFile(manifestPath, `${JSON.stringify(manifestWithBundle, null, 2)}\n`);
|
|
119
|
-
await writeFile(wranglerPath, renderWranglerConfig(capsule.config.name, manifest.storage.files));
|
|
120
|
-
await writeFile(durableObjectSchemaPath, renderDurableObjectSchema());
|
|
121
|
-
if (tursoSchemaPath && tursoSchema) {
|
|
122
|
-
await writeFile(tursoSchemaPath, tursoSchema.source);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
target: "cloudflare",
|
|
127
|
-
root: capsule.root,
|
|
128
|
-
buildDir: cloudflareBuildDir,
|
|
129
|
-
manifestPath,
|
|
130
|
-
workerPath,
|
|
131
|
-
wranglerPath,
|
|
132
|
-
durableObjectSchemaPath,
|
|
133
|
-
tursoSchemaPath,
|
|
134
|
-
bundleSha256,
|
|
135
|
-
warnings,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function validateCloudflareBuild(capsule: LoadedAgentCapsule): void {
|
|
140
|
-
if (capsule.config.runtime !== "edge") {
|
|
141
|
-
throw new AgentKitError(
|
|
142
|
-
"build_not_edge",
|
|
143
|
-
'Cloudflare deploy requires runtime: "edge" in agentkit.config.ts.',
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (capsule.config.storage.driver !== "agentkit") {
|
|
148
|
-
throw new AgentKitError(
|
|
149
|
-
"build_storage_driver",
|
|
150
|
-
'Cloudflare deploy requires storage.driver: "agentkit" so hosted state is managed by AgentKit Cloud.',
|
|
151
|
-
);
|
|
152
|
-
}
|
|
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
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function buildWarnings(capsule: LoadedAgentCapsule): string[] {
|
|
165
|
-
const warnings: string[] = [];
|
|
166
|
-
|
|
167
|
-
if (capsule.config.access.mode === "private") {
|
|
168
|
-
warnings.push(
|
|
169
|
-
"Private access requires setting AGENTKIT_PRIVATE_TOKEN as a Cloudflare secret before /chat accepts requests.",
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (capsule.config.storage.database?.driver === "turso") {
|
|
174
|
-
warnings.push(
|
|
175
|
-
"AgentKit Cloud will provision Turso and inject TURSO_DATABASE_URL/TURSO_AUTH_TOKEN; only --local-wrangler deploys must provide them manually.",
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (capsule.config.storage.files?.driver === "r2") {
|
|
180
|
-
warnings.push("R2 file storage requires the AGENTKIT_FILES binding in wrangler.jsonc to point at an existing bucket.");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return warnings;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function buildManifest(
|
|
187
|
-
capsule: LoadedAgentCapsule,
|
|
188
|
-
tursoSchema: ManifestSchema | null,
|
|
189
|
-
): AgentManifest {
|
|
190
|
-
const shared = buildSharedAgentManifest(capsule, { databaseSchema: tursoSchema });
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
schema_version: shared.schema_version,
|
|
194
|
-
name: shared.name,
|
|
195
|
-
runtime: "edge",
|
|
196
|
-
target: "cloudflare",
|
|
197
|
-
provider: shared.provider,
|
|
198
|
-
timeZone: shared.timeZone,
|
|
199
|
-
prompt: shared.prompt,
|
|
200
|
-
secrets: shared.secrets,
|
|
201
|
-
tools: shared.tools,
|
|
202
|
-
integrations: shared.integrations,
|
|
203
|
-
channels: shared.channels,
|
|
204
|
-
transcription: shared.transcription,
|
|
205
|
-
knowledge: shared.knowledge,
|
|
206
|
-
access: shared.access,
|
|
207
|
-
storage: {
|
|
208
|
-
driver: "agentkit",
|
|
209
|
-
database: shared.storage.database,
|
|
210
|
-
files: buildCloudflareFilesManifest(shared.storage.files),
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function buildCloudflareFilesManifest(files: SharedAgentManifest["storage"]["files"]): AgentManifest["storage"]["files"] {
|
|
216
|
-
if (files.driver === "none") {
|
|
217
|
-
return { driver: "none" };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
driver: "r2",
|
|
222
|
-
binding: "AGENTKIT_FILES",
|
|
223
|
-
bucket: files.bucket,
|
|
224
|
-
prefix: files.prefix,
|
|
225
|
-
maxUploadBytes: files.maxUploadBytes,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async function loadTursoSchema(capsule: LoadedAgentCapsule): Promise<ManifestSchema | null> {
|
|
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
|
-
}
|
|
245
|
-
|
|
246
|
-
if (sources.length === 0) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const source = sources.map((part) => part.trim()).filter(Boolean).join("\n\n") + "\n";
|
|
251
|
-
const digest = sha256(source);
|
|
252
|
-
return { source, sha256: digest };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const EDGE_INCOMPATIBLE_IMPORTS = new Map<string, string>([
|
|
256
|
-
["bun:sqlite", "Use ctx.db inside tools instead of importing SQLite or Bun database APIs."],
|
|
257
|
-
["node:fs", "Cloudflare Workers cannot read local files. Move static data into config, prompts, files, or a tool input."],
|
|
258
|
-
["fs", "Cloudflare Workers cannot read local files. Move static data into config, prompts, files, or a tool input."],
|
|
259
|
-
["node:path", "Cloudflare Workers do not provide Node path APIs in tool bundles. Avoid filesystem path logic in edge tools."],
|
|
260
|
-
["path", "Cloudflare Workers do not provide Node path APIs in tool bundles. Avoid filesystem path logic in edge tools."],
|
|
261
|
-
["node:net", "Cloudflare Workers cannot open raw TCP sockets from tools. Use fetch-compatible APIs."],
|
|
262
|
-
["net", "Cloudflare Workers cannot open raw TCP sockets from tools. Use fetch-compatible APIs."],
|
|
263
|
-
["node:child_process", "Cloudflare Workers cannot spawn local processes. Move this work to a local/container target."],
|
|
264
|
-
["child_process", "Cloudflare Workers cannot spawn local processes. Move this work to a local/container target."],
|
|
265
|
-
["better-sqlite3", "Use ctx.db inside tools instead of importing native database clients."],
|
|
266
|
-
["sqlite3", "Use ctx.db inside tools instead of importing native database clients."],
|
|
267
|
-
["libsql", "Use ctx.db inside tools so AgentKit can route local SQLite and hosted Turso consistently."],
|
|
268
|
-
["@libsql/client", "Use ctx.db inside tools so AgentKit can route local SQLite and hosted Turso consistently."],
|
|
269
|
-
]);
|
|
270
|
-
|
|
271
|
-
async function assertEdgeCompatibleImports(capsule: LoadedAgentCapsule): Promise<void> {
|
|
272
|
-
const files = await listSourceFiles(capsule.root);
|
|
273
|
-
|
|
274
|
-
for (const file of files) {
|
|
275
|
-
const source = await readFile(file, "utf8");
|
|
276
|
-
|
|
277
|
-
for (const specifier of findImportSpecifiers(source)) {
|
|
278
|
-
const guidance = edgeImportGuidance(specifier);
|
|
279
|
-
|
|
280
|
-
if (!guidance) {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
throw new AgentKitError(
|
|
285
|
-
"edge_incompatible_import",
|
|
286
|
-
[
|
|
287
|
-
`Cloudflare build cannot include import "${specifier}" from ${relative(capsule.root, file)}.`,
|
|
288
|
-
`Target runtime: cloudflare.`,
|
|
289
|
-
guidance,
|
|
290
|
-
].join(" "),
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function listSourceFiles(root: string): Promise<string[]> {
|
|
297
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
298
|
-
const files: string[] = [];
|
|
299
|
-
|
|
300
|
-
for (const entry of entries) {
|
|
301
|
-
if (entry.name === "node_modules" || entry.name === ".agentkit" || entry.name === ".git") {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const path = join(root, entry.name);
|
|
306
|
-
|
|
307
|
-
if (entry.isDirectory()) {
|
|
308
|
-
files.push(...(await listSourceFiles(path)));
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (entry.isFile() && [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extname(entry.name))) {
|
|
313
|
-
files.push(path);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return files.sort();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function findImportSpecifiers(source: string): string[] {
|
|
321
|
-
const specifiers: string[] = [];
|
|
322
|
-
const patterns = [
|
|
323
|
-
/\bimport\s+(?!type\b)(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g,
|
|
324
|
-
/\bexport\s+(?!type\b)[^"'()]*?\s+from\s+["']([^"']+)["']/g,
|
|
325
|
-
/\brequire\(\s*["']([^"']+)["']\s*\)/g,
|
|
326
|
-
/\bimport\(\s*["']([^"']+)["']\s*\)/g,
|
|
327
|
-
];
|
|
328
|
-
|
|
329
|
-
for (const pattern of patterns) {
|
|
330
|
-
for (const match of source.matchAll(pattern)) {
|
|
331
|
-
if (match[1]) {
|
|
332
|
-
specifiers.push(match[1]);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return specifiers;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function edgeImportGuidance(specifier: string): string | null {
|
|
341
|
-
if (EDGE_INCOMPATIBLE_IMPORTS.has(specifier)) {
|
|
342
|
-
return EDGE_INCOMPATIBLE_IMPORTS.get(specifier) ?? null;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
for (const [blocked, guidance] of EDGE_INCOMPATIBLE_IMPORTS) {
|
|
346
|
-
if (specifier === blocked || specifier.startsWith(`${blocked}/`)) {
|
|
347
|
-
return guidance;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async function writeEdgeBundleConfig(
|
|
355
|
-
capsule: LoadedAgentCapsule,
|
|
356
|
-
): Promise<{ configPath: string; shimPath: string; cleanupPaths: string[] }> {
|
|
357
|
-
const source = await readFile(capsule.configPath, "utf8");
|
|
358
|
-
const suffix = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
359
|
-
const shimPath = join(dirname(capsule.configPath), `.agentkit.edge-shim-${suffix}.ts`);
|
|
360
|
-
const bundleConfigPath = join(dirname(capsule.configPath), `.agentkit.config.edge-bundle-${suffix}.ts`);
|
|
361
|
-
const localAgentKitPath = resolve(moduleDir, "../../../index.ts");
|
|
362
|
-
const shimImport = JSON.stringify(`./${relative(dirname(capsule.configPath), shimPath)}`);
|
|
363
|
-
const patchedSource = source
|
|
364
|
-
.replace(
|
|
365
|
-
/from\s+(["'])file:\/\/([^"']+)\1/g,
|
|
366
|
-
(_match, quote: string, path: string) => `from ${quote}${decodeURIComponent(path)}${quote}`,
|
|
367
|
-
)
|
|
368
|
-
.replace(/from\s+["']@andreprado\/agentkit["']/g, `from ${shimImport}`)
|
|
369
|
-
.replace(
|
|
370
|
-
new RegExp(`from\\s+["']${escapeRegExp(localAgentKitPath)}["']`, "g"),
|
|
371
|
-
`from ${shimImport}`,
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
await writeFile(shimPath, renderEdgeShim());
|
|
375
|
-
await writeFile(bundleConfigPath, patchedSource);
|
|
376
|
-
return { configPath: bundleConfigPath, shimPath, cleanupPaths: [bundleConfigPath, shimPath] };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function renderEdgeShim(): string {
|
|
380
|
-
return `export function defineAgent(config) {
|
|
381
|
-
return config;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function defineTool(tool) {
|
|
385
|
-
return tool;
|
|
386
|
-
}
|
|
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
|
-
|
|
408
|
-
export function websiteChannel(input) {
|
|
409
|
-
return {
|
|
410
|
-
type: "website",
|
|
411
|
-
provider: "agentkit",
|
|
412
|
-
name: input.name,
|
|
413
|
-
access: input.access ?? { mode: "token" },
|
|
414
|
-
secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"], "website channel"),
|
|
415
|
-
...(input.limits ? { limits: input.limits } : {}),
|
|
416
|
-
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "website channel") } : {}),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export function telegramChannel(input) {
|
|
421
|
-
return {
|
|
422
|
-
type: "telegram",
|
|
423
|
-
provider: "telegram",
|
|
424
|
-
name: input.name,
|
|
425
|
-
secrets: normalizeChannelSecrets(input.secrets ?? ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"], "telegram channel"),
|
|
426
|
-
allowedUpdates: input.allowedUpdates ?? ["message"],
|
|
427
|
-
...(input.limits ? { limits: input.limits } : {}),
|
|
428
|
-
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "telegram channel") } : {}),
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export function whatsappChannel(input) {
|
|
433
|
-
return {
|
|
434
|
-
type: "whatsapp",
|
|
435
|
-
provider: input.provider,
|
|
436
|
-
name: input.name,
|
|
437
|
-
secrets: normalizeChannelSecrets(input.secrets ?? defaultWhatsappChannelSecrets(input.provider), \`\${input.provider} whatsapp channel\`),
|
|
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") } : {}),
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
export function webhookChannel(input) {
|
|
466
|
-
return {
|
|
467
|
-
type: "webhook",
|
|
468
|
-
provider: "generic",
|
|
469
|
-
direction: "inbound",
|
|
470
|
-
name: input.name,
|
|
471
|
-
secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBHOOK_SECRET"], "generic webhook channel"),
|
|
472
|
-
...(input.limits ? { limits: input.limits } : {}),
|
|
473
|
-
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "generic webhook channel") } : {}),
|
|
474
|
-
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "generic webhook channel") } : {}),
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
export function webhookOutputChannel(input) {
|
|
479
|
-
const auth = normalizeGenericWebhookOutputAuth(input.auth ?? { type: "none" }, "generic webhook output channel");
|
|
480
|
-
const requiredSecrets = [
|
|
481
|
-
input.urlSecret,
|
|
482
|
-
...genericWebhookOutputAuthSecrets(auth),
|
|
483
|
-
...(input.secrets ?? []),
|
|
484
|
-
];
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
type: "webhook",
|
|
488
|
-
provider: "generic",
|
|
489
|
-
direction: "outbound",
|
|
490
|
-
name: input.name,
|
|
491
|
-
urlSecret: normalizeSecretName(input.urlSecret, "generic webhook output channel urlSecret"),
|
|
492
|
-
auth,
|
|
493
|
-
secrets: normalizeChannelSecrets(requiredSecrets, "generic webhook output channel"),
|
|
494
|
-
...(input.limits ? { limits: input.limits } : {}),
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function defaultWhatsappChannelSecrets(provider) {
|
|
499
|
-
if (provider === "zapster") {
|
|
500
|
-
return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"];
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (provider === "uazapi") {
|
|
504
|
-
return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"];
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (provider === "evolution") {
|
|
508
|
-
return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function normalizeChannelSecrets(secrets, context) {
|
|
515
|
-
const uniqueSecrets = new Set();
|
|
516
|
-
|
|
517
|
-
for (const secret of secrets) {
|
|
518
|
-
uniqueSecrets.add(normalizeSecretName(secret, \`\${context} secrets\`));
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
return Array.from(uniqueSecrets).sort();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function normalizeSecretName(secret, context) {
|
|
525
|
-
if (!/^[A-Z_][A-Z0-9_]*$/.test(secret)) {
|
|
526
|
-
if (context.endsWith("secrets")) {
|
|
527
|
-
throw new Error(\`\${context} must be environment variable names, not secret values.\`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
throw new Error(\`\${context} must be an environment variable name, not a secret value.\`);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return secret;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function normalizeChannelReplyRoute(input, context) {
|
|
537
|
-
const channel = input.channel.trim();
|
|
538
|
-
|
|
539
|
-
if (!/^[a-z][a-z0-9-]*$/.test(channel)) {
|
|
540
|
-
throw new Error(\`\${context} replyTo.channel must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return {
|
|
544
|
-
channel,
|
|
545
|
-
...(input.recipientFrom ? { recipientFrom: normalizeJsonPath(input.recipientFrom, \`\${context} replyTo.recipientFrom\`) } : {}),
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function normalizeJsonPath(value, context) {
|
|
550
|
-
const path = value.trim().replace(/^payload\\./, "");
|
|
551
|
-
|
|
552
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
|
|
553
|
-
throw new Error(\`\${context} must be a dotted JSON field path such as "phone" or "customer.phone".\`);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
return path;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function normalizeGenericWebhookOutputAuth(auth, context) {
|
|
560
|
-
if (auth.type === "none") {
|
|
561
|
-
return { type: "none" };
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (auth.type === "bearer") {
|
|
565
|
-
return {
|
|
566
|
-
type: "bearer",
|
|
567
|
-
tokenSecret: normalizeSecretName(auth.tokenSecret, \`\${context} auth.tokenSecret\`),
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (auth.type === "header") {
|
|
572
|
-
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
|
|
573
|
-
throw new Error(\`\${context} auth.headerName must be an HTTP header name.\`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return {
|
|
577
|
-
type: "header",
|
|
578
|
-
headerName: auth.headerName,
|
|
579
|
-
valueSecret: normalizeSecretName(auth.valueSecret, \`\${context} auth.valueSecret\`),
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (auth.type === "hmac") {
|
|
584
|
-
if (auth.headerName !== undefined && !/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
|
|
585
|
-
throw new Error(\`\${context} auth.headerName must be an HTTP header name.\`);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
type: "hmac",
|
|
590
|
-
secret: normalizeSecretName(auth.secret, \`\${context} auth.secret\`),
|
|
591
|
-
...(auth.headerName ? { headerName: auth.headerName } : {}),
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
throw new Error(\`\${context} auth.type must be "none", "bearer", "header", or "hmac".\`);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function genericWebhookOutputAuthSecrets(auth) {
|
|
599
|
-
if (auth.type === "bearer") {
|
|
600
|
-
return [auth.tokenSecret];
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (auth.type === "header") {
|
|
604
|
-
return [auth.valueSecret];
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (auth.type === "hmac") {
|
|
608
|
-
return [auth.secret];
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return [];
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function normalizeIntegrationName(value, context) {
|
|
615
|
-
const name = value.trim();
|
|
616
|
-
|
|
617
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
618
|
-
throw new Error(\`\${context} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return name;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function normalizeToolkitList(values, context) {
|
|
625
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
626
|
-
throw new Error(\`\${context} must include at least one toolkit slug.\`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const toolkits = new Set();
|
|
630
|
-
|
|
631
|
-
for (const value of values) {
|
|
632
|
-
if (typeof value !== "string") {
|
|
633
|
-
throw new Error(\`\${context} entries must be toolkit slug strings.\`);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const toolkit = value.trim().toLowerCase();
|
|
637
|
-
|
|
638
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(toolkit)) {
|
|
639
|
-
throw new Error(\`\${context} entries must start with a letter and contain only letters, numbers, underscores, and hyphens.\`);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
toolkits.add(toolkit);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return Array.from(toolkits).sort();
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function normalizeComposioToolMap(values, toolkits) {
|
|
649
|
-
const toolkitSet = new Set(toolkits);
|
|
650
|
-
const normalized = {};
|
|
651
|
-
|
|
652
|
-
for (const [rawToolkit, rawTools] of Object.entries(values)) {
|
|
653
|
-
const toolkit = rawToolkit.trim().toLowerCase();
|
|
654
|
-
|
|
655
|
-
if (!toolkitSet.has(toolkit)) {
|
|
656
|
-
throw new Error(\`composioManaged.tools.\${rawToolkit} must reference a toolkit listed in composioManaged.toolkits.\`);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
660
|
-
throw new Error(\`composioManaged.tools.\${rawToolkit} must include at least one Composio action slug.\`);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const tools = new Set();
|
|
664
|
-
|
|
665
|
-
for (const rawTool of rawTools) {
|
|
666
|
-
if (typeof rawTool !== "string") {
|
|
667
|
-
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be Composio action slug strings.\`);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const tool = rawTool.trim().toUpperCase();
|
|
671
|
-
|
|
672
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(tool)) {
|
|
673
|
-
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be uppercase Composio action slugs.\`);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
tools.add(tool);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
normalized[toolkit] = Array.from(tools).sort();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function normalizeIntegrationTags(tags) {
|
|
686
|
-
const normalized = {};
|
|
687
|
-
|
|
688
|
-
if (tags.enable !== undefined) {
|
|
689
|
-
normalized.enable = normalizeTagList(tags.enable, "composioManaged.tags.enable");
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
if (tags.disable !== undefined) {
|
|
693
|
-
normalized.disable = normalizeTagList(tags.disable, "composioManaged.tags.disable");
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return normalized;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function normalizeTagList(values, context) {
|
|
700
|
-
if (!Array.isArray(values)) {
|
|
701
|
-
throw new Error(\`\${context} must be an array of tag strings.\`);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
return Array.from(new Set(values.map((value) => {
|
|
705
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
706
|
-
throw new Error(\`\${context} entries must be non-empty strings.\`);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
return value.trim();
|
|
710
|
-
}))).sort();
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function normalizeChannelBuffer(input, context) {
|
|
714
|
-
if (input.mode === "off") {
|
|
715
|
-
return { mode: "off" };
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (input.mode !== undefined && input.mode !== "debounce") {
|
|
719
|
-
throw new Error(\`\${context} buffer.mode must be "off" or "debounce".\`);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const buffer = {
|
|
723
|
-
mode: "debounce",
|
|
724
|
-
quietWindowMs: input.quietWindowMs ?? 2500,
|
|
725
|
-
maxWaitMs: input.maxWaitMs ?? 12000,
|
|
726
|
-
maxMessages: input.maxMessages ?? 20,
|
|
727
|
-
maxChars: input.maxChars ?? 8000,
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
for (const field of ["quietWindowMs", "maxWaitMs", "maxMessages", "maxChars"]) {
|
|
731
|
-
if (!Number.isInteger(buffer[field]) || buffer[field] <= 0) {
|
|
732
|
-
throw new Error(\`\${context} buffer.\${field} must be a positive integer.\`);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
if (buffer.maxWaitMs < buffer.quietWindowMs) {
|
|
737
|
-
throw new Error(\`\${context} buffer.maxWaitMs must be greater than or equal to buffer.quietWindowMs.\`);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
return buffer;
|
|
741
|
-
}
|
|
742
|
-
`;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function escapeRegExp(value: string): string {
|
|
746
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
async function bundleCloudflareWorker(entryPath: string, workerPath: string, edgeShimPath: string): Promise<void> {
|
|
750
|
-
const localAgentKitPath = resolve(moduleDir, "../../../index.ts");
|
|
751
|
-
const localAgentKitFileUrl = pathToFileURL(localAgentKitPath).href;
|
|
752
|
-
const localAgentKitImportPattern = new RegExp(
|
|
753
|
-
`^(?:${escapeRegExp(localAgentKitPath)}|${escapeRegExp(localAgentKitFileUrl)})$`,
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
try {
|
|
757
|
-
const result = await esbuild({
|
|
758
|
-
entryPoints: [entryPath],
|
|
759
|
-
bundle: true,
|
|
760
|
-
platform: "browser",
|
|
761
|
-
format: "esm",
|
|
762
|
-
target: "es2022",
|
|
763
|
-
external: ["cloudflare:workers"],
|
|
764
|
-
minify: true,
|
|
765
|
-
write: false,
|
|
766
|
-
logLevel: "silent",
|
|
767
|
-
plugins: [
|
|
768
|
-
{
|
|
769
|
-
name: "agentkit-edge-shim",
|
|
770
|
-
setup(build) {
|
|
771
|
-
build.onResolve({ filter: /^@andreprado\/agentkit$/ }, () => ({
|
|
772
|
-
path: edgeShimPath,
|
|
773
|
-
}));
|
|
774
|
-
build.onResolve({ filter: localAgentKitImportPattern }, () => ({
|
|
775
|
-
path: edgeShimPath,
|
|
776
|
-
}));
|
|
777
|
-
},
|
|
778
|
-
} satisfies Plugin,
|
|
779
|
-
],
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
const output = result.outputFiles?.[0];
|
|
783
|
-
|
|
784
|
-
if (!output) {
|
|
785
|
-
throw new Error("No bundle was emitted.");
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const processShim = "var process = globalThis.process ?? (globalThis.process = { env: {}, versions: {} });\n";
|
|
789
|
-
await writeFile(workerPath, processShim + output.text);
|
|
790
|
-
} catch (error) {
|
|
791
|
-
throw new AgentKitError(
|
|
792
|
-
"edge_bundle_failed",
|
|
793
|
-
`Could not bundle Agent Capsule for Cloudflare Workers. ${formatBuildError(error)}`,
|
|
794
|
-
{ cause: error },
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
function formatBuildError(error: unknown): string {
|
|
800
|
-
if (error && typeof error === "object" && "errors" in error && Array.isArray(error.errors)) {
|
|
801
|
-
const messages = error.errors.map((log) => String(log?.text ?? log?.message ?? "")).filter(Boolean);
|
|
802
|
-
if (messages.length > 0) {
|
|
803
|
-
return messages.join("\n");
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return error instanceof Error ? error.message : String(error);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function renderCloudflareWorker(input: { manifest: AgentManifest; instructions: string; configPath: string }): string {
|
|
811
|
-
const manifestJson = JSON.stringify(input.manifest, null, 2);
|
|
812
|
-
const instructionsJson = JSON.stringify(input.instructions);
|
|
813
|
-
const configImportPath = JSON.stringify(input.configPath);
|
|
814
|
-
const providerImportPath = JSON.stringify(resolve(moduleDir, "../../../providers/index.ts"));
|
|
815
|
-
const composioIntegrationImportPath = JSON.stringify(resolve(moduleDir, "../../integrations/composio.ts"));
|
|
816
|
-
const promptContextImportPath = JSON.stringify(resolve(moduleDir, "../../prompt-context.ts"));
|
|
817
|
-
|
|
818
|
-
return `import { DurableObject } from "cloudflare:workers";
|
|
819
|
-
import agentConfig from ${configImportPath};
|
|
820
|
-
import { getProviderAdapter } from ${providerImportPath};
|
|
821
|
-
import { MANAGED_COMPOSIO_USER_ID_BINDING, createManagedComposioTools } from ${composioIntegrationImportPath};
|
|
822
|
-
import { appendRuntimePromptContext, buildRuntimeClockContext } from ${promptContextImportPath};
|
|
823
|
-
|
|
824
|
-
const manifest = ${manifestJson};
|
|
825
|
-
const instructions = ${instructionsJson};
|
|
826
|
-
const hostedKnowledgeTool = manifest.knowledge.enabled ? createHostedKnowledgeSearchTool() : null;
|
|
827
|
-
const DEFAULT_TOOL_TIMEOUT_MS = 30000;
|
|
828
|
-
|
|
829
|
-
export class AgentKitConversationStore extends DurableObject {
|
|
830
|
-
constructor(ctx, env) {
|
|
831
|
-
super(ctx, env);
|
|
832
|
-
this.sql = ctx.storage.sql;
|
|
833
|
-
this.sql.exec(\`
|
|
834
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
835
|
-
id TEXT PRIMARY KEY,
|
|
836
|
-
agent_name TEXT NOT NULL,
|
|
837
|
-
title TEXT,
|
|
838
|
-
created_at TEXT NOT NULL,
|
|
839
|
-
updated_at TEXT NOT NULL
|
|
840
|
-
);
|
|
841
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
842
|
-
id TEXT PRIMARY KEY,
|
|
843
|
-
conversation_id TEXT NOT NULL,
|
|
844
|
-
role TEXT NOT NULL,
|
|
845
|
-
content TEXT NOT NULL,
|
|
846
|
-
created_at TEXT NOT NULL
|
|
847
|
-
);
|
|
848
|
-
CREATE TABLE IF NOT EXISTS runs (
|
|
849
|
-
id TEXT PRIMARY KEY,
|
|
850
|
-
conversation_id TEXT NOT NULL,
|
|
851
|
-
provider TEXT NOT NULL,
|
|
852
|
-
model TEXT NOT NULL,
|
|
853
|
-
status TEXT NOT NULL,
|
|
854
|
-
error_code TEXT,
|
|
855
|
-
error_message TEXT,
|
|
856
|
-
input_tokens INTEGER,
|
|
857
|
-
output_tokens INTEGER,
|
|
858
|
-
total_tokens INTEGER,
|
|
859
|
-
cost_json TEXT,
|
|
860
|
-
created_at TEXT NOT NULL,
|
|
861
|
-
completed_at TEXT
|
|
862
|
-
);
|
|
863
|
-
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
864
|
-
id TEXT PRIMARY KEY,
|
|
865
|
-
run_id TEXT NOT NULL,
|
|
866
|
-
conversation_id TEXT NOT NULL,
|
|
867
|
-
tool_name TEXT NOT NULL,
|
|
868
|
-
visibility TEXT NOT NULL,
|
|
869
|
-
status TEXT NOT NULL,
|
|
870
|
-
input_json TEXT NOT NULL,
|
|
871
|
-
output_json TEXT,
|
|
872
|
-
rendered_json TEXT,
|
|
873
|
-
error_message TEXT,
|
|
874
|
-
created_at TEXT NOT NULL,
|
|
875
|
-
completed_at TEXT
|
|
876
|
-
);
|
|
877
|
-
\`);
|
|
878
|
-
try {
|
|
879
|
-
this.sql.exec("ALTER TABLE tool_calls ADD COLUMN rendered_json TEXT");
|
|
880
|
-
} catch {
|
|
881
|
-
// Existing Durable Object tables either already have this column or do not need migration.
|
|
882
|
-
}
|
|
883
|
-
for (const statement of [
|
|
884
|
-
"ALTER TABLE runs ADD COLUMN input_tokens INTEGER",
|
|
885
|
-
"ALTER TABLE runs ADD COLUMN output_tokens INTEGER",
|
|
886
|
-
"ALTER TABLE runs ADD COLUMN total_tokens INTEGER",
|
|
887
|
-
"ALTER TABLE runs ADD COLUMN cost_json TEXT",
|
|
888
|
-
]) {
|
|
889
|
-
try {
|
|
890
|
-
this.sql.exec(statement);
|
|
891
|
-
} catch {
|
|
892
|
-
// Existing Durable Object tables either already have this column or do not need migration.
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
async fetch(request) {
|
|
898
|
-
const url = new URL(request.url);
|
|
899
|
-
|
|
900
|
-
if (request.method === "POST" && url.pathname === "/messages") {
|
|
901
|
-
return this.persistMessages(await request.json());
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
if (request.method === "GET" && url.pathname === "/messages") {
|
|
905
|
-
return this.getMessages(url.searchParams.get("conversationId"));
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
if (request.method === "GET" && url.pathname === "/conversations") {
|
|
909
|
-
return this.listConversations();
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const conversationTraceMatch = url.pathname.match(/^\\/conversations\\/([^/]+)\\/trace$/);
|
|
913
|
-
|
|
914
|
-
if (request.method === "GET" && conversationTraceMatch) {
|
|
915
|
-
return this.getConversationTrace(decodeURIComponent(conversationTraceMatch[1] || ""));
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const conversationMatch = url.pathname.match(/^\\/conversations\\/([^/]+)$/);
|
|
919
|
-
|
|
920
|
-
if (request.method === "GET" && conversationMatch) {
|
|
921
|
-
return this.getConversation(decodeURIComponent(conversationMatch[1] || ""));
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return Response.json({ error: { code: "not_found", message: "Unknown Durable Object route." } }, { status: 404 });
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
getMessages(conversationId) {
|
|
928
|
-
if (!conversationId) {
|
|
929
|
-
return Response.json({ messages: [] });
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
const rows = this.sql.exec(
|
|
933
|
-
"SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
934
|
-
String(conversationId),
|
|
935
|
-
).toArray();
|
|
936
|
-
|
|
937
|
-
return Response.json({
|
|
938
|
-
messages: rows
|
|
939
|
-
.filter((row) => row.role === "user" || row.role === "assistant")
|
|
940
|
-
.map((row) => ({
|
|
941
|
-
role: String(row.role),
|
|
942
|
-
content: String(row.content),
|
|
943
|
-
})),
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
listConversations() {
|
|
948
|
-
const rows = this.sql.exec(\`
|
|
949
|
-
SELECT conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at, COUNT(messages.id) AS message_count
|
|
950
|
-
FROM conversations
|
|
951
|
-
LEFT JOIN messages ON messages.conversation_id = conversations.id
|
|
952
|
-
GROUP BY conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at
|
|
953
|
-
ORDER BY conversations.updated_at DESC
|
|
954
|
-
\`).toArray();
|
|
955
|
-
|
|
956
|
-
return Response.json({
|
|
957
|
-
conversations: rows.map((row) => ({
|
|
958
|
-
id: String(row.id),
|
|
959
|
-
agentName: String(row.agent_name),
|
|
960
|
-
title: row.title === null || row.title === undefined ? null : String(row.title),
|
|
961
|
-
createdAt: String(row.created_at),
|
|
962
|
-
updatedAt: String(row.updated_at),
|
|
963
|
-
messageCount: Number(row.message_count || 0),
|
|
964
|
-
})),
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
getConversation(conversationId) {
|
|
969
|
-
const conversation = this.readConversation(conversationId);
|
|
970
|
-
|
|
971
|
-
if (!conversation) {
|
|
972
|
-
return Response.json(
|
|
973
|
-
{ error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
|
|
974
|
-
{ status: 404 },
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
return Response.json(conversation);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
getConversationTrace(conversationId) {
|
|
982
|
-
const conversation = this.readConversation(conversationId);
|
|
983
|
-
|
|
984
|
-
if (!conversation) {
|
|
985
|
-
return Response.json(
|
|
986
|
-
{ error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
|
|
987
|
-
{ status: 404 },
|
|
988
|
-
);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
const runs = this.sql.exec(
|
|
992
|
-
"SELECT id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
993
|
-
conversationId,
|
|
994
|
-
).toArray();
|
|
995
|
-
|
|
996
|
-
return Response.json({
|
|
997
|
-
...conversation,
|
|
998
|
-
runs: runs.map((run) => ({
|
|
999
|
-
id: String(run.id),
|
|
1000
|
-
conversationId: String(run.conversation_id),
|
|
1001
|
-
provider: String(run.provider),
|
|
1002
|
-
model: String(run.model),
|
|
1003
|
-
status: String(run.status),
|
|
1004
|
-
errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
|
|
1005
|
-
errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
|
|
1006
|
-
usage: readStoredUsage(run),
|
|
1007
|
-
startedAt: String(run.created_at),
|
|
1008
|
-
completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
|
|
1009
|
-
toolCalls: this.readToolCalls(String(run.id)),
|
|
1010
|
-
})),
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
readConversation(conversationId) {
|
|
1015
|
-
if (!conversationId) {
|
|
1016
|
-
return null;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
const row = this.sql.exec(
|
|
1020
|
-
"SELECT id, agent_name, title, created_at, updated_at FROM conversations WHERE id = ?",
|
|
1021
|
-
conversationId,
|
|
1022
|
-
).one();
|
|
1023
|
-
|
|
1024
|
-
if (!row) {
|
|
1025
|
-
return null;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
const messages = this.sql.exec(
|
|
1029
|
-
"SELECT id, conversation_id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
1030
|
-
conversationId,
|
|
1031
|
-
).toArray();
|
|
1032
|
-
|
|
1033
|
-
return {
|
|
1034
|
-
id: String(row.id),
|
|
1035
|
-
agentName: String(row.agent_name),
|
|
1036
|
-
title: row.title === null || row.title === undefined ? null : String(row.title),
|
|
1037
|
-
createdAt: String(row.created_at),
|
|
1038
|
-
updatedAt: String(row.updated_at),
|
|
1039
|
-
messages: messages.map((message) => ({
|
|
1040
|
-
id: String(message.id),
|
|
1041
|
-
conversationId: String(message.conversation_id),
|
|
1042
|
-
role: String(message.role),
|
|
1043
|
-
content: String(message.content),
|
|
1044
|
-
createdAt: String(message.created_at),
|
|
1045
|
-
})),
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
readToolCalls(runId) {
|
|
1050
|
-
const rows = this.sql.exec(
|
|
1051
|
-
"SELECT id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, rendered_json, error_message, created_at, completed_at FROM tool_calls WHERE run_id = ? ORDER BY rowid ASC",
|
|
1052
|
-
runId,
|
|
1053
|
-
).toArray();
|
|
1054
|
-
|
|
1055
|
-
return rows.map((row) => ({
|
|
1056
|
-
id: String(row.id),
|
|
1057
|
-
runId: String(row.run_id),
|
|
1058
|
-
conversationId: String(row.conversation_id),
|
|
1059
|
-
name: String(row.tool_name),
|
|
1060
|
-
visibility: String(row.visibility),
|
|
1061
|
-
status: String(row.status),
|
|
1062
|
-
input: parseStoredJson(row.input_json, {}),
|
|
1063
|
-
output: row.output_json === null || row.output_json === undefined ? undefined : parseStoredJson(row.output_json, null),
|
|
1064
|
-
rendered: row.rendered_json === null || row.rendered_json === undefined ? undefined : parseStoredJson(row.rendered_json, null),
|
|
1065
|
-
errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
|
|
1066
|
-
createdAt: String(row.created_at),
|
|
1067
|
-
completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
|
|
1068
|
-
}));
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
persistMessages(input) {
|
|
1072
|
-
const now = new Date().toISOString();
|
|
1073
|
-
const conversationId = String(input.conversationId || crypto.randomUUID());
|
|
1074
|
-
const runId = crypto.randomUUID();
|
|
1075
|
-
const userMessageId = crypto.randomUUID();
|
|
1076
|
-
const assistantMessageId = crypto.randomUUID();
|
|
1077
|
-
const title = titleFromMessage(String(input.userMessage || ""));
|
|
1078
|
-
const usage = normalizeRuntimeUsage(input.usage);
|
|
1079
|
-
|
|
1080
|
-
this.sql.exec(
|
|
1081
|
-
"INSERT OR IGNORE INTO conversations (id, agent_name, title, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
1082
|
-
conversationId,
|
|
1083
|
-
manifest.name,
|
|
1084
|
-
title,
|
|
1085
|
-
now,
|
|
1086
|
-
now,
|
|
1087
|
-
);
|
|
1088
|
-
this.sql.exec("UPDATE conversations SET updated_at = ? WHERE id = ?", now, conversationId);
|
|
1089
|
-
this.sql.exec(
|
|
1090
|
-
"INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
1091
|
-
userMessageId,
|
|
1092
|
-
conversationId,
|
|
1093
|
-
"user",
|
|
1094
|
-
String(input.userMessage || ""),
|
|
1095
|
-
now,
|
|
1096
|
-
);
|
|
1097
|
-
this.sql.exec(
|
|
1098
|
-
"INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1099
|
-
runId,
|
|
1100
|
-
conversationId,
|
|
1101
|
-
manifest.provider.name,
|
|
1102
|
-
manifest.provider.model,
|
|
1103
|
-
String(input.status || "completed"),
|
|
1104
|
-
input.errorCode ? String(input.errorCode) : null,
|
|
1105
|
-
input.errorMessage ? String(input.errorMessage) : null,
|
|
1106
|
-
usage.inputTokens ?? null,
|
|
1107
|
-
usage.outputTokens ?? null,
|
|
1108
|
-
usage.totalTokens ?? null,
|
|
1109
|
-
usage.cost && Object.keys(usage.cost).length > 0 ? JSON.stringify(usage.cost) : null,
|
|
1110
|
-
now,
|
|
1111
|
-
now,
|
|
1112
|
-
);
|
|
1113
|
-
|
|
1114
|
-
if (input.assistantMessage !== undefined) {
|
|
1115
|
-
this.sql.exec(
|
|
1116
|
-
"INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
1117
|
-
assistantMessageId,
|
|
1118
|
-
conversationId,
|
|
1119
|
-
"assistant",
|
|
1120
|
-
String(input.assistantMessage || ""),
|
|
1121
|
-
now,
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
for (const toolCall of input.toolCalls || []) {
|
|
1126
|
-
this.sql.exec(
|
|
1127
|
-
"INSERT INTO tool_calls (id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, rendered_json, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1128
|
-
String(toolCall.id || crypto.randomUUID()),
|
|
1129
|
-
runId,
|
|
1130
|
-
conversationId,
|
|
1131
|
-
String(toolCall.name || ""),
|
|
1132
|
-
String(toolCall.visibility || "user"),
|
|
1133
|
-
String(toolCall.status || "completed"),
|
|
1134
|
-
JSON.stringify(toolCall.input ?? {}),
|
|
1135
|
-
toolCall.output === undefined ? null : JSON.stringify(toolCall.output),
|
|
1136
|
-
toolCall.rendered === undefined ? null : JSON.stringify(toolCall.rendered),
|
|
1137
|
-
toolCall.errorMessage ? String(toolCall.errorMessage) : null,
|
|
1138
|
-
now,
|
|
1139
|
-
now,
|
|
1140
|
-
);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
return Response.json({
|
|
1144
|
-
conversationId,
|
|
1145
|
-
runId,
|
|
1146
|
-
userMessageId,
|
|
1147
|
-
assistantMessageId: input.assistantMessage === undefined ? null : assistantMessageId,
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
export default {
|
|
1153
|
-
async fetch(request, env, ctx) {
|
|
1154
|
-
const url = new URL(request.url);
|
|
1155
|
-
|
|
1156
|
-
if (request.method === "GET" && url.pathname === "/_agentkit") {
|
|
1157
|
-
const auth = authorizeAdmin(request, env);
|
|
1158
|
-
|
|
1159
|
-
if (auth) {
|
|
1160
|
-
return auth;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
const hostedTools = createHostedTools(env);
|
|
1164
|
-
|
|
1165
|
-
return Response.json({
|
|
1166
|
-
agent: manifest.name,
|
|
1167
|
-
runtime: manifest.runtime,
|
|
1168
|
-
target: manifest.target,
|
|
1169
|
-
provider: manifest.provider,
|
|
1170
|
-
prompt: manifest.prompt,
|
|
1171
|
-
tools: hostedTools.map((tool) => tool.name),
|
|
1172
|
-
integrations: hostedIntegrations(env),
|
|
1173
|
-
channels: manifest.channels.map((channel) => ({
|
|
1174
|
-
name: channel.name,
|
|
1175
|
-
type: channel.type,
|
|
1176
|
-
provider: channel.provider,
|
|
1177
|
-
secrets: channel.secrets,
|
|
1178
|
-
})),
|
|
1179
|
-
knowledge: manifest.knowledge,
|
|
1180
|
-
access: manifest.access,
|
|
1181
|
-
storage: manifest.storage,
|
|
1182
|
-
database: inspectHostedDatabase(),
|
|
1183
|
-
secrets: Object.fromEntries(manifest.secrets.map((name) => [name, env[name] ? "set" : "missing"])),
|
|
1184
|
-
managedSecrets: hostedManagedSecrets(),
|
|
1185
|
-
userSecrets: manifest.secrets.filter((name) => !hostedManagedSecrets().includes(name)),
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
if (request.method === "GET" && url.pathname === "/") {
|
|
1190
|
-
const auth = await authorizeDeployAccess(request, env, {}, {
|
|
1191
|
-
notConfiguredCode: "cloud_access_not_configured",
|
|
1192
|
-
notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
if (auth) {
|
|
1196
|
-
return auth;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
return Response.json({
|
|
1200
|
-
agent: manifest.name,
|
|
1201
|
-
status: "ready",
|
|
1202
|
-
inspect: "/_agentkit",
|
|
1203
|
-
chat: "/chat",
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
if (request.method === "GET" && url.pathname === "/health") {
|
|
1208
|
-
const auth = await authorizeDeployAccess(request, env, {}, {
|
|
1209
|
-
notConfiguredCode: "cloud_access_not_configured",
|
|
1210
|
-
notConfiguredMessage: "Hosted AgentKit Cloud routes require a deploy access token gate.",
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
if (auth) {
|
|
1214
|
-
return auth;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return Response.json({ ok: true });
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
if (isHostedChannelWebhookPath(url.pathname)) {
|
|
1221
|
-
return forwardHostedChannelWebhook(request, env, url);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
if (request.method === "POST" && (url.pathname === "/chat" || url.pathname === "/v1/chat")) {
|
|
1225
|
-
return handleChat(request, env, ctx);
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
|
|
1229
|
-
return handleConversationRead(request, env, "list");
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
const conversationTraceMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)\\/trace$/);
|
|
1233
|
-
|
|
1234
|
-
if (request.method === "GET" && conversationTraceMatch) {
|
|
1235
|
-
return handleConversationRead(request, env, "trace", decodeURIComponent(conversationTraceMatch[1] || ""));
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
const conversationMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)$/);
|
|
1239
|
-
|
|
1240
|
-
if (request.method === "GET" && conversationMatch) {
|
|
1241
|
-
return handleConversationRead(request, env, "show", decodeURIComponent(conversationMatch[1] || ""));
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
|
|
1245
|
-
return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (url.pathname.startsWith("/files/")) {
|
|
1249
|
-
return handleFile(request, env, url.pathname.slice("/files/".length));
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
return Response.json({ error: { code: "not_found", message: "Unknown AgentKit route." } }, { status: 404 });
|
|
1253
|
-
},
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
|
-
function isHostedChannelWebhookPath(pathname) {
|
|
1257
|
-
return (
|
|
1258
|
-
/^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta|uazapi|evolution)|discord\\/discord|slack\\/slack|webhook\\/generic)\\/webhook$/.test(pathname) ||
|
|
1259
|
-
/^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack|webhook)\\/webhook$/.test(pathname) ||
|
|
1260
|
-
/^\\/channels\\/[^/]+\\/webhook$/.test(pathname) ||
|
|
1261
|
-
/^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta|uazapi|evolution)\\/webhook$/.test(pathname)
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
async function forwardHostedChannelWebhook(request, env, url) {
|
|
1266
|
-
if (!env.AGENTKIT_CLOUD_API_URL) {
|
|
1267
|
-
return Response.json(
|
|
1268
|
-
{
|
|
1269
|
-
error: {
|
|
1270
|
-
code: "channel_webhook_proxy_not_configured",
|
|
1271
|
-
message: "Hosted channel webhooks require AGENTKIT_CLOUD_API_URL on the deployed Worker.",
|
|
1272
|
-
},
|
|
1273
|
-
},
|
|
1274
|
-
{ status: 501 },
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
const target = new URL(\`\${url.pathname}\${url.search}\`, env.AGENTKIT_CLOUD_API_URL);
|
|
1279
|
-
const headers = new Headers(request.headers);
|
|
1280
|
-
headers.delete("host");
|
|
1281
|
-
headers.delete("content-length");
|
|
1282
|
-
|
|
1283
|
-
if (env.AGENTKIT_DEPLOY_ID) {
|
|
1284
|
-
headers.set("X-AgentKit-Deploy-ID", env.AGENTKIT_DEPLOY_ID);
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
if (env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
1288
|
-
headers.set("X-AgentKit-Runtime-Gate", env.AGENTKIT_RUNTIME_GATE_TOKEN);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
return fetch(target.href, {
|
|
1292
|
-
method: request.method,
|
|
1293
|
-
headers,
|
|
1294
|
-
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
|
|
1295
|
-
redirect: "manual",
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
async function handleFile(request, env, rawKey) {
|
|
1300
|
-
const auth = authorizeAdmin(request, env);
|
|
1301
|
-
|
|
1302
|
-
if (auth) {
|
|
1303
|
-
return auth;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
if (manifest.storage.files.driver !== "r2") {
|
|
1307
|
-
return Response.json({ error: { code: "files_not_configured", message: "R2 files are not configured." } }, { status: 404 });
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
if (!env.AGENTKIT_FILES) {
|
|
1311
|
-
return Response.json(
|
|
1312
|
-
{ error: { code: "deploy_not_ready", message: "AGENTKIT_FILES R2 binding is not configured." } },
|
|
1313
|
-
{ status: 503 },
|
|
1314
|
-
);
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
const key = objectKey(rawKey);
|
|
1318
|
-
|
|
1319
|
-
if (!key) {
|
|
1320
|
-
return Response.json({ error: { code: "validation_error", message: "file key must be non-empty." } }, { status: 400 });
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
if (request.method === "PUT" || request.method === "POST") {
|
|
1324
|
-
const contentLengthHeader = request.headers.get("content-length");
|
|
1325
|
-
|
|
1326
|
-
if (contentLengthHeader === null) {
|
|
1327
|
-
return Response.json(
|
|
1328
|
-
{ error: { code: "length_required", message: "File upload requires content-length." } },
|
|
1329
|
-
{ status: 411 },
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
const contentLength = Number(contentLengthHeader);
|
|
1334
|
-
|
|
1335
|
-
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
1336
|
-
return Response.json(
|
|
1337
|
-
{ error: { code: "validation_error", message: "content-length must be a valid non-negative number." } },
|
|
1338
|
-
{ status: 400 },
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
if (contentLength > manifest.storage.files.maxUploadBytes) {
|
|
1343
|
-
return Response.json(
|
|
1344
|
-
{ error: { code: "payload_too_large", message: "File exceeds configured maxUploadBytes." } },
|
|
1345
|
-
{ status: 413 },
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
const body = await request.arrayBuffer();
|
|
1350
|
-
|
|
1351
|
-
if (body.byteLength > manifest.storage.files.maxUploadBytes) {
|
|
1352
|
-
return Response.json(
|
|
1353
|
-
{ error: { code: "payload_too_large", message: "File exceeds configured maxUploadBytes." } },
|
|
1354
|
-
{ status: 413 },
|
|
1355
|
-
);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
await env.AGENTKIT_FILES.put(key, body, {
|
|
1359
|
-
httpMetadata: {
|
|
1360
|
-
contentType: request.headers.get("content-type") || "application/octet-stream",
|
|
1361
|
-
},
|
|
1362
|
-
customMetadata: {
|
|
1363
|
-
agent: manifest.name,
|
|
1364
|
-
},
|
|
1365
|
-
});
|
|
1366
|
-
|
|
1367
|
-
return Response.json({ key, bucket: manifest.storage.files.bucket });
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
if (request.method === "GET") {
|
|
1371
|
-
const object = await env.AGENTKIT_FILES.get(key);
|
|
1372
|
-
|
|
1373
|
-
if (!object) {
|
|
1374
|
-
return Response.json({ error: { code: "not_found", message: "File not found." } }, { status: 404 });
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
const headers = new Headers();
|
|
1378
|
-
object.writeHttpMetadata(headers);
|
|
1379
|
-
headers.set("etag", object.httpEtag);
|
|
1380
|
-
return new Response(object.body, { headers });
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
if (request.method === "DELETE") {
|
|
1384
|
-
await env.AGENTKIT_FILES.delete(key);
|
|
1385
|
-
return Response.json({ key, deleted: true });
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
return Response.json({ error: { code: "method_not_allowed", message: "Unsupported file method." } }, { status: 405 });
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
async function handleTool(request, env, toolName) {
|
|
1392
|
-
const auth = authorizeAdmin(request, env);
|
|
1393
|
-
|
|
1394
|
-
if (auth) {
|
|
1395
|
-
return auth;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
let body;
|
|
1399
|
-
|
|
1400
|
-
try {
|
|
1401
|
-
body = await request.json();
|
|
1402
|
-
} catch {
|
|
1403
|
-
return Response.json(
|
|
1404
|
-
{ error: { code: "validation_error", message: "Expected JSON body with optional input field." } },
|
|
1405
|
-
{ status: 400 },
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
try {
|
|
1410
|
-
const result = await executeHostedTool({
|
|
1411
|
-
name: toolName,
|
|
1412
|
-
input: isRecord(body) && "input" in body ? body.input : body ?? {},
|
|
1413
|
-
env,
|
|
1414
|
-
signal: request.signal,
|
|
1415
|
-
invocation: "tool",
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
return Response.json({
|
|
1419
|
-
tool: {
|
|
1420
|
-
id: result.id,
|
|
1421
|
-
name: result.name,
|
|
1422
|
-
visibility: result.visibility,
|
|
1423
|
-
},
|
|
1424
|
-
input: result.input,
|
|
1425
|
-
output: result.output,
|
|
1426
|
-
...(result.rendered ? { rendered: result.rendered } : {}),
|
|
1427
|
-
});
|
|
1428
|
-
} catch (error) {
|
|
1429
|
-
return hostedErrorResponse(error);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
async function handleChat(request, env, ctx) {
|
|
1434
|
-
let body;
|
|
1435
|
-
|
|
1436
|
-
try {
|
|
1437
|
-
body = await request.json();
|
|
1438
|
-
} catch {
|
|
1439
|
-
return Response.json(
|
|
1440
|
-
{ error: { code: "validation_error", message: "Expected JSON body with a message field." } },
|
|
1441
|
-
{ status: 400 },
|
|
1442
|
-
);
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
const auth = await authorizeChat(request, env, body);
|
|
1446
|
-
|
|
1447
|
-
if (auth) {
|
|
1448
|
-
return auth;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
const message = readChatMessage(body);
|
|
1452
|
-
|
|
1453
|
-
if (!message) {
|
|
1454
|
-
return Response.json(
|
|
1455
|
-
{ error: { code: "validation_error", message: "message must be a non-empty string." } },
|
|
1456
|
-
{ status: 400 },
|
|
1457
|
-
);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
let conversationId;
|
|
1461
|
-
|
|
1462
|
-
try {
|
|
1463
|
-
conversationId = normalizeConversationId(body.conversationId) ?? crypto.randomUUID();
|
|
1464
|
-
} catch (error) {
|
|
1465
|
-
return hostedErrorResponse(error);
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
const previousMessages = await loadConversationMessages(env, conversationId).catch(() => []);
|
|
1469
|
-
let content;
|
|
1470
|
-
let providerResult;
|
|
1471
|
-
const toolCalls = [];
|
|
1472
|
-
|
|
1473
|
-
try {
|
|
1474
|
-
providerResult = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal, toolCalls);
|
|
1475
|
-
content = providerResult.content;
|
|
1476
|
-
} catch (error) {
|
|
1477
|
-
const persisted = await persistFailure(env, conversationId, message, error, toolCalls);
|
|
1478
|
-
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1479
|
-
kind: "chat_run",
|
|
1480
|
-
status: "failed",
|
|
1481
|
-
conversationId,
|
|
1482
|
-
runId: persisted?.runId,
|
|
1483
|
-
provider: manifest.provider.name,
|
|
1484
|
-
model: manifest.provider.model,
|
|
1485
|
-
metadata: {
|
|
1486
|
-
error_code: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
|
|
1487
|
-
tool_calls: toolCalls.length,
|
|
1488
|
-
},
|
|
1489
|
-
}));
|
|
1490
|
-
return hostedErrorResponse(error);
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
const persisted = await persist(env, {
|
|
1494
|
-
conversationId,
|
|
1495
|
-
userMessage: message,
|
|
1496
|
-
assistantMessage: content,
|
|
1497
|
-
usage: providerResult.usage,
|
|
1498
|
-
toolCalls,
|
|
1499
|
-
});
|
|
1500
|
-
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1501
|
-
kind: "chat_run",
|
|
1502
|
-
status: "succeeded",
|
|
1503
|
-
conversationId: persisted.conversationId ?? conversationId,
|
|
1504
|
-
runId: persisted.runId,
|
|
1505
|
-
provider: manifest.provider.name,
|
|
1506
|
-
model: manifest.provider.model,
|
|
1507
|
-
usage: providerResult.usage,
|
|
1508
|
-
metadata: {
|
|
1509
|
-
tool_calls: toolCalls.length,
|
|
1510
|
-
},
|
|
1511
|
-
}));
|
|
1512
|
-
|
|
1513
|
-
return Response.json({
|
|
1514
|
-
message: {
|
|
1515
|
-
role: "assistant",
|
|
1516
|
-
content,
|
|
1517
|
-
},
|
|
1518
|
-
...persisted,
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
async function handleConversationRead(request, env, action, conversationId) {
|
|
1523
|
-
const auth = await authorizeConversationRead(request, env);
|
|
1524
|
-
|
|
1525
|
-
if (auth) {
|
|
1526
|
-
return auth;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
if (!env.AGENTKIT_CONVERSATIONS) {
|
|
1530
|
-
return Response.json({ error: { code: "conversations_not_configured", message: "Hosted conversation storage is not configured." } }, { status: 503 });
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
if (action === "list") {
|
|
1534
|
-
const stub = hostedConversationStore(env);
|
|
1535
|
-
return stub.fetch("https://agentkit.internal/conversations");
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
if (!conversationId) {
|
|
1539
|
-
return Response.json({ error: { code: "validation_error", message: "Missing conversation id." } }, { status: 400 });
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
const stub = hostedConversationStore(env);
|
|
1543
|
-
const suffix = action === "trace" ? "/trace" : "";
|
|
1544
|
-
const path = \`/conversations/\${encodeURIComponent(conversationId)}\${suffix}\`;
|
|
1545
|
-
const response = await stub.fetch(\`https://agentkit.internal\${path}\`);
|
|
1546
|
-
|
|
1547
|
-
if (response.status !== 404) {
|
|
1548
|
-
return response;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
const legacyStub = legacyConversationStore(env, conversationId);
|
|
1552
|
-
return legacyStub.fetch(\`https://agentkit.internal\${path}\`);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
function readChatMessage(body) {
|
|
1556
|
-
if (typeof body.message === "string") {
|
|
1557
|
-
return body.message.trim();
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
if (body.message && typeof body.message === "object" && body.message.role === "user" && typeof body.message.content === "string") {
|
|
1561
|
-
return body.message.content.trim();
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
return "";
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
function isRecord(value) {
|
|
1568
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
async function callHostedProvider(env, messages, signal, toolCalls = []) {
|
|
1572
|
-
const adapter = getProviderAdapter(manifest.provider);
|
|
1573
|
-
const hostedTools = createHostedTools(env);
|
|
1574
|
-
const clock = buildRuntimeClockContext({
|
|
1575
|
-
timeZone: manifest.timeZone,
|
|
1576
|
-
env,
|
|
1577
|
-
});
|
|
1578
|
-
const result = await adapter.run({
|
|
1579
|
-
agent: {
|
|
1580
|
-
name: manifest.name,
|
|
1581
|
-
runtime: manifest.runtime,
|
|
1582
|
-
provider: manifest.provider,
|
|
1583
|
-
},
|
|
1584
|
-
instructions: appendRuntimePromptContext(
|
|
1585
|
-
withToolVisibilityInstructions(appendHostedKnowledgePromptPolicy(instructions), hostedTools),
|
|
1586
|
-
{
|
|
1587
|
-
clock,
|
|
1588
|
-
},
|
|
1589
|
-
),
|
|
1590
|
-
messages,
|
|
1591
|
-
tools: hostedTools,
|
|
1592
|
-
toolRuntime: createHostedProviderToolRuntime(env, signal, clock, toolCalls),
|
|
1593
|
-
env,
|
|
1594
|
-
signal,
|
|
1595
|
-
});
|
|
1596
|
-
|
|
1597
|
-
return {
|
|
1598
|
-
content: result.message.content,
|
|
1599
|
-
usage: result.usage,
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
function appendHostedKnowledgePromptPolicy(baseInstructions) {
|
|
1604
|
-
if (!manifest.knowledge.enabled) {
|
|
1605
|
-
return baseInstructions;
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
return \`\${baseInstructions.trimEnd()}
|
|
1609
|
-
|
|
1610
|
-
Knowledge rules:
|
|
1611
|
-
- Use agentkit_search_knowledge before answering business-specific factual questions about the user's documents, policies, prices, availability, services, procedures, or source-specific claims.
|
|
1612
|
-
- Do not invent prices, policies, legal/clinical claims, availability, or source-specific facts when Knowledge has not returned support for the answer.
|
|
1613
|
-
- 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.
|
|
1614
|
-
\`;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
function withToolVisibilityInstructions(baseInstructions, tools) {
|
|
1618
|
-
const internalTools = tools
|
|
1619
|
-
.filter((tool) => tool.visibility === "internal")
|
|
1620
|
-
.map((tool) => tool.name)
|
|
1621
|
-
.sort();
|
|
1622
|
-
|
|
1623
|
-
if (internalTools.length === 0) {
|
|
1624
|
-
return baseInstructions;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
return \`\${baseInstructions.trimEnd()}
|
|
1628
|
-
|
|
1629
|
-
Tool visibility rules:
|
|
1630
|
-
- Tool outputs marked internal are operational state for the agent, not user-facing content.
|
|
1631
|
-
- Use internal outputs to decide what to do next, but do not reveal raw internal labels, scores, statuses, JSON fields, or tool output objects to the user.
|
|
1632
|
-
- Internal tools: \${internalTools.join(", ")}.
|
|
1633
|
-
\`;
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
function createHostedKnowledgeSearchTool() {
|
|
1637
|
-
return {
|
|
1638
|
-
name: manifest.knowledge.internalTool || "agentkit_search_knowledge",
|
|
1639
|
-
description: "Search the Agent Capsule's configured Knowledge sources for business-specific facts.",
|
|
1640
|
-
visibility: "internal",
|
|
1641
|
-
inputSchema: {
|
|
1642
|
-
type: "object",
|
|
1643
|
-
properties: {
|
|
1644
|
-
query: { type: "string" },
|
|
1645
|
-
topK: { type: "integer" },
|
|
1646
|
-
},
|
|
1647
|
-
required: ["query"],
|
|
1648
|
-
additionalProperties: false,
|
|
1649
|
-
},
|
|
1650
|
-
outputSchema: {
|
|
1651
|
-
type: "object",
|
|
1652
|
-
properties: {
|
|
1653
|
-
results: {
|
|
1654
|
-
type: "array",
|
|
1655
|
-
items: {
|
|
1656
|
-
type: "object",
|
|
1657
|
-
properties: {
|
|
1658
|
-
content: { type: "string" },
|
|
1659
|
-
score: { type: "number" },
|
|
1660
|
-
source: { type: "object" },
|
|
1661
|
-
},
|
|
1662
|
-
required: ["content", "score", "source"],
|
|
1663
|
-
},
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
required: ["results"],
|
|
1667
|
-
additionalProperties: false,
|
|
1668
|
-
},
|
|
1669
|
-
secrets: manifest.knowledge.embedding.secret ? [manifest.knowledge.embedding.secret] : [],
|
|
1670
|
-
async execute(input, ctx) {
|
|
1671
|
-
const query = typeof input?.query === "string" ? input.query : "";
|
|
1672
|
-
const topK = normalizeKnowledgeTopK(input?.topK ?? manifest.knowledge.retrieval.topK);
|
|
1673
|
-
const results = await searchHostedKnowledge(ctx.database, query, topK, ctx.secrets ?? {});
|
|
1674
|
-
|
|
1675
|
-
return {
|
|
1676
|
-
results: results.map((result) => ({
|
|
1677
|
-
content: result.content,
|
|
1678
|
-
score: result.score,
|
|
1679
|
-
source: result.source,
|
|
1680
|
-
})),
|
|
1681
|
-
};
|
|
1682
|
-
},
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
async function searchHostedKnowledge(database, query, topK, secrets) {
|
|
1687
|
-
const normalizedQuery = String(query || "").trim();
|
|
1688
|
-
|
|
1689
|
-
if (!normalizedQuery) {
|
|
1690
|
-
throw agentKitError("knowledge_query_invalid", "Knowledge search query must be a non-empty string.");
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
const hasEmbeddingProvider = manifest.knowledge.embedding.provider !== "none";
|
|
1694
|
-
const lexical =
|
|
1695
|
-
manifest.knowledge.retrieval.hybrid || !hasEmbeddingProvider
|
|
1696
|
-
? await searchHostedKnowledgeLexical(database, normalizedQuery, Math.max(topK * 4, topK))
|
|
1697
|
-
: [];
|
|
1698
|
-
const semantic = hasEmbeddingProvider
|
|
1699
|
-
? await searchHostedKnowledgeSemantic(database, normalizedQuery, topK * 4, secrets)
|
|
1700
|
-
: [];
|
|
1701
|
-
|
|
1702
|
-
return combineHostedKnowledgeResults(lexical, semantic, topK);
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
async function searchHostedKnowledgeLexical(database, query, limit) {
|
|
1706
|
-
const tokens = hostedKnowledgeQueryTokens(query);
|
|
1707
|
-
|
|
1708
|
-
if (tokens.length === 0) {
|
|
1709
|
-
return [];
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
try {
|
|
1713
|
-
const result = await database.query(
|
|
1714
|
-
\`
|
|
1715
|
-
SELECT
|
|
1716
|
-
c.id AS chunk_id,
|
|
1717
|
-
c.source_id AS source_id,
|
|
1718
|
-
c.content AS content,
|
|
1719
|
-
c.title AS title,
|
|
1720
|
-
c.section AS section,
|
|
1721
|
-
c.locator AS locator,
|
|
1722
|
-
s.source_key AS source_key,
|
|
1723
|
-
rank
|
|
1724
|
-
FROM agentkit_knowledge_fts
|
|
1725
|
-
JOIN agentkit_knowledge_chunks c ON c.id = agentkit_knowledge_fts.chunk_id
|
|
1726
|
-
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1727
|
-
WHERE agentkit_knowledge_fts MATCH ?
|
|
1728
|
-
ORDER BY rank ASC
|
|
1729
|
-
LIMIT ?
|
|
1730
|
-
\`,
|
|
1731
|
-
[tokens.map((token) => \`"\${token.replace(/"/g, '""')}"\`).join(" OR "), limit],
|
|
1732
|
-
);
|
|
1733
|
-
|
|
1734
|
-
return result.rows.map((row, index) => hostedKnowledgeResultFromRow(row, hostedLexicalScore(index), null));
|
|
1735
|
-
} catch {
|
|
1736
|
-
return searchHostedKnowledgeLexicalFallback(database, query, limit);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
async function searchHostedKnowledgeLexicalFallback(database, query, limit) {
|
|
1741
|
-
const like = \`%\${query.toLowerCase()}%\`;
|
|
1742
|
-
const result = await database.query(
|
|
1743
|
-
\`
|
|
1744
|
-
SELECT
|
|
1745
|
-
c.id AS chunk_id,
|
|
1746
|
-
c.source_id AS source_id,
|
|
1747
|
-
c.content AS content,
|
|
1748
|
-
c.title AS title,
|
|
1749
|
-
c.section AS section,
|
|
1750
|
-
c.locator AS locator,
|
|
1751
|
-
s.source_key AS source_key
|
|
1752
|
-
FROM agentkit_knowledge_chunks c
|
|
1753
|
-
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1754
|
-
WHERE lower(c.content) LIKE ?
|
|
1755
|
-
OR lower(COALESCE(c.title, '')) LIKE ?
|
|
1756
|
-
OR lower(COALESCE(c.section, '')) LIKE ?
|
|
1757
|
-
ORDER BY c.created_at DESC, c.ordinal ASC
|
|
1758
|
-
LIMIT ?
|
|
1759
|
-
\`,
|
|
1760
|
-
[like, like, like, limit],
|
|
1761
|
-
);
|
|
1762
|
-
|
|
1763
|
-
return result.rows.map((row, index) => hostedKnowledgeResultFromRow(row, hostedLexicalScore(index), null));
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
async function searchHostedKnowledgeSemantic(database, query, limit, secrets) {
|
|
1767
|
-
const queryEmbedding = await createHostedKnowledgeEmbedding(query, secrets);
|
|
1768
|
-
|
|
1769
|
-
if (!queryEmbedding) {
|
|
1770
|
-
return [];
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
try {
|
|
1774
|
-
return await searchHostedKnowledgeNativeVector(database, queryEmbedding, limit);
|
|
1775
|
-
} catch {
|
|
1776
|
-
return searchHostedKnowledgeSemanticFallback(database, queryEmbedding, limit);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
async function searchHostedKnowledgeNativeVector(database, queryEmbedding, limit) {
|
|
1781
|
-
const vector = JSON.stringify(queryEmbedding);
|
|
1782
|
-
const result = await database.query(
|
|
1783
|
-
\`
|
|
1784
|
-
SELECT
|
|
1785
|
-
c.id AS chunk_id,
|
|
1786
|
-
c.source_id AS source_id,
|
|
1787
|
-
c.content AS content,
|
|
1788
|
-
c.title AS title,
|
|
1789
|
-
c.section AS section,
|
|
1790
|
-
c.locator AS locator,
|
|
1791
|
-
s.source_key AS source_key,
|
|
1792
|
-
vector_distance_cos(v.embedding, vector32(?)) AS vector_distance
|
|
1793
|
-
FROM vector_top_k('agentkit_knowledge_vectors_embedding_idx', vector32(?), ?) nearest
|
|
1794
|
-
JOIN agentkit_knowledge_vectors v ON v.id = nearest.id
|
|
1795
|
-
JOIN agentkit_knowledge_chunks c ON c.id = v.chunk_id
|
|
1796
|
-
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1797
|
-
ORDER BY vector_distance ASC
|
|
1798
|
-
\`,
|
|
1799
|
-
[vector, vector, limit],
|
|
1800
|
-
);
|
|
1801
|
-
|
|
1802
|
-
return result.rows
|
|
1803
|
-
.map((row) => {
|
|
1804
|
-
const distance = Number(row.vector_distance);
|
|
1805
|
-
const semantic = Number.isFinite(distance) ? 1 - distance : 0;
|
|
1806
|
-
return hostedKnowledgeResultFromRow(row, 0, semantic);
|
|
1807
|
-
})
|
|
1808
|
-
.filter((result) => result.match.semantic !== null && result.match.semantic > 0)
|
|
1809
|
-
.slice(0, limit);
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
async function searchHostedKnowledgeSemanticFallback(database, queryEmbedding, limit) {
|
|
1813
|
-
const result = await database.query(
|
|
1814
|
-
\`
|
|
1815
|
-
SELECT
|
|
1816
|
-
c.id AS chunk_id,
|
|
1817
|
-
c.source_id AS source_id,
|
|
1818
|
-
c.content AS content,
|
|
1819
|
-
c.title AS title,
|
|
1820
|
-
c.section AS section,
|
|
1821
|
-
c.locator AS locator,
|
|
1822
|
-
c.embedding_json AS embedding_json,
|
|
1823
|
-
s.source_key AS source_key
|
|
1824
|
-
FROM agentkit_knowledge_chunks c
|
|
1825
|
-
JOIN agentkit_knowledge_sources s ON s.id = c.source_id
|
|
1826
|
-
WHERE c.embedding_json IS NOT NULL
|
|
1827
|
-
ORDER BY c.created_at DESC, c.ordinal ASC
|
|
1828
|
-
\`,
|
|
1829
|
-
);
|
|
1830
|
-
|
|
1831
|
-
return result.rows
|
|
1832
|
-
.map((row) => {
|
|
1833
|
-
const embedding = parseHostedKnowledgeEmbedding(row.embedding_json);
|
|
1834
|
-
const semantic = embedding ? hostedCosineSimilarity(queryEmbedding, embedding) : 0;
|
|
1835
|
-
return hostedKnowledgeResultFromRow(row, 0, semantic);
|
|
1836
|
-
})
|
|
1837
|
-
.filter((result) => result.match.semantic !== null && result.match.semantic > 0)
|
|
1838
|
-
.sort((left, right) => (right.match.semantic ?? 0) - (left.match.semantic ?? 0))
|
|
1839
|
-
.slice(0, limit);
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
async function createHostedKnowledgeEmbedding(input, secrets) {
|
|
1843
|
-
const provider = manifest.knowledge.embedding.provider;
|
|
1844
|
-
|
|
1845
|
-
if (provider === "none") {
|
|
1846
|
-
return null;
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
if (provider === "test") {
|
|
1850
|
-
return hostedFakeEmbedding(input, manifest.knowledge.embedding.dimensions || 32);
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
if (provider !== "openai") {
|
|
1854
|
-
throw agentKitError("knowledge_embedding_provider_unsupported", "Unsupported knowledge embedding provider.");
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
const secretName = manifest.knowledge.embedding.secret || "OPENAI_API_KEY";
|
|
1858
|
-
const apiKey = secrets[secretName];
|
|
1859
|
-
|
|
1860
|
-
if (!apiKey) {
|
|
1861
|
-
throw agentKitError("knowledge_embedding_secret_missing", \`Knowledge embeddings require missing secret "\${secretName}".\`);
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
1865
|
-
method: "POST",
|
|
1866
|
-
headers: {
|
|
1867
|
-
"content-type": "application/json",
|
|
1868
|
-
authorization: \`Bearer \${apiKey}\`,
|
|
1869
|
-
},
|
|
1870
|
-
body: JSON.stringify({
|
|
1871
|
-
model: manifest.knowledge.embedding.model || "text-embedding-3-small",
|
|
1872
|
-
input,
|
|
1873
|
-
encoding_format: "float",
|
|
1874
|
-
...(manifest.knowledge.embedding.dimensions ? { dimensions: manifest.knowledge.embedding.dimensions } : {}),
|
|
1875
|
-
}),
|
|
1876
|
-
});
|
|
1877
|
-
|
|
1878
|
-
if (!response.ok) {
|
|
1879
|
-
const text = await response.text().catch(() => "");
|
|
1880
|
-
throw agentKitError(
|
|
1881
|
-
"knowledge_embedding_failed",
|
|
1882
|
-
\`OpenAI embedding request failed with HTTP \${response.status}.\${text ? " " + text.slice(0, 240) : ""}\`,
|
|
1883
|
-
);
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
const payload = await response.json();
|
|
1887
|
-
const embedding = payload?.data?.[0]?.embedding;
|
|
1888
|
-
|
|
1889
|
-
if (!Array.isArray(embedding) || embedding.some((value) => typeof value !== "number")) {
|
|
1890
|
-
throw agentKitError("knowledge_embedding_failed", "OpenAI embedding response did not contain a numeric vector.");
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
return embedding;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
function combineHostedKnowledgeResults(lexical, semantic, topK) {
|
|
1897
|
-
const byChunk = new Map();
|
|
1898
|
-
|
|
1899
|
-
for (const result of lexical) {
|
|
1900
|
-
byChunk.set(result.chunkId, result);
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
for (const result of semantic) {
|
|
1904
|
-
const existing = byChunk.get(result.chunkId);
|
|
1905
|
-
|
|
1906
|
-
if (!existing) {
|
|
1907
|
-
byChunk.set(result.chunkId, {
|
|
1908
|
-
...result,
|
|
1909
|
-
score: hostedCombinedScore(0, result.match.semantic),
|
|
1910
|
-
});
|
|
1911
|
-
continue;
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
byChunk.set(result.chunkId, {
|
|
1915
|
-
...existing,
|
|
1916
|
-
score: hostedCombinedScore(existing.match.lexical, result.match.semantic),
|
|
1917
|
-
match: {
|
|
1918
|
-
lexical: existing.match.lexical,
|
|
1919
|
-
semantic: result.match.semantic,
|
|
1920
|
-
},
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
return Array.from(byChunk.values())
|
|
1925
|
-
.map((result) => ({
|
|
1926
|
-
...result,
|
|
1927
|
-
score: result.score || hostedCombinedScore(result.match.lexical, result.match.semantic),
|
|
1928
|
-
}))
|
|
1929
|
-
.sort((left, right) => right.score - left.score || left.source.path.localeCompare(right.source.path))
|
|
1930
|
-
.slice(0, topK);
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
function hostedKnowledgeResultFromRow(row, lexical, semantic) {
|
|
1934
|
-
return {
|
|
1935
|
-
chunkId: String(row.chunk_id),
|
|
1936
|
-
content: String(row.content),
|
|
1937
|
-
score: hostedCombinedScore(lexical, semantic),
|
|
1938
|
-
source: {
|
|
1939
|
-
id: String(row.source_id),
|
|
1940
|
-
path: String(row.source_key),
|
|
1941
|
-
title: typeof row.title === "string" ? row.title : null,
|
|
1942
|
-
section: typeof row.section === "string" ? row.section : null,
|
|
1943
|
-
locator: typeof row.locator === "string" ? row.locator : null,
|
|
1944
|
-
},
|
|
1945
|
-
match: {
|
|
1946
|
-
lexical,
|
|
1947
|
-
semantic,
|
|
1948
|
-
},
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
function normalizeKnowledgeTopK(value) {
|
|
1953
|
-
const topK = Number(value);
|
|
1954
|
-
|
|
1955
|
-
if (!Number.isInteger(topK) || topK <= 0 || topK > 50) {
|
|
1956
|
-
throw agentKitError("knowledge_query_invalid", "Knowledge search topK must be a positive integer no larger than 50.");
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
return topK;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
function hostedKnowledgeQueryTokens(query) {
|
|
1963
|
-
return Array.from(new Set(String(query).toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [])).slice(0, 12);
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
function parseHostedKnowledgeEmbedding(value) {
|
|
1967
|
-
if (typeof value !== "string") {
|
|
1968
|
-
return null;
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
try {
|
|
1972
|
-
const parsed = JSON.parse(value);
|
|
1973
|
-
return Array.isArray(parsed) && parsed.every((item) => typeof item === "number") ? parsed : null;
|
|
1974
|
-
} catch {
|
|
1975
|
-
return null;
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
function hostedCombinedScore(lexical, semantic) {
|
|
1980
|
-
const semanticScore = semantic === null ? 0 : Math.max(0, (semantic + 1) / 2);
|
|
1981
|
-
return Number((lexical * 0.6 + semanticScore * 0.4).toFixed(6));
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
function hostedLexicalScore(index) {
|
|
1985
|
-
return Number((1 / (index + 1)).toFixed(6));
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
function hostedCosineSimilarity(left, right) {
|
|
1989
|
-
if (left.length !== right.length || left.length === 0) {
|
|
1990
|
-
return 0;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
let dot = 0;
|
|
1994
|
-
let leftNorm = 0;
|
|
1995
|
-
let rightNorm = 0;
|
|
1996
|
-
|
|
1997
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
1998
|
-
dot += left[index] * right[index];
|
|
1999
|
-
leftNorm += left[index] * left[index];
|
|
2000
|
-
rightNorm += right[index] * right[index];
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
if (leftNorm === 0 || rightNorm === 0) {
|
|
2004
|
-
return 0;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
async function hostedFakeEmbedding(input, dimensions) {
|
|
2011
|
-
const values = Array.from({ length: dimensions }, () => 0);
|
|
2012
|
-
const tokens = String(input).toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [];
|
|
2013
|
-
const encoder = new TextEncoder();
|
|
2014
|
-
|
|
2015
|
-
for (const token of tokens) {
|
|
2016
|
-
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token)));
|
|
2017
|
-
const slot = hash[0] % dimensions;
|
|
2018
|
-
const sign = hash[1] % 2 === 0 ? 1 : -1;
|
|
2019
|
-
values[slot] += sign * (1 + (hash[2] % 7) / 10);
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
const norm = Math.sqrt(values.reduce((sum, value) => sum + value * value, 0));
|
|
2023
|
-
return norm === 0 ? values : values.map((value) => Number((value / norm).toFixed(8)));
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
|
|
2027
|
-
return persist(env, {
|
|
2028
|
-
conversationId,
|
|
2029
|
-
userMessage,
|
|
2030
|
-
status: "failed",
|
|
2031
|
-
errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
|
|
2032
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
2033
|
-
toolCalls,
|
|
2034
|
-
}).catch(() => undefined);
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
function inspectHostedDatabase() {
|
|
2038
|
-
const schema = manifest.storage.database.driver === "turso" ? manifest.storage.database.schema : null;
|
|
2039
|
-
|
|
2040
|
-
return {
|
|
2041
|
-
local: {
|
|
2042
|
-
driver: "sqlite",
|
|
2043
|
-
path: ".agentkit/agentkit.db",
|
|
2044
|
-
schema,
|
|
2045
|
-
},
|
|
2046
|
-
hosted: {
|
|
2047
|
-
driver: manifest.storage.database.driver,
|
|
2048
|
-
provisioning: manifest.storage.database.driver === "turso" ? "agentkit-managed" : "none",
|
|
2049
|
-
schema,
|
|
2050
|
-
},
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
function hostedManagedSecrets() {
|
|
2055
|
-
const secrets = [];
|
|
2056
|
-
|
|
2057
|
-
if (manifest.storage.database.driver === "turso") {
|
|
2058
|
-
secrets.push("TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL");
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
if (manifest.integrations.some((integration) => integration.provider === "composio" && integration.mode === "managed")) {
|
|
2062
|
-
secrets.push("COMPOSIO_API_KEY");
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
return secrets.sort();
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
function createHostedTools(env) {
|
|
2069
|
-
return [
|
|
2070
|
-
...(hostedKnowledgeTool ? [hostedKnowledgeTool] : []),
|
|
2071
|
-
...createManagedComposioTools(agentConfig, { userId: env[MANAGED_COMPOSIO_USER_ID_BINDING] }),
|
|
2072
|
-
...(agentConfig.tools ?? []),
|
|
2073
|
-
];
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
function hostedIntegrations(env) {
|
|
2077
|
-
const userId = env[MANAGED_COMPOSIO_USER_ID_BINDING];
|
|
2078
|
-
|
|
2079
|
-
if (!userId) {
|
|
2080
|
-
return manifest.integrations;
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
return manifest.integrations.map((integration) =>
|
|
2084
|
-
integration.provider === "composio" && integration.mode === "managed"
|
|
2085
|
-
? { ...integration, userId }
|
|
2086
|
-
: integration,
|
|
2087
|
-
);
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
function createHostedProviderToolRuntime(env, signal, clock, toolCalls) {
|
|
2091
|
-
const hostedTools = createHostedTools(env);
|
|
2092
|
-
|
|
2093
|
-
return {
|
|
2094
|
-
definitions: hostedTools,
|
|
2095
|
-
async execute(request) {
|
|
2096
|
-
try {
|
|
2097
|
-
const result = await executeHostedTool({
|
|
2098
|
-
tools: hostedTools,
|
|
2099
|
-
name: request.name,
|
|
2100
|
-
input: request.input,
|
|
2101
|
-
env,
|
|
2102
|
-
signal,
|
|
2103
|
-
invocation: "chat",
|
|
2104
|
-
clock,
|
|
2105
|
-
});
|
|
2106
|
-
toolCalls?.push(result);
|
|
2107
|
-
return result;
|
|
2108
|
-
} catch (error) {
|
|
2109
|
-
toolCalls?.push(failedHostedToolCall(hostedTools, request, error));
|
|
2110
|
-
throw error;
|
|
2111
|
-
}
|
|
2112
|
-
},
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
async function executeHostedTool(request) {
|
|
2117
|
-
const hostedTools = request.tools ?? createHostedTools(request.env);
|
|
2118
|
-
const tool = hostedTools.find((candidate) => candidate.name === request.name);
|
|
2119
|
-
const id = crypto.randomUUID();
|
|
2120
|
-
|
|
2121
|
-
if (!tool) {
|
|
2122
|
-
throw agentKitError("tool_not_found", \`Tool "\${request.name}" is not declared in agentkit.config.ts.\`);
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
const input = normalizeOptionalNulls(request.input ?? {}, tool.inputSchema);
|
|
2126
|
-
validateSchema(input, tool.inputSchema, \`tool "\${tool.name}" input\`);
|
|
2127
|
-
assertToolInvocationAuthorized(tool, request.invocation ?? "tool");
|
|
2128
|
-
|
|
2129
|
-
const secrets = resolveToolSecrets(tool, request.env);
|
|
2130
|
-
const database = createHostedDatabaseRunner(request.env);
|
|
2131
|
-
const clock = request.clock ?? buildRuntimeClockContext({
|
|
2132
|
-
timeZone: manifest.timeZone,
|
|
2133
|
-
env: request.env,
|
|
2134
|
-
});
|
|
2135
|
-
const runtime = {
|
|
2136
|
-
environment: "hosted",
|
|
2137
|
-
invocation: request.invocation ?? "tool",
|
|
2138
|
-
target: "cloudflare",
|
|
2139
|
-
database: manifest.storage.database.driver === "turso" ? "turso" : "none",
|
|
2140
|
-
};
|
|
2141
|
-
const output = await runToolWithTimeout(
|
|
2142
|
-
tool,
|
|
2143
|
-
input,
|
|
2144
|
-
{
|
|
2145
|
-
secrets,
|
|
2146
|
-
database,
|
|
2147
|
-
signal: request.signal,
|
|
2148
|
-
clock,
|
|
2149
|
-
runtime,
|
|
2150
|
-
},
|
|
2151
|
-
tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
|
|
2152
|
-
);
|
|
2153
|
-
|
|
2154
|
-
if (tool.outputSchema) {
|
|
2155
|
-
validateSchema(output, tool.outputSchema, \`tool "\${tool.name}" output\`);
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
const rendered = renderHostedToolOutput(tool, input, output, runtime, clock);
|
|
2159
|
-
|
|
2160
|
-
return {
|
|
2161
|
-
id,
|
|
2162
|
-
name: tool.name,
|
|
2163
|
-
visibility: tool.visibility ?? "user",
|
|
2164
|
-
status: "completed",
|
|
2165
|
-
input,
|
|
2166
|
-
output,
|
|
2167
|
-
...(rendered ? { rendered } : {}),
|
|
2168
|
-
};
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
function assertToolInvocationAuthorized(tool, invocation) {
|
|
2172
|
-
if (invocation === "tool") {
|
|
2173
|
-
return;
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
const privilegedPermissions = (tool.permissions ?? []).filter((permission) => !permission.endsWith(":read"));
|
|
2177
|
-
|
|
2178
|
-
if (privilegedPermissions.length === 0) {
|
|
2179
|
-
return;
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
throw agentKitError(
|
|
2183
|
-
"tool_authorization_required",
|
|
2184
|
-
\`Tool "\${tool.name}" requires an explicit operator invocation because it declares privileged permissions: \${privilegedPermissions.join(", ")}.\`,
|
|
2185
|
-
);
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
function failedHostedToolCall(tools, request, error) {
|
|
2189
|
-
const tool = tools.find((candidate) => candidate.name === request.name);
|
|
2190
|
-
|
|
2191
|
-
return {
|
|
2192
|
-
id: crypto.randomUUID(),
|
|
2193
|
-
name: String(request.name || ""),
|
|
2194
|
-
visibility: tool?.visibility ?? "user",
|
|
2195
|
-
status: "failed",
|
|
2196
|
-
input: request.input ?? {},
|
|
2197
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
function renderHostedToolOutput(tool, input, output, runtime, clock) {
|
|
2202
|
-
if (!tool.render) {
|
|
2203
|
-
return undefined;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
const rendered = tool.render(output, {
|
|
2207
|
-
input,
|
|
2208
|
-
runtime,
|
|
2209
|
-
clock,
|
|
2210
|
-
});
|
|
2211
|
-
|
|
2212
|
-
if (typeof rendered === "string") {
|
|
2213
|
-
return { text: rendered };
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
if (!isRecord(rendered) || typeof rendered.text !== "string" || rendered.text.trim().length === 0) {
|
|
2217
|
-
throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render must return a non-empty text string.\`);
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
|
-
if (rendered.blocks !== undefined) {
|
|
2221
|
-
if (!Array.isArray(rendered.blocks)) {
|
|
2222
|
-
throw agentKitError("tool_validation_error", \`tool "\${tool.name}" render.blocks must be an array when provided.\`);
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
for (const [index, block] of rendered.blocks.entries()) {
|
|
2226
|
-
validateRenderBlock(block, \`tool "\${tool.name}" render.blocks[\${index}]\`);
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
return {
|
|
2231
|
-
text: rendered.text,
|
|
2232
|
-
...(rendered.blocks ? { blocks: rendered.blocks } : {}),
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
function validateRenderBlock(block, label) {
|
|
2237
|
-
if (!isRecord(block)) {
|
|
2238
|
-
throw agentKitError("tool_validation_error", \`\${label} must be an object.\`);
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
if (block.type === "text") {
|
|
2242
|
-
if (typeof block.text !== "string" || block.text.trim().length === 0) {
|
|
2243
|
-
throw agentKitError("tool_validation_error", \`\${label}.text must be a non-empty string.\`);
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
if (block.type === "card") {
|
|
2250
|
-
if (typeof block.title !== "string" || block.title.trim().length === 0) {
|
|
2251
|
-
throw agentKitError("tool_validation_error", \`\${label}.title must be a non-empty string.\`);
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
validateOptionalString(block.subtitle, \`\${label}.subtitle\`);
|
|
2255
|
-
validateRenderFields(block.fields, \`\${label}.fields\`);
|
|
2256
|
-
validateRenderActions(block.actions, \`\${label}.actions\`);
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
throw agentKitError("tool_validation_error", \`\${label}.type must be "text" or "card".\`);
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
function validateRenderFields(value, label) {
|
|
2264
|
-
if (value === undefined) {
|
|
2265
|
-
return;
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
if (!Array.isArray(value)) {
|
|
2269
|
-
throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
for (const [index, field] of value.entries()) {
|
|
2273
|
-
if (!isRecord(field)) {
|
|
2274
|
-
throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
validateRequiredString(field.label, \`\${label}[\${index}].label\`);
|
|
2278
|
-
validateRequiredString(field.value, \`\${label}[\${index}].value\`);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
function validateRenderActions(value, label) {
|
|
2283
|
-
if (value === undefined) {
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
if (!Array.isArray(value)) {
|
|
2288
|
-
throw agentKitError("tool_validation_error", \`\${label} must be an array when provided.\`);
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
for (const [index, action] of value.entries()) {
|
|
2292
|
-
if (!isRecord(action)) {
|
|
2293
|
-
throw agentKitError("tool_validation_error", \`\${label}[\${index}] must be an object.\`);
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
validateRequiredString(action.label, \`\${label}[\${index}].label\`);
|
|
2297
|
-
validateOptionalString(action.href, \`\${label}[\${index}].href\`);
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
function validateRequiredString(value, label) {
|
|
2302
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2303
|
-
throw agentKitError("tool_validation_error", \`\${label} must be a non-empty string.\`);
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
function validateOptionalString(value, label) {
|
|
2308
|
-
if (value !== undefined && typeof value !== "string") {
|
|
2309
|
-
throw agentKitError("tool_validation_error", \`\${label} must be a string when provided.\`);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
function resolveToolSecrets(tool, env) {
|
|
2314
|
-
const secrets = {};
|
|
2315
|
-
|
|
2316
|
-
for (const secretName of tool.secrets ?? []) {
|
|
2317
|
-
if (!env[secretName]) {
|
|
2318
|
-
throw agentKitError("tool_secret_missing", \`Tool "\${tool.name}" requires missing secret "\${secretName}".\`);
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
secrets[secretName] = env[secretName];
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
return secrets;
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
async function runToolWithTimeout(tool, input, context, timeoutMs) {
|
|
2328
|
-
const controller = new AbortController();
|
|
2329
|
-
const parentSignal = context.signal;
|
|
2330
|
-
|
|
2331
|
-
if (parentSignal?.aborted) {
|
|
2332
|
-
throw agentKitError("runtime_aborted", \`Tool "\${tool.name}" was aborted before it started.\`);
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
2336
|
-
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
2337
|
-
|
|
2338
|
-
let timeout;
|
|
2339
|
-
|
|
2340
|
-
try {
|
|
2341
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2342
|
-
timeout = setTimeout(() => {
|
|
2343
|
-
controller.abort();
|
|
2344
|
-
reject(agentKitError("tool_timeout", \`Tool "\${tool.name}" timed out after \${timeoutMs}ms.\`));
|
|
2345
|
-
}, timeoutMs);
|
|
2346
|
-
});
|
|
2347
|
-
|
|
2348
|
-
const execution = Promise.resolve().then(() =>
|
|
2349
|
-
tool.execute(input, {
|
|
2350
|
-
secrets: context.secrets,
|
|
2351
|
-
signal: controller.signal,
|
|
2352
|
-
runtime: context.runtime,
|
|
2353
|
-
clock: context.clock,
|
|
2354
|
-
database: context.database,
|
|
2355
|
-
db: context.database,
|
|
2356
|
-
storage: {
|
|
2357
|
-
sql: context.database,
|
|
2358
|
-
},
|
|
2359
|
-
}),
|
|
2360
|
-
);
|
|
2361
|
-
|
|
2362
|
-
return await Promise.race([execution, timeoutPromise]);
|
|
2363
|
-
} finally {
|
|
2364
|
-
if (timeout) {
|
|
2365
|
-
clearTimeout(timeout);
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
function createHostedDatabaseRunner(env) {
|
|
2373
|
-
const run = async (sql, args = []) => {
|
|
2374
|
-
validateDatabaseQuery(sql, args);
|
|
2375
|
-
|
|
2376
|
-
const configError = requireTurso(env);
|
|
2377
|
-
|
|
2378
|
-
if (configError) {
|
|
2379
|
-
const payload = await configError.json().catch(() => null);
|
|
2380
|
-
const error = payload?.error;
|
|
2381
|
-
throw agentKitError(error?.code || "database_not_configured", error?.message || "Hosted database is not configured.");
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
return executeTursoSql(env, sql, args);
|
|
2385
|
-
};
|
|
2386
|
-
|
|
2387
|
-
run.execute = run;
|
|
2388
|
-
run.query = run;
|
|
2389
|
-
run.batch = async (statements) => {
|
|
2390
|
-
if (!Array.isArray(statements)) {
|
|
2391
|
-
throw agentKitError("database_query_invalid", "ctx.db.batch requires an array of statements.");
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
for (const [index, statement] of statements.entries()) {
|
|
2395
|
-
if (!isRecord(statement)) {
|
|
2396
|
-
throw agentKitError("database_query_invalid", \`ctx.db.batch statement \${index} must be an object.\`);
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
validateDatabaseQuery(statement.sql, statement.args ?? []);
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
const configError = requireTurso(env);
|
|
2403
|
-
|
|
2404
|
-
if (configError) {
|
|
2405
|
-
const payload = await configError.json().catch(() => null);
|
|
2406
|
-
const error = payload?.error;
|
|
2407
|
-
throw agentKitError(error?.code || "database_not_configured", error?.message || "Hosted database is not configured.");
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
return executeTursoBatch(env, statements);
|
|
2411
|
-
};
|
|
2412
|
-
run.transaction = async () => {
|
|
2413
|
-
throw agentKitError(
|
|
2414
|
-
"database_transaction_unsupported",
|
|
2415
|
-
"Hosted Cloudflare tools do not expose long-lived interactive transactions in v1. Use ctx.db.batch([...]) for atomic Turso pipeline operations.",
|
|
2416
|
-
);
|
|
2417
|
-
};
|
|
2418
|
-
return run;
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
function validateDatabaseQuery(sql, args) {
|
|
2422
|
-
if (typeof sql !== "string" || sql.trim().length === 0) {
|
|
2423
|
-
throw agentKitError("database_query_invalid", "Database SQL must be a non-empty string.");
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
if (!Array.isArray(args)) {
|
|
2427
|
-
throw agentKitError("database_query_invalid", "Database SQL args must be an array.");
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
for (const arg of args) {
|
|
2431
|
-
if (arg !== null && typeof arg !== "string" && typeof arg !== "number" && typeof arg !== "boolean") {
|
|
2432
|
-
throw agentKitError("database_query_invalid", "Database SQL args may only contain strings, numbers, booleans, or null.");
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
function validateSchema(value, schema, label) {
|
|
2438
|
-
const error = validateValue(value, schema, "$");
|
|
2439
|
-
|
|
2440
|
-
if (error) {
|
|
2441
|
-
throw agentKitError("tool_validation_error", \`\${label} failed schema validation: \${error}\`);
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
function validateValue(value, schema, path) {
|
|
2446
|
-
if (Array.isArray(schema.enum) && !schema.enum.some((item) => Object.is(item, value))) {
|
|
2447
|
-
return \`\${path} must be one of \${JSON.stringify(schema.enum)}.\`;
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
2451
|
-
const errors = types.map((type) => validateValueForType(value, schema, type, path)).filter((error) => error !== null);
|
|
2452
|
-
|
|
2453
|
-
if (errors.length < types.length) {
|
|
2454
|
-
return null;
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
if (types.length > 1) {
|
|
2458
|
-
return \`\${path} must match one of these schema types: \${types.join(", ")}.\`;
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
return errors[0] ?? null;
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
function validateValueForType(value, schema, type, path) {
|
|
2465
|
-
switch (type) {
|
|
2466
|
-
case "object":
|
|
2467
|
-
return validateObject(value, schema, path);
|
|
2468
|
-
case "array":
|
|
2469
|
-
return validateArray(value, schema, path);
|
|
2470
|
-
case "string":
|
|
2471
|
-
return typeof value === "string" ? null : \`\${path} must be a string.\`;
|
|
2472
|
-
case "number":
|
|
2473
|
-
return typeof value === "number" && Number.isFinite(value) ? null : \`\${path} must be a number.\`;
|
|
2474
|
-
case "integer":
|
|
2475
|
-
return Number.isInteger(value) ? null : \`\${path} must be an integer.\`;
|
|
2476
|
-
case "boolean":
|
|
2477
|
-
return typeof value === "boolean" ? null : \`\${path} must be a boolean.\`;
|
|
2478
|
-
case "null":
|
|
2479
|
-
return value === null ? null : \`\${path} must be null.\`;
|
|
2480
|
-
default:
|
|
2481
|
-
return \`\${path} has unsupported schema type "\${type}".\`;
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
function validateObject(value, schema, path) {
|
|
2486
|
-
if (!isRecord(value)) {
|
|
2487
|
-
return \`\${path} must be an object.\`;
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
for (const property of schema.required ?? []) {
|
|
2491
|
-
if (!(property in value)) {
|
|
2492
|
-
return \`\${path}.\${property} is required.\`;
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
for (const [property, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
2497
|
-
if (property in value) {
|
|
2498
|
-
const error = validateValue(value[property], propertySchema, \`\${path}.\${property}\`);
|
|
2499
|
-
|
|
2500
|
-
if (error) {
|
|
2501
|
-
return error;
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
if (schema.additionalProperties === false) {
|
|
2507
|
-
const allowedProperties = new Set(Object.keys(schema.properties ?? {}));
|
|
2508
|
-
|
|
2509
|
-
for (const property of Object.keys(value)) {
|
|
2510
|
-
if (!allowedProperties.has(property)) {
|
|
2511
|
-
return \`\${path}.\${property} is not allowed.\`;
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
return null;
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
function validateArray(value, schema, path) {
|
|
2520
|
-
if (!Array.isArray(value)) {
|
|
2521
|
-
return \`\${path} must be an array.\`;
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
if (!schema.items) {
|
|
2525
|
-
return null;
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
for (const [index, item] of value.entries()) {
|
|
2529
|
-
const error = validateValue(item, schema.items, \`\${path}[\${index}]\`);
|
|
2530
|
-
|
|
2531
|
-
if (error) {
|
|
2532
|
-
return error;
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
return null;
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
function normalizeOptionalNulls(value, schema) {
|
|
2540
|
-
const types = schemaTypes(schema);
|
|
2541
|
-
|
|
2542
|
-
if (types.includes("object") && isRecord(value)) {
|
|
2543
|
-
const required = new Set(schema.required ?? []);
|
|
2544
|
-
const properties = schema.properties ?? {};
|
|
2545
|
-
const normalized = { ...value };
|
|
2546
|
-
|
|
2547
|
-
for (const [property, propertySchema] of Object.entries(properties)) {
|
|
2548
|
-
if (!(property in normalized)) {
|
|
2549
|
-
continue;
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
if (normalized[property] === null && !required.has(property) && !allowsNull(propertySchema)) {
|
|
2553
|
-
delete normalized[property];
|
|
2554
|
-
continue;
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
normalized[property] = normalizeOptionalNulls(normalized[property], propertySchema);
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
return normalized;
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
if (types.includes("array") && Array.isArray(value) && schema.items) {
|
|
2564
|
-
return value.map((item) => normalizeOptionalNulls(item, schema.items));
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
return value;
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
function schemaTypes(schema) {
|
|
2571
|
-
return Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
|
-
function allowsNull(schema) {
|
|
2575
|
-
return schemaTypes(schema).includes("null") || (Array.isArray(schema.enum) && schema.enum.some((item) => item === null));
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
async function authorizeChat(request, env, body) {
|
|
2579
|
-
return authorizeDeployAccess(request, env, body, {
|
|
2580
|
-
notConfiguredCode: "chat_access_not_configured",
|
|
2581
|
-
notConfiguredMessage: "Hosted chat requires a deploy access token gate.",
|
|
2582
|
-
});
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
async function authorizeConversationRead(request, env) {
|
|
2586
|
-
return authorizeDeployAccess(request, env, {}, {
|
|
2587
|
-
notConfiguredCode: "conversation_access_not_configured",
|
|
2588
|
-
notConfiguredMessage: "Hosted conversation reads require a deploy access token gate.",
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
async function authorizeDeployAccess(request, env, body, options) {
|
|
2593
|
-
const runtimeGate = await authorizeRuntimeGate(request, env, body);
|
|
2594
|
-
|
|
2595
|
-
if (runtimeGate) {
|
|
2596
|
-
if (runtimeGate === "authorized") {
|
|
2597
|
-
return null;
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
return runtimeGate;
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
const token = deployAccessToken(request, body);
|
|
2604
|
-
const acceptedTokens = [env.AGENTKIT_PRIVATE_TOKEN, env.AGENTKIT_ACCESS_TOKEN].filter(
|
|
2605
|
-
(value) => typeof value === "string" && value.length > 0,
|
|
2606
|
-
);
|
|
2607
|
-
|
|
2608
|
-
if (acceptedTokens.length === 0) {
|
|
2609
|
-
return Response.json(
|
|
2610
|
-
{
|
|
2611
|
-
error: {
|
|
2612
|
-
code: options.notConfiguredCode,
|
|
2613
|
-
message: options.notConfiguredMessage,
|
|
2614
|
-
},
|
|
2615
|
-
},
|
|
2616
|
-
{ status: 503 },
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
if (!token || !acceptedTokens.includes(token)) {
|
|
2621
|
-
return Response.json(
|
|
2622
|
-
{ error: { code: "unauthorized", message: "Missing or invalid deploy access token." } },
|
|
2623
|
-
{ status: 401 },
|
|
2624
|
-
);
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
return null;
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
async function authorizeRuntimeGate(request, env, body) {
|
|
2631
|
-
if (!env.AGENTKIT_CLOUD_API_URL || !env.AGENTKIT_DEPLOY_ID || !env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
2632
|
-
return null;
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
let response;
|
|
2636
|
-
const token = deployAccessToken(request, body);
|
|
2637
|
-
|
|
2638
|
-
try {
|
|
2639
|
-
response = await fetch(
|
|
2640
|
-
new URL(\`/v1/runtime/deploys/\${encodeURIComponent(env.AGENTKIT_DEPLOY_ID)}/authorize\`, env.AGENTKIT_CLOUD_API_URL).href,
|
|
2641
|
-
{
|
|
2642
|
-
method: "POST",
|
|
2643
|
-
headers: {
|
|
2644
|
-
"Authorization": \`Bearer \${env.AGENTKIT_RUNTIME_GATE_TOKEN}.\${token}\`,
|
|
2645
|
-
"Content-Type": "application/json",
|
|
2646
|
-
"X-AgentKit-Deploy-Access-Token": token,
|
|
2647
|
-
},
|
|
2648
|
-
body: JSON.stringify({
|
|
2649
|
-
token,
|
|
2650
|
-
}),
|
|
2651
|
-
},
|
|
2652
|
-
);
|
|
2653
|
-
} catch {
|
|
2654
|
-
return Response.json(
|
|
2655
|
-
{ error: { code: "runtime_gate_unavailable", message: "AgentKit runtime gate is temporarily unavailable." } },
|
|
2656
|
-
{ status: 503 },
|
|
2657
|
-
);
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
if (response.ok) {
|
|
2661
|
-
return "authorized";
|
|
2662
|
-
}
|
|
2663
|
-
|
|
2664
|
-
const payload = await response.json().catch(() => ({
|
|
2665
|
-
error: {
|
|
2666
|
-
code: "runtime_gate_failed",
|
|
2667
|
-
message: "AgentKit runtime gate rejected the request.",
|
|
2668
|
-
},
|
|
2669
|
-
}));
|
|
2670
|
-
return Response.json(payload, { status: response.status });
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
function deployAccessToken(request, body) {
|
|
2674
|
-
const authorization = request.headers.get("authorization");
|
|
2675
|
-
|
|
2676
|
-
if (authorization?.startsWith("Bearer ")) {
|
|
2677
|
-
return authorization.slice("Bearer ".length);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
const tokenHeader =
|
|
2681
|
-
request.headers.get("x-agentkit-access-token") ??
|
|
2682
|
-
request.headers.get("x-agentkit-deploy-access-token") ??
|
|
2683
|
-
"";
|
|
2684
|
-
const token = tokenHeader.startsWith("Bearer ") ? tokenHeader.slice("Bearer ".length) : tokenHeader;
|
|
2685
|
-
if (token.trim()) {
|
|
2686
|
-
return token.trim();
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
if (body && typeof body === "object") {
|
|
2690
|
-
const bodyToken = typeof body.access_token === "string" ? body.access_token : typeof body.accessToken === "string" ? body.accessToken : "";
|
|
2691
|
-
return bodyToken.trim();
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
return "";
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
function authorizeAdmin(request, env) {
|
|
2698
|
-
if (!env.AGENTKIT_PRIVATE_TOKEN) {
|
|
2699
|
-
return Response.json(
|
|
2700
|
-
{
|
|
2701
|
-
error: {
|
|
2702
|
-
code: "deploy_not_ready",
|
|
2703
|
-
message: "Privileged AgentKit endpoints require AGENTKIT_PRIVATE_TOKEN to be set as a Cloudflare secret.",
|
|
2704
|
-
},
|
|
2705
|
-
},
|
|
2706
|
-
{ status: 503 },
|
|
2707
|
-
);
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
const expected = \`Bearer \${env.AGENTKIT_PRIVATE_TOKEN}\`;
|
|
2711
|
-
if (request.headers.get("authorization") !== expected) {
|
|
2712
|
-
return Response.json(
|
|
2713
|
-
{ error: { code: "unauthorized", message: "Missing or invalid AgentKit admin token." } },
|
|
2714
|
-
{ status: 401 },
|
|
2715
|
-
);
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
return null;
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
async function persist(env, input) {
|
|
2722
|
-
if (!env.AGENTKIT_CONVERSATIONS) {
|
|
2723
|
-
return {};
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
const stub = hostedConversationStore(env);
|
|
2727
|
-
const response = await stub.fetch("https://agentkit.internal/messages", {
|
|
2728
|
-
method: "POST",
|
|
2729
|
-
body: JSON.stringify(input),
|
|
2730
|
-
});
|
|
2731
|
-
|
|
2732
|
-
if (!response.ok) {
|
|
2733
|
-
throw new Error(\`Could not persist conversation: \${response.status}\`);
|
|
2734
|
-
}
|
|
2735
|
-
|
|
2736
|
-
return response.json();
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
function queueRuntimeUsage(ctx, promise) {
|
|
2740
|
-
const safePromise = promise.catch(() => undefined);
|
|
2741
|
-
|
|
2742
|
-
if (ctx && typeof ctx.waitUntil === "function") {
|
|
2743
|
-
ctx.waitUntil(safePromise);
|
|
2744
|
-
return;
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
void safePromise;
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
async function sendRuntimeUsage(env, input) {
|
|
2751
|
-
if (!env.AGENTKIT_CLOUD_API_URL || !env.AGENTKIT_DEPLOY_ID || !env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
2752
|
-
return;
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
const usage = normalizeRuntimeUsage(input.usage);
|
|
2756
|
-
const metadata = isRecord(input.metadata) ? input.metadata : {};
|
|
2757
|
-
const body = {
|
|
2758
|
-
kind: input.kind || "chat_run",
|
|
2759
|
-
status: input.status || "succeeded",
|
|
2760
|
-
...(input.runId ? { run_id: String(input.runId) } : {}),
|
|
2761
|
-
...(input.conversationId ? { conversation_id: String(input.conversationId) } : {}),
|
|
2762
|
-
...(input.provider ? { provider: String(input.provider) } : {}),
|
|
2763
|
-
...(input.model ? { model: String(input.model) } : {}),
|
|
2764
|
-
...(usage.inputTokens !== undefined ? { input_tokens: usage.inputTokens } : {}),
|
|
2765
|
-
...(usage.outputTokens !== undefined ? { output_tokens: usage.outputTokens } : {}),
|
|
2766
|
-
...(usage.totalTokens !== undefined ? { total_tokens: usage.totalTokens } : {}),
|
|
2767
|
-
...(usage.cost && Object.keys(usage.cost).length > 0 ? { cost: usage.cost } : {}),
|
|
2768
|
-
...(usage.cost?.total !== undefined ? { cost_usd: usage.cost.total } : {}),
|
|
2769
|
-
metadata: sanitizeRuntimeUsageMetadata(metadata),
|
|
2770
|
-
};
|
|
2771
|
-
|
|
2772
|
-
const url = new URL(
|
|
2773
|
-
"/v1/runtime/deploys/" + encodeURIComponent(String(env.AGENTKIT_DEPLOY_ID)) + "/usage",
|
|
2774
|
-
env.AGENTKIT_CLOUD_API_URL,
|
|
2775
|
-
);
|
|
2776
|
-
await fetch(url.href, {
|
|
2777
|
-
method: "POST",
|
|
2778
|
-
headers: {
|
|
2779
|
-
Authorization: "Bearer " + env.AGENTKIT_RUNTIME_GATE_TOKEN,
|
|
2780
|
-
"Content-Type": "application/json",
|
|
2781
|
-
},
|
|
2782
|
-
body: JSON.stringify(body),
|
|
2783
|
-
});
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
function sanitizeRuntimeUsageMetadata(value) {
|
|
2787
|
-
const metadata = {};
|
|
2788
|
-
|
|
2789
|
-
for (const [key, raw] of Object.entries(value)) {
|
|
2790
|
-
if (!/^[A-Za-z0-9_.:-]{1,80}$/.test(key)) {
|
|
2791
|
-
continue;
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
if (typeof raw === "string") {
|
|
2795
|
-
metadata[key] = raw.length > 160 ? raw.slice(0, 160) : raw;
|
|
2796
|
-
} else if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
2797
|
-
metadata[key] = raw;
|
|
2798
|
-
} else if (typeof raw === "boolean") {
|
|
2799
|
-
metadata[key] = raw;
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
return metadata;
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
async function loadConversationMessages(env, conversationId) {
|
|
2807
|
-
if (!env.AGENTKIT_CONVERSATIONS || !conversationId) {
|
|
2808
|
-
return [];
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
const stub = hostedConversationStore(env);
|
|
2812
|
-
const response = await stub.fetch(
|
|
2813
|
-
\`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
|
|
2814
|
-
);
|
|
2815
|
-
|
|
2816
|
-
if (!response.ok) {
|
|
2817
|
-
return [];
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
const payload = await response.json();
|
|
2821
|
-
const messages = Array.isArray(payload.messages)
|
|
2822
|
-
? payload.messages.filter((message) =>
|
|
2823
|
-
message &&
|
|
2824
|
-
typeof message === "object" &&
|
|
2825
|
-
(message.role === "user" || message.role === "assistant") &&
|
|
2826
|
-
typeof message.content === "string"
|
|
2827
|
-
)
|
|
2828
|
-
: [];
|
|
2829
|
-
|
|
2830
|
-
if (messages.length > 0) {
|
|
2831
|
-
return messages;
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
|
-
const legacyStub = legacyConversationStore(env, conversationId);
|
|
2835
|
-
const legacyResponse = await legacyStub.fetch(
|
|
2836
|
-
\`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
|
|
2837
|
-
);
|
|
2838
|
-
|
|
2839
|
-
if (!legacyResponse.ok) {
|
|
2840
|
-
return [];
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
const legacyPayload = await legacyResponse.json();
|
|
2844
|
-
return Array.isArray(legacyPayload.messages)
|
|
2845
|
-
? legacyPayload.messages.filter((message) =>
|
|
2846
|
-
message &&
|
|
2847
|
-
typeof message === "object" &&
|
|
2848
|
-
(message.role === "user" || message.role === "assistant") &&
|
|
2849
|
-
typeof message.content === "string"
|
|
2850
|
-
)
|
|
2851
|
-
: [];
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
function hostedConversationStore(env) {
|
|
2855
|
-
const id = env.AGENTKIT_CONVERSATIONS.idFromName("agentkit-conversations");
|
|
2856
|
-
return env.AGENTKIT_CONVERSATIONS.get(id);
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
function legacyConversationStore(env, conversationId) {
|
|
2860
|
-
const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
|
|
2861
|
-
return env.AGENTKIT_CONVERSATIONS.get(id);
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
function normalizeConversationId(value) {
|
|
2865
|
-
if (value === undefined || value === null) {
|
|
2866
|
-
return null;
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
if (typeof value !== "string") {
|
|
2870
|
-
throw agentKitError("validation_error", "conversationId must be a non-empty string when provided.");
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
const conversationId = value.trim();
|
|
2874
|
-
if (!conversationId) {
|
|
2875
|
-
throw agentKitError("validation_error", "conversationId must be a non-empty string when provided.");
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
return conversationId;
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
function agentKitError(code, message) {
|
|
2882
|
-
const error = new Error(message);
|
|
2883
|
-
error.code = code;
|
|
2884
|
-
return error;
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
|
-
function hostedErrorResponse(error) {
|
|
2888
|
-
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error";
|
|
2889
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2890
|
-
const status =
|
|
2891
|
-
code === "tool_not_found" || code === "database_not_configured" || code === "files_not_configured"
|
|
2892
|
-
? 404
|
|
2893
|
-
: code === "constraint_conflict"
|
|
2894
|
-
? 409
|
|
2895
|
-
: code === "tool_validation_error" || code === "validation_error" || code === "database_query_invalid"
|
|
2896
|
-
? 400
|
|
2897
|
-
: code === "database_unavailable" || code === "database_transaction_unsupported"
|
|
2898
|
-
? 503
|
|
2899
|
-
: code === "database_schema_error"
|
|
2900
|
-
? 500
|
|
2901
|
-
: code === "provider_not_supported"
|
|
2902
|
-
? 501
|
|
2903
|
-
: code === "tool_secret_missing" || code === "secret_not_found" || code === "deploy_not_ready"
|
|
2904
|
-
? 503
|
|
2905
|
-
: 500;
|
|
2906
|
-
|
|
2907
|
-
return Response.json({ error: { code, message } }, { status });
|
|
2908
|
-
}
|
|
2909
|
-
|
|
2910
|
-
async function executeTursoSql(env, sql, args) {
|
|
2911
|
-
const results = await executeTursoPipeline(env, [
|
|
2912
|
-
{
|
|
2913
|
-
type: "execute",
|
|
2914
|
-
stmt: {
|
|
2915
|
-
sql,
|
|
2916
|
-
args: args.map(toTursoArg),
|
|
2917
|
-
},
|
|
2918
|
-
},
|
|
2919
|
-
]);
|
|
2920
|
-
|
|
2921
|
-
return results[0] ?? emptyDatabaseResult();
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
async function executeTursoBatch(env, statements) {
|
|
2925
|
-
const results = await executeTursoPipeline(
|
|
2926
|
-
env,
|
|
2927
|
-
statements.map((statement) => ({
|
|
2928
|
-
type: "execute",
|
|
2929
|
-
stmt: {
|
|
2930
|
-
sql: statement.sql,
|
|
2931
|
-
args: (statement.args ?? []).map(toTursoArg),
|
|
2932
|
-
},
|
|
2933
|
-
})),
|
|
2934
|
-
);
|
|
2935
|
-
|
|
2936
|
-
return results;
|
|
2937
|
-
}
|
|
2938
|
-
|
|
2939
|
-
async function executeTursoPipeline(env, executeRequests) {
|
|
2940
|
-
let response;
|
|
2941
|
-
|
|
2942
|
-
try {
|
|
2943
|
-
response = await fetch(toTursoBaseUrl(env.TURSO_DATABASE_URL) + "/v2/pipeline", {
|
|
2944
|
-
method: "POST",
|
|
2945
|
-
headers: {
|
|
2946
|
-
Authorization: \`Bearer \${env.TURSO_AUTH_TOKEN}\`,
|
|
2947
|
-
"Content-Type": "application/json",
|
|
2948
|
-
},
|
|
2949
|
-
body: JSON.stringify({
|
|
2950
|
-
requests: [...executeRequests, { type: "close" }],
|
|
2951
|
-
}),
|
|
2952
|
-
});
|
|
2953
|
-
} catch (error) {
|
|
2954
|
-
throw agentKitError("database_unavailable", \`Turso SQL over HTTP request failed before a response: \${error instanceof Error ? error.message : String(error)}\`);
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
const payload = await response.json().catch(() => null);
|
|
2958
|
-
|
|
2959
|
-
if (!response.ok) {
|
|
2960
|
-
throw mapTursoHttpError(response.status, payload);
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
const results = [];
|
|
2964
|
-
|
|
2965
|
-
for (const [index, result] of (payload?.results ?? []).entries()) {
|
|
2966
|
-
if (index >= executeRequests.length) {
|
|
2967
|
-
break;
|
|
2968
|
-
}
|
|
2969
|
-
|
|
2970
|
-
const error = result?.response?.error ?? result?.error;
|
|
2971
|
-
|
|
2972
|
-
if (error) {
|
|
2973
|
-
throw mapTursoSqlError(error);
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
results.push(normalizeTursoExecuteResult(result));
|
|
2977
|
-
}
|
|
2978
|
-
|
|
2979
|
-
return results;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
function normalizeTursoExecuteResult(payload) {
|
|
2983
|
-
const result = payload?.response?.result ?? payload?.results?.[0]?.response?.result;
|
|
2984
|
-
const columns = Array.isArray(result?.cols) ? result.cols.map((column) => String(column.name || "")) : [];
|
|
2985
|
-
const rows = Array.isArray(result?.rows)
|
|
2986
|
-
? result.rows.map((row) =>
|
|
2987
|
-
Object.fromEntries(row.map((value, index) => [columns[index] || \`column_\${index}\`, fromTursoValue(value)])),
|
|
2988
|
-
)
|
|
2989
|
-
: [];
|
|
2990
|
-
|
|
2991
|
-
return {
|
|
2992
|
-
rows,
|
|
2993
|
-
rowsAffected: Number(result?.affected_row_count ?? 0),
|
|
2994
|
-
lastInsertRowid: result?.last_insert_rowid ?? null,
|
|
2995
|
-
columns,
|
|
2996
|
-
};
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
function emptyDatabaseResult() {
|
|
3000
|
-
return {
|
|
3001
|
-
rows: [],
|
|
3002
|
-
rowsAffected: 0,
|
|
3003
|
-
lastInsertRowid: null,
|
|
3004
|
-
columns: [],
|
|
3005
|
-
};
|
|
3006
|
-
}
|
|
3007
|
-
|
|
3008
|
-
function mapTursoHttpError(status, payload) {
|
|
3009
|
-
if (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {
|
|
3010
|
-
return agentKitError("database_unavailable", \`Turso SQL over HTTP is unavailable (HTTP \${status}).\`);
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
return mapTursoSqlError(payload?.error ?? payload);
|
|
3014
|
-
}
|
|
3015
|
-
|
|
3016
|
-
function mapTursoSqlError(error) {
|
|
3017
|
-
const message = readTursoErrorMessage(error);
|
|
3018
|
-
|
|
3019
|
-
if (/constraint failed|unique constraint|primary key|foreign key|check constraint|SQLITE_CONSTRAINT/i.test(message)) {
|
|
3020
|
-
return agentKitError("constraint_conflict", "Database constraint conflict.");
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
if (/no such table|no such column|has no column named|schema|SQLITE_SCHEMA/i.test(message)) {
|
|
3024
|
-
return agentKitError("database_schema_error", \`Database schema error: \${message}\`);
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
|
-
if (/syntax error|incomplete input|SQLITE_ERROR|invalid sql/i.test(message)) {
|
|
3028
|
-
return agentKitError("database_query_invalid", \`Database query is invalid: \${message}\`);
|
|
3029
|
-
}
|
|
3030
|
-
|
|
3031
|
-
return agentKitError("database_unavailable", \`Turso SQL over HTTP failed: \${message}\`);
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
function readTursoErrorMessage(error) {
|
|
3035
|
-
if (!error) {
|
|
3036
|
-
return "unknown Turso error";
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
if (typeof error === "string") {
|
|
3040
|
-
return error;
|
|
3041
|
-
}
|
|
3042
|
-
|
|
3043
|
-
if (typeof error.message === "string") {
|
|
3044
|
-
return error.message;
|
|
3045
|
-
}
|
|
3046
|
-
|
|
3047
|
-
if (typeof error.code === "string") {
|
|
3048
|
-
return error.code;
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
return JSON.stringify(error);
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3054
|
-
function fromTursoValue(value) {
|
|
3055
|
-
if (!value || value.type === "null") {
|
|
3056
|
-
return null;
|
|
3057
|
-
}
|
|
3058
|
-
|
|
3059
|
-
if (value.type === "integer") {
|
|
3060
|
-
const numberValue = Number(value.value);
|
|
3061
|
-
return Number.isSafeInteger(numberValue) ? numberValue : String(value.value);
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
if (value.type === "float") {
|
|
3065
|
-
return Number(value.value);
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
if (value.type === "text" || value.type === "blob") {
|
|
3069
|
-
return String(value.value ?? "");
|
|
3070
|
-
}
|
|
3071
|
-
|
|
3072
|
-
return value.value ?? null;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
|
-
function requireTurso(env) {
|
|
3076
|
-
if (manifest.storage.database.driver !== "turso") {
|
|
3077
|
-
return Response.json({ error: { code: "database_not_configured", message: "Turso is not configured." } }, { status: 404 });
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
if (!env.TURSO_DATABASE_URL || !env.TURSO_AUTH_TOKEN) {
|
|
3081
|
-
return Response.json(
|
|
3082
|
-
{
|
|
3083
|
-
error: {
|
|
3084
|
-
code: "deploy_not_ready",
|
|
3085
|
-
message: "TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be set as Cloudflare secrets.",
|
|
3086
|
-
},
|
|
3087
|
-
},
|
|
3088
|
-
{ status: 503 },
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
return null;
|
|
3093
|
-
}
|
|
3094
|
-
|
|
3095
|
-
function toTursoBaseUrl(value) {
|
|
3096
|
-
return String(value).replace(/^libsql:\\/\\//, "https://").replace(/\\/$/, "");
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
function toTursoArg(value) {
|
|
3100
|
-
if (value === null || value === undefined) {
|
|
3101
|
-
return { type: "null" };
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
if (typeof value === "boolean") {
|
|
3105
|
-
return { type: "integer", value: value ? "1" : "0" };
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
if (typeof value === "number" && Number.isInteger(value)) {
|
|
3109
|
-
return { type: "integer", value: String(value) };
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3112
|
-
if (typeof value === "number") {
|
|
3113
|
-
return { type: "float", value: String(value) };
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
if (typeof value === "string") {
|
|
3117
|
-
return { type: "text", value };
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
throw agentKitError("database_query_invalid", "Database SQL args may only contain strings, numbers, booleans, or null.");
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
function objectKey(rawKey) {
|
|
3124
|
-
const decoded = decodeURIComponent(String(rawKey || "")).replace(/^\\/+/, "");
|
|
3125
|
-
|
|
3126
|
-
if (!decoded || decoded.includes("..")) {
|
|
3127
|
-
return null;
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
const prefix = manifest.storage.files.driver === "r2" ? manifest.storage.files.prefix : "";
|
|
3131
|
-
return [prefix, decoded].filter(Boolean).join("/").replace(/\\/+/g, "/");
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
function titleFromMessage(content) {
|
|
3135
|
-
const compact = content.split(/\\r?\\n/, 1)[0].replace(/\\s+/g, " ").trim();
|
|
3136
|
-
return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
|
-
function readStoredUsage(row) {
|
|
3140
|
-
const usage = {};
|
|
3141
|
-
const inputTokens = normalizeRuntimeInteger(row.input_tokens);
|
|
3142
|
-
const outputTokens = normalizeRuntimeInteger(row.output_tokens);
|
|
3143
|
-
const totalTokens = normalizeRuntimeInteger(row.total_tokens);
|
|
3144
|
-
const cost = normalizeRuntimeCost(parseStoredJson(row.cost_json, {}));
|
|
3145
|
-
|
|
3146
|
-
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3147
|
-
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3148
|
-
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3149
|
-
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3150
|
-
|
|
3151
|
-
return usage;
|
|
3152
|
-
}
|
|
3153
|
-
|
|
3154
|
-
function normalizeRuntimeUsage(value) {
|
|
3155
|
-
if (!isRecord(value)) {
|
|
3156
|
-
return {};
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
const usage = {};
|
|
3160
|
-
const inputTokens = normalizeRuntimeInteger(value.inputTokens ?? value.input_tokens);
|
|
3161
|
-
const outputTokens = normalizeRuntimeInteger(value.outputTokens ?? value.output_tokens);
|
|
3162
|
-
const totalTokens = normalizeRuntimeInteger(value.totalTokens ?? value.total_tokens);
|
|
3163
|
-
const cost = normalizeRuntimeCost(value.cost);
|
|
3164
|
-
|
|
3165
|
-
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3166
|
-
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3167
|
-
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3168
|
-
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3169
|
-
|
|
3170
|
-
return usage;
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
function normalizeRuntimeCost(value) {
|
|
3174
|
-
if (!isRecord(value)) {
|
|
3175
|
-
return {};
|
|
3176
|
-
}
|
|
3177
|
-
|
|
3178
|
-
const cost = {};
|
|
3179
|
-
|
|
3180
|
-
for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
3181
|
-
const normalized = normalizeRuntimeNumber(value[field]);
|
|
3182
|
-
|
|
3183
|
-
if (normalized !== undefined) {
|
|
3184
|
-
cost[field] = normalized;
|
|
3185
|
-
}
|
|
3186
|
-
}
|
|
3187
|
-
|
|
3188
|
-
return cost;
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
|
-
function normalizeRuntimeInteger(value) {
|
|
3192
|
-
const normalized = normalizeRuntimeNumber(value);
|
|
3193
|
-
return normalized !== undefined && Number.isInteger(normalized) ? normalized : undefined;
|
|
3194
|
-
}
|
|
3195
|
-
|
|
3196
|
-
function normalizeRuntimeNumber(value) {
|
|
3197
|
-
const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
3198
|
-
return typeof parsed === "number" && Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
|
-
function parseStoredJson(value, fallback) {
|
|
3202
|
-
if (typeof value !== "string") {
|
|
3203
|
-
return fallback;
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
try {
|
|
3207
|
-
return JSON.parse(value);
|
|
3208
|
-
} catch {
|
|
3209
|
-
return fallback;
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
|
-
`;
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
function renderWranglerConfig(agentName: string, files: AgentManifest["storage"]["files"]): string {
|
|
3216
|
-
const config: Record<string, unknown> = {
|
|
3217
|
-
$schema: "node_modules/wrangler/config-schema.json",
|
|
3218
|
-
name: `agentkit-${slugify(agentName)}`,
|
|
3219
|
-
main: "./worker.js",
|
|
3220
|
-
compatibility_date: CLOUDFLARE_COMPATIBILITY_DATE,
|
|
3221
|
-
compatibility_flags: [...CLOUDFLARE_COMPATIBILITY_FLAGS],
|
|
3222
|
-
observability: {
|
|
3223
|
-
enabled: true,
|
|
3224
|
-
},
|
|
3225
|
-
durable_objects: {
|
|
3226
|
-
bindings: [
|
|
3227
|
-
{
|
|
3228
|
-
name: "AGENTKIT_CONVERSATIONS",
|
|
3229
|
-
class_name: "AgentKitConversationStore",
|
|
3230
|
-
},
|
|
3231
|
-
],
|
|
3232
|
-
},
|
|
3233
|
-
migrations: [
|
|
3234
|
-
{
|
|
3235
|
-
tag: "v1",
|
|
3236
|
-
new_sqlite_classes: ["AgentKitConversationStore"],
|
|
3237
|
-
},
|
|
3238
|
-
],
|
|
3239
|
-
};
|
|
3240
|
-
|
|
3241
|
-
if (files.driver === "r2") {
|
|
3242
|
-
config.r2_buckets = [
|
|
3243
|
-
{
|
|
3244
|
-
binding: files.binding,
|
|
3245
|
-
bucket_name: files.bucket,
|
|
3246
|
-
},
|
|
3247
|
-
];
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
return `${JSON.stringify(
|
|
3251
|
-
config,
|
|
3252
|
-
null,
|
|
3253
|
-
2,
|
|
3254
|
-
)}\n`;
|
|
3255
|
-
}
|
|
3256
|
-
|
|
3257
|
-
function renderDurableObjectSchema(): string {
|
|
3258
|
-
return `CREATE TABLE IF NOT EXISTS conversations (
|
|
3259
|
-
id TEXT PRIMARY KEY,
|
|
3260
|
-
agent_name TEXT NOT NULL,
|
|
3261
|
-
title TEXT,
|
|
3262
|
-
created_at TEXT NOT NULL,
|
|
3263
|
-
updated_at TEXT NOT NULL
|
|
3264
|
-
);
|
|
3265
|
-
|
|
3266
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
3267
|
-
id TEXT PRIMARY KEY,
|
|
3268
|
-
conversation_id TEXT NOT NULL,
|
|
3269
|
-
role TEXT NOT NULL,
|
|
3270
|
-
content TEXT NOT NULL,
|
|
3271
|
-
created_at TEXT NOT NULL
|
|
3272
|
-
);
|
|
3273
|
-
|
|
3274
|
-
CREATE TABLE IF NOT EXISTS runs (
|
|
3275
|
-
id TEXT PRIMARY KEY,
|
|
3276
|
-
conversation_id TEXT NOT NULL,
|
|
3277
|
-
provider TEXT NOT NULL,
|
|
3278
|
-
model TEXT NOT NULL,
|
|
3279
|
-
status TEXT NOT NULL,
|
|
3280
|
-
error_code TEXT,
|
|
3281
|
-
error_message TEXT,
|
|
3282
|
-
input_tokens INTEGER,
|
|
3283
|
-
output_tokens INTEGER,
|
|
3284
|
-
total_tokens INTEGER,
|
|
3285
|
-
cost_json TEXT,
|
|
3286
|
-
created_at TEXT NOT NULL,
|
|
3287
|
-
completed_at TEXT
|
|
3288
|
-
);
|
|
3289
|
-
|
|
3290
|
-
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
3291
|
-
id TEXT PRIMARY KEY,
|
|
3292
|
-
run_id TEXT NOT NULL,
|
|
3293
|
-
conversation_id TEXT NOT NULL,
|
|
3294
|
-
tool_name TEXT NOT NULL,
|
|
3295
|
-
visibility TEXT NOT NULL,
|
|
3296
|
-
status TEXT NOT NULL,
|
|
3297
|
-
input_json TEXT NOT NULL,
|
|
3298
|
-
output_json TEXT,
|
|
3299
|
-
rendered_json TEXT,
|
|
3300
|
-
error_message TEXT,
|
|
3301
|
-
created_at TEXT NOT NULL,
|
|
3302
|
-
completed_at TEXT
|
|
3303
|
-
);
|
|
3304
|
-
`;
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
function slugify(value: string): string {
|
|
3308
|
-
const slug = value
|
|
3309
|
-
.toLowerCase()
|
|
3310
|
-
.replace(/[^a-z0-9-]+/g, "-")
|
|
3311
|
-
.replace(/^-+|-+$/g, "")
|
|
3312
|
-
.slice(0, 48);
|
|
3313
|
-
|
|
3314
|
-
return slug || "agent";
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
function sha256(source: string): string {
|
|
3318
|
-
return createHash("sha256").update(source).digest("hex");
|
|
3319
|
-
}
|