@andreprado/agentkit 0.1.0-alpha.16 → 0.1.0-alpha.17
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 -0
- package/docs/guides/add-channel.md +2 -0
- package/docs/guides/add-managed-composio.md +137 -0
- package/docs/guides/channels-production-handoff.md +1 -1
- package/docs/guides/connect-telegram.md +3 -1
- package/docs/guides/prepare-deploy.md +3 -1
- package/docs/llms-full.txt +13 -1
- package/docs/llms.txt +7 -0
- package/package.json +1 -1
- package/src/cli/commands/channels.ts +119 -7
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/deploy-readiness.ts +59 -3
- package/src/cli/help.ts +15 -0
- package/src/cli/index.ts +130 -0
- package/src/create-project.ts +4 -32
- package/src/index.ts +145 -0
- package/src/runtime/chat.ts +2 -1
- package/src/runtime/config.ts +129 -0
- package/src/runtime/core/manifest.ts +29 -2
- package/src/runtime/deploy-readiness.ts +31 -1
- package/src/runtime/inspect.ts +108 -4
- package/src/runtime/integrations/composio.ts +257 -0
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +160 -5
- package/src/runtime/tool-runner.ts +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +2 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +3 -1
- package/src/templates/skills/agentkit-channels/references/telegram.md +3 -1
- package/src/templates/skills/agentkit-deploy/SKILL.md +1 -1
- package/src/templates/skills/agentkit-integrations/SKILL.md +66 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +3 -2
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { AgentConfig, AgentTool, ComposioManagedIntegration } from "../../index";
|
|
2
|
+
import { AgentKitError } from "../errors";
|
|
3
|
+
|
|
4
|
+
export const MANAGED_COMPOSIO_TOOL_NAME = "agentkit_composio_execute";
|
|
5
|
+
export const MANAGED_COMPOSIO_API_KEY_SECRET = "COMPOSIO_API_KEY";
|
|
6
|
+
export const MANAGED_COMPOSIO_WEBHOOK_SECRET = "COMPOSIO_WEBHOOK_SECRET";
|
|
7
|
+
export const MANAGED_COMPOSIO_ENTITLEMENT = "managed_composio";
|
|
8
|
+
export const MANAGED_COMPOSIO_USER_ID_BINDING = "AGENTKIT_MANAGED_COMPOSIO_USER_ID";
|
|
9
|
+
|
|
10
|
+
export type ResolvedComposioManagedIntegration = ComposioManagedIntegration & {
|
|
11
|
+
userId: string;
|
|
12
|
+
allowedTools: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ManagedComposioResolutionOptions = {
|
|
16
|
+
userId?: string;
|
|
17
|
+
userIdScope?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ManagedComposioToolInput = {
|
|
21
|
+
toolSlug: string;
|
|
22
|
+
arguments: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ManagedComposioToolOutput = {
|
|
26
|
+
successful: boolean;
|
|
27
|
+
data: unknown;
|
|
28
|
+
error: unknown;
|
|
29
|
+
logId: string | null;
|
|
30
|
+
toolkit: string;
|
|
31
|
+
toolSlug: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function resolveManagedComposioIntegrations(
|
|
36
|
+
config: Pick<AgentConfig, "name" | "integrations">,
|
|
37
|
+
options: ManagedComposioResolutionOptions = {},
|
|
38
|
+
): ResolvedComposioManagedIntegration[] {
|
|
39
|
+
return (config.integrations ?? [])
|
|
40
|
+
.filter((integration): integration is ComposioManagedIntegration => integration.provider === "composio" && integration.mode === "managed")
|
|
41
|
+
.map((integration) => ({
|
|
42
|
+
...integration,
|
|
43
|
+
userId: options.userId ?? managedComposioUserId(config.name, options.userIdScope),
|
|
44
|
+
allowedTools: allowedComposioTools(integration),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hasManagedComposioIntegration(config: Pick<AgentConfig, "name" | "integrations">): boolean {
|
|
49
|
+
return resolveManagedComposioIntegrations(config).length > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function managedComposioRequiredSecrets(config: Pick<AgentConfig, "name" | "integrations">): string[] {
|
|
53
|
+
return hasManagedComposioIntegration(config) ? [MANAGED_COMPOSIO_API_KEY_SECRET] : [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createManagedComposioTools(
|
|
57
|
+
config: Pick<AgentConfig, "name" | "integrations">,
|
|
58
|
+
options: ManagedComposioResolutionOptions = {},
|
|
59
|
+
): AgentTool[] {
|
|
60
|
+
const integrations = resolveManagedComposioIntegrations(config, options);
|
|
61
|
+
|
|
62
|
+
return integrations.flatMap((integration) => {
|
|
63
|
+
if (integration.allowedTools.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const toolkitByTool = new Map<string, string>();
|
|
68
|
+
|
|
69
|
+
for (const [toolkit, tools] of Object.entries(integration.tools ?? {})) {
|
|
70
|
+
for (const tool of tools) {
|
|
71
|
+
toolkitByTool.set(tool, toolkit);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const tool: AgentTool<ManagedComposioToolInput, ManagedComposioToolOutput> = {
|
|
76
|
+
name: MANAGED_COMPOSIO_TOOL_NAME,
|
|
77
|
+
description:
|
|
78
|
+
"Executes one explicitly allowed AgentKit-managed Composio action for this deployed agent. Use only for configured connected apps.",
|
|
79
|
+
visibility: "user",
|
|
80
|
+
secrets: [MANAGED_COMPOSIO_API_KEY_SECRET],
|
|
81
|
+
permissions: integration.allowedTools.map((toolName) => `composio:${toolName.toLowerCase()}`),
|
|
82
|
+
timeoutMs: 60_000,
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
toolSlug: {
|
|
87
|
+
type: "string",
|
|
88
|
+
enum: integration.allowedTools,
|
|
89
|
+
description: "Allowed Composio action slug to execute.",
|
|
90
|
+
},
|
|
91
|
+
arguments: {
|
|
92
|
+
type: "object",
|
|
93
|
+
additionalProperties: true,
|
|
94
|
+
description: "Structured arguments for the selected Composio action.",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["toolSlug", "arguments"],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
outputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
successful: { type: "boolean" },
|
|
104
|
+
data: { type: ["object", "array", "string", "number", "boolean", "null"] },
|
|
105
|
+
error: { type: ["object", "string", "null"] },
|
|
106
|
+
logId: { type: ["string", "null"] },
|
|
107
|
+
toolkit: { type: "string" },
|
|
108
|
+
toolSlug: { type: "string" },
|
|
109
|
+
userId: { type: "string" },
|
|
110
|
+
},
|
|
111
|
+
required: ["successful", "toolkit", "toolSlug", "userId"],
|
|
112
|
+
additionalProperties: true,
|
|
113
|
+
},
|
|
114
|
+
async execute(input, ctx) {
|
|
115
|
+
const toolkit = toolkitByTool.get(input.toolSlug);
|
|
116
|
+
|
|
117
|
+
if (!toolkit || !integration.allowedTools.includes(input.toolSlug)) {
|
|
118
|
+
throw new AgentKitError(
|
|
119
|
+
"composio_tool_not_allowed",
|
|
120
|
+
`Composio action "${input.toolSlug}" is not allowed by this agent's managed Composio settings.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = await executeComposioTool({
|
|
125
|
+
apiKey: ctx.secrets[MANAGED_COMPOSIO_API_KEY_SECRET],
|
|
126
|
+
userId: integration.userId,
|
|
127
|
+
toolSlug: input.toolSlug,
|
|
128
|
+
arguments: input.arguments,
|
|
129
|
+
signal: ctx.signal,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
successful: result.successful,
|
|
134
|
+
data: result.data ?? null,
|
|
135
|
+
error: result.error ?? null,
|
|
136
|
+
logId: result.log_id ?? null,
|
|
137
|
+
toolkit,
|
|
138
|
+
toolSlug: input.toolSlug,
|
|
139
|
+
userId: integration.userId,
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
render(output) {
|
|
143
|
+
if (output.successful) {
|
|
144
|
+
return `Composio action ${output.toolSlug} completed.`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
text: `Composio action ${output.toolSlug} did not complete successfully.`,
|
|
149
|
+
blocks: [
|
|
150
|
+
{
|
|
151
|
+
type: "card",
|
|
152
|
+
title: "Composio connection or action issue",
|
|
153
|
+
subtitle: typeof output.error === "string" ? output.error : undefined,
|
|
154
|
+
fields: [
|
|
155
|
+
{ label: "Toolkit", value: output.toolkit },
|
|
156
|
+
{ label: "Action", value: output.toolSlug },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return [tool];
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function managedComposioUserId(agentName: string, scope = "local"): string {
|
|
169
|
+
return `ak_agent_${stableHash(`${scope}:${agentName}`).slice(0, 24)}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function allowedComposioTools(integration: ComposioManagedIntegration): string[] {
|
|
173
|
+
return Array.from(new Set(Object.values(integration.tools ?? {}).flat())).sort();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function executeComposioTool(input: {
|
|
177
|
+
apiKey: string | undefined;
|
|
178
|
+
userId: string;
|
|
179
|
+
toolSlug: string;
|
|
180
|
+
arguments: Record<string, unknown>;
|
|
181
|
+
signal: AbortSignal;
|
|
182
|
+
}): Promise<{ successful: boolean; data?: unknown; error?: unknown; log_id?: string }> {
|
|
183
|
+
if (!input.apiKey) {
|
|
184
|
+
throw new AgentKitError("composio_secret_missing", "AgentKit-managed Composio requires COMPOSIO_API_KEY at runtime.");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const response = await fetch(
|
|
188
|
+
`https://backend.composio.dev/api/v3.1/tools/execute/${encodeURIComponent(input.toolSlug)}`,
|
|
189
|
+
{
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: {
|
|
192
|
+
"content-type": "application/json",
|
|
193
|
+
"x-api-key": input.apiKey,
|
|
194
|
+
},
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
user_id: input.userId,
|
|
197
|
+
arguments: input.arguments,
|
|
198
|
+
}),
|
|
199
|
+
signal: input.signal,
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
const payload = await response.json().catch(() => null);
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new AgentKitError(
|
|
206
|
+
"composio_execution_failed",
|
|
207
|
+
`Composio action ${input.toolSlug} failed with HTTP ${response.status}: ${readComposioError(payload)}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!isRecord(payload)) {
|
|
212
|
+
return { successful: true, data: payload };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
successful: payload.successful !== false,
|
|
217
|
+
data: payload.data,
|
|
218
|
+
error: payload.error,
|
|
219
|
+
log_id: typeof payload.log_id === "string" ? payload.log_id : undefined,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function readComposioError(payload: unknown): string {
|
|
224
|
+
if (isRecord(payload)) {
|
|
225
|
+
if (typeof payload.error === "string") {
|
|
226
|
+
return payload.error;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (isRecord(payload.error) && typeof payload.error.message === "string") {
|
|
230
|
+
return payload.error.message;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return "unknown error";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function stableHash(value: string): string {
|
|
238
|
+
let first = 2166136261;
|
|
239
|
+
let second = 0x811c9dc5;
|
|
240
|
+
let third = 0x01000193;
|
|
241
|
+
|
|
242
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
243
|
+
const code = value.charCodeAt(index);
|
|
244
|
+
first ^= code;
|
|
245
|
+
first = Math.imul(first, 16777619);
|
|
246
|
+
second ^= code + index;
|
|
247
|
+
second = Math.imul(second, 1597334677);
|
|
248
|
+
third ^= code * (index + 1);
|
|
249
|
+
third = Math.imul(third, 2246822519);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return [first, second, third].map((hash) => (hash >>> 0).toString(16).padStart(8, "0")).join("");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
256
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
257
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export type DefaultSkillFile = {
|
|
6
|
+
path: string;
|
|
7
|
+
contents: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type DefaultSkillStatus = {
|
|
11
|
+
status: "current" | "missing" | "outdated";
|
|
12
|
+
checked: number;
|
|
13
|
+
missing: string[];
|
|
14
|
+
outdated: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
export async function syncDefaultSkillFiles(projectPath: string): Promise<{ files: string[] }> {
|
|
20
|
+
const files = await loadDefaultSkillFiles();
|
|
21
|
+
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const absolutePath = join(projectPath, file.path);
|
|
24
|
+
|
|
25
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
26
|
+
await writeFile(absolutePath, file.contents);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { files: files.map((file) => file.path) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function checkDefaultSkillStatus(projectPath: string): Promise<DefaultSkillStatus> {
|
|
33
|
+
const files = await loadDefaultSkillFiles();
|
|
34
|
+
const missing: string[] = [];
|
|
35
|
+
const outdated: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const absolutePath = join(projectPath, file.path);
|
|
39
|
+
|
|
40
|
+
if (!(await pathExists(absolutePath))) {
|
|
41
|
+
missing.push(file.path);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const current = await readFile(absolutePath, "utf8");
|
|
46
|
+
if (current !== file.contents) {
|
|
47
|
+
outdated.push(file.path);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status: missing.length > 0 ? "missing" : outdated.length > 0 ? "outdated" : "current",
|
|
53
|
+
checked: files.length,
|
|
54
|
+
missing,
|
|
55
|
+
outdated,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function loadDefaultSkillFiles(): Promise<DefaultSkillFile[]> {
|
|
60
|
+
const sourceRoot = join(moduleDir, "..", "templates", "skills");
|
|
61
|
+
const sourceFiles = await listFiles(sourceRoot);
|
|
62
|
+
|
|
63
|
+
return Promise.all(
|
|
64
|
+
sourceFiles.map(async (sourcePath) => ({
|
|
65
|
+
path: join("skills", relative(sourceRoot, sourcePath)).replaceAll("\\", "/"),
|
|
66
|
+
contents: await readFile(sourcePath, "utf8"),
|
|
67
|
+
})),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function listFiles(directory: string): Promise<string[]> {
|
|
72
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
73
|
+
const files = await Promise.all(
|
|
74
|
+
entries.map(async (entry) => {
|
|
75
|
+
const path = join(directory, entry.name);
|
|
76
|
+
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
return listFiles(path);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return entry.isFile() ? [path] : [];
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return files.flat().sort();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
89
|
+
try {
|
|
90
|
+
await stat(path);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -36,6 +36,7 @@ export type AgentManifest = {
|
|
|
36
36
|
prompt: SharedAgentManifest["prompt"];
|
|
37
37
|
secrets: SharedAgentManifest["secrets"];
|
|
38
38
|
tools: SharedAgentManifest["tools"];
|
|
39
|
+
integrations: SharedAgentManifest["integrations"];
|
|
39
40
|
channels: SharedAgentManifest["channels"];
|
|
40
41
|
transcription: SharedAgentManifest["transcription"];
|
|
41
42
|
knowledge: SharedAgentManifest["knowledge"];
|
|
@@ -198,6 +199,7 @@ function buildManifest(
|
|
|
198
199
|
prompt: shared.prompt,
|
|
199
200
|
secrets: shared.secrets,
|
|
200
201
|
tools: shared.tools,
|
|
202
|
+
integrations: shared.integrations,
|
|
201
203
|
channels: shared.channels,
|
|
202
204
|
transcription: shared.transcription,
|
|
203
205
|
knowledge: shared.knowledge,
|
|
@@ -383,6 +385,23 @@ export function defineTool(tool) {
|
|
|
383
385
|
return tool;
|
|
384
386
|
}
|
|
385
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
|
+
|
|
394
|
+
return {
|
|
395
|
+
type: "composio",
|
|
396
|
+
provider: "composio",
|
|
397
|
+
mode: "managed",
|
|
398
|
+
name,
|
|
399
|
+
toolkits,
|
|
400
|
+
...(tools ? { tools } : {}),
|
|
401
|
+
...(tags ? { tags } : {}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
386
405
|
export function websiteChannel(input) {
|
|
387
406
|
return {
|
|
388
407
|
type: "website",
|
|
@@ -440,6 +459,105 @@ function normalizeChannelSecrets(secrets, context) {
|
|
|
440
459
|
return Array.from(uniqueSecrets).sort();
|
|
441
460
|
}
|
|
442
461
|
|
|
462
|
+
function normalizeIntegrationName(value, context) {
|
|
463
|
+
const name = value.trim();
|
|
464
|
+
|
|
465
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
466
|
+
throw new Error(\`\${context} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return name;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function normalizeToolkitList(values, context) {
|
|
473
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
474
|
+
throw new Error(\`\${context} must include at least one toolkit slug.\`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const toolkits = new Set();
|
|
478
|
+
|
|
479
|
+
for (const value of values) {
|
|
480
|
+
if (typeof value !== "string") {
|
|
481
|
+
throw new Error(\`\${context} entries must be toolkit slug strings.\`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const toolkit = value.trim().toLowerCase();
|
|
485
|
+
|
|
486
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(toolkit)) {
|
|
487
|
+
throw new Error(\`\${context} entries must start with a letter and contain only letters, numbers, underscores, and hyphens.\`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
toolkits.add(toolkit);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return Array.from(toolkits).sort();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function normalizeComposioToolMap(values, toolkits) {
|
|
497
|
+
const toolkitSet = new Set(toolkits);
|
|
498
|
+
const normalized = {};
|
|
499
|
+
|
|
500
|
+
for (const [rawToolkit, rawTools] of Object.entries(values)) {
|
|
501
|
+
const toolkit = rawToolkit.trim().toLowerCase();
|
|
502
|
+
|
|
503
|
+
if (!toolkitSet.has(toolkit)) {
|
|
504
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} must reference a toolkit listed in composioManaged.toolkits.\`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
508
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} must include at least one Composio action slug.\`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const tools = new Set();
|
|
512
|
+
|
|
513
|
+
for (const rawTool of rawTools) {
|
|
514
|
+
if (typeof rawTool !== "string") {
|
|
515
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be Composio action slug strings.\`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const tool = rawTool.trim().toUpperCase();
|
|
519
|
+
|
|
520
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(tool)) {
|
|
521
|
+
throw new Error(\`composioManaged.tools.\${rawToolkit} entries must be uppercase Composio action slugs.\`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
tools.add(tool);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
normalized[toolkit] = Array.from(tools).sort();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function normalizeIntegrationTags(tags) {
|
|
534
|
+
const normalized = {};
|
|
535
|
+
|
|
536
|
+
if (tags.enable !== undefined) {
|
|
537
|
+
normalized.enable = normalizeTagList(tags.enable, "composioManaged.tags.enable");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (tags.disable !== undefined) {
|
|
541
|
+
normalized.disable = normalizeTagList(tags.disable, "composioManaged.tags.disable");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return normalized;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function normalizeTagList(values, context) {
|
|
548
|
+
if (!Array.isArray(values)) {
|
|
549
|
+
throw new Error(\`\${context} must be an array of tag strings.\`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return Array.from(new Set(values.map((value) => {
|
|
553
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
554
|
+
throw new Error(\`\${context} entries must be non-empty strings.\`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return value.trim();
|
|
558
|
+
}))).sort();
|
|
559
|
+
}
|
|
560
|
+
|
|
443
561
|
function normalizeChannelBuffer(input, context) {
|
|
444
562
|
if (input.mode === "off") {
|
|
445
563
|
return { mode: "off" };
|
|
@@ -542,17 +660,18 @@ function renderCloudflareWorker(input: { manifest: AgentManifest; instructions:
|
|
|
542
660
|
const instructionsJson = JSON.stringify(input.instructions);
|
|
543
661
|
const configImportPath = JSON.stringify(input.configPath);
|
|
544
662
|
const providerImportPath = JSON.stringify(resolve(moduleDir, "../../../providers/index.ts"));
|
|
663
|
+
const composioIntegrationImportPath = JSON.stringify(resolve(moduleDir, "../../integrations/composio.ts"));
|
|
545
664
|
const promptContextImportPath = JSON.stringify(resolve(moduleDir, "../../prompt-context.ts"));
|
|
546
665
|
|
|
547
666
|
return `import { DurableObject } from "cloudflare:workers";
|
|
548
667
|
import agentConfig from ${configImportPath};
|
|
549
668
|
import { getProviderAdapter } from ${providerImportPath};
|
|
669
|
+
import { MANAGED_COMPOSIO_USER_ID_BINDING, createManagedComposioTools } from ${composioIntegrationImportPath};
|
|
550
670
|
import { appendRuntimePromptContext, buildRuntimeClockContext } from ${promptContextImportPath};
|
|
551
671
|
|
|
552
672
|
const manifest = ${manifestJson};
|
|
553
673
|
const instructions = ${instructionsJson};
|
|
554
674
|
const hostedKnowledgeTool = manifest.knowledge.enabled ? createHostedKnowledgeSearchTool() : null;
|
|
555
|
-
const hostedTools = hostedKnowledgeTool ? [hostedKnowledgeTool, ...(agentConfig.tools ?? [])] : (agentConfig.tools ?? []);
|
|
556
675
|
const DEFAULT_TOOL_TIMEOUT_MS = 30000;
|
|
557
676
|
|
|
558
677
|
export class AgentKitConversationStore extends DurableObject {
|
|
@@ -721,13 +840,16 @@ export default {
|
|
|
721
840
|
return auth;
|
|
722
841
|
}
|
|
723
842
|
|
|
843
|
+
const hostedTools = createHostedTools(env);
|
|
844
|
+
|
|
724
845
|
return Response.json({
|
|
725
846
|
agent: manifest.name,
|
|
726
847
|
runtime: manifest.runtime,
|
|
727
848
|
target: manifest.target,
|
|
728
849
|
provider: manifest.provider,
|
|
729
850
|
prompt: manifest.prompt,
|
|
730
|
-
tools:
|
|
851
|
+
tools: hostedTools.map((tool) => tool.name),
|
|
852
|
+
integrations: hostedIntegrations(env),
|
|
731
853
|
channels: manifest.channels.map((channel) => ({
|
|
732
854
|
name: channel.name,
|
|
733
855
|
type: channel.type,
|
|
@@ -1129,6 +1251,7 @@ function isRecord(value) {
|
|
|
1129
1251
|
|
|
1130
1252
|
async function callHostedProvider(env, messages, signal) {
|
|
1131
1253
|
const adapter = getProviderAdapter(manifest.provider);
|
|
1254
|
+
const hostedTools = createHostedTools(env);
|
|
1132
1255
|
const clock = buildRuntimeClockContext({
|
|
1133
1256
|
timeZone: manifest.timeZone,
|
|
1134
1257
|
env,
|
|
@@ -1626,18 +1749,49 @@ function inspectHostedDatabase() {
|
|
|
1626
1749
|
}
|
|
1627
1750
|
|
|
1628
1751
|
function hostedManagedSecrets() {
|
|
1629
|
-
|
|
1630
|
-
|
|
1752
|
+
const secrets = [];
|
|
1753
|
+
|
|
1754
|
+
if (manifest.storage.database.driver === "turso") {
|
|
1755
|
+
secrets.push("TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL");
|
|
1631
1756
|
}
|
|
1632
1757
|
|
|
1633
|
-
|
|
1758
|
+
if (manifest.integrations.some((integration) => integration.provider === "composio" && integration.mode === "managed")) {
|
|
1759
|
+
secrets.push("COMPOSIO_API_KEY");
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
return secrets.sort();
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function createHostedTools(env) {
|
|
1766
|
+
return [
|
|
1767
|
+
...(hostedKnowledgeTool ? [hostedKnowledgeTool] : []),
|
|
1768
|
+
...createManagedComposioTools(agentConfig, { userId: env[MANAGED_COMPOSIO_USER_ID_BINDING] }),
|
|
1769
|
+
...(agentConfig.tools ?? []),
|
|
1770
|
+
];
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function hostedIntegrations(env) {
|
|
1774
|
+
const userId = env[MANAGED_COMPOSIO_USER_ID_BINDING];
|
|
1775
|
+
|
|
1776
|
+
if (!userId) {
|
|
1777
|
+
return manifest.integrations;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
return manifest.integrations.map((integration) =>
|
|
1781
|
+
integration.provider === "composio" && integration.mode === "managed"
|
|
1782
|
+
? { ...integration, userId }
|
|
1783
|
+
: integration,
|
|
1784
|
+
);
|
|
1634
1785
|
}
|
|
1635
1786
|
|
|
1636
1787
|
function createHostedProviderToolRuntime(env, signal, clock) {
|
|
1788
|
+
const hostedTools = createHostedTools(env);
|
|
1789
|
+
|
|
1637
1790
|
return {
|
|
1638
1791
|
definitions: hostedTools,
|
|
1639
1792
|
execute(request) {
|
|
1640
1793
|
return executeHostedTool({
|
|
1794
|
+
tools: hostedTools,
|
|
1641
1795
|
name: request.name,
|
|
1642
1796
|
input: request.input,
|
|
1643
1797
|
env,
|
|
@@ -1650,6 +1804,7 @@ function createHostedProviderToolRuntime(env, signal, clock) {
|
|
|
1650
1804
|
}
|
|
1651
1805
|
|
|
1652
1806
|
async function executeHostedTool(request) {
|
|
1807
|
+
const hostedTools = request.tools ?? createHostedTools(request.env);
|
|
1653
1808
|
const tool = hostedTools.find((candidate) => candidate.name === request.name);
|
|
1654
1809
|
const id = crypto.randomUUID();
|
|
1655
1810
|
|
|
@@ -2,6 +2,7 @@ import { loadAgentCapsule } from "./config";
|
|
|
2
2
|
import { openPreparedCapsuleStore } from "./database";
|
|
3
3
|
import { loadCapsuleEnv } from "./env";
|
|
4
4
|
import { isAgentKitError } from "./errors";
|
|
5
|
+
import { createManagedComposioTools } from "./integrations/composio";
|
|
5
6
|
import { buildRuntimeClockContext } from "./prompt-context";
|
|
6
7
|
import { createToolRuntime, type ToolCallResult } from "./tools";
|
|
7
8
|
|
|
@@ -26,7 +27,7 @@ export async function runToolFromCwd(cwd: string, options: RunToolOptions): Prom
|
|
|
26
27
|
model: capsule.config.provider.model,
|
|
27
28
|
});
|
|
28
29
|
const toolRuntime = createToolRuntime({
|
|
29
|
-
tools: capsule.config.tools ?? [],
|
|
30
|
+
tools: [...createManagedComposioTools(capsule.config), ...(capsule.config.tools ?? [])],
|
|
30
31
|
store,
|
|
31
32
|
runId: run.id,
|
|
32
33
|
conversationId: conversation.id,
|
|
@@ -19,6 +19,7 @@ Use this first inside an AgentKit Agent Capsule.
|
|
|
19
19
|
- Build or reshape the agent from the owner's brief: `skills/agentkit-build-agent/SKILL.md`
|
|
20
20
|
- Edit prompts: `skills/agentkit-prompts/SKILL.md`
|
|
21
21
|
- Add actions or external data: `skills/agentkit-tools/SKILL.md`
|
|
22
|
+
- Add AgentKit-managed integrations such as managed Composio: `skills/agentkit-integrations/SKILL.md`
|
|
22
23
|
- Add database tables or database-backed tools: `skills/agentkit-database/SKILL.md`
|
|
23
24
|
- Add docs, FAQs, prices, policies, or CSV facts: `skills/agentkit-knowledge/SKILL.md`
|
|
24
25
|
- Switch from `test/fake` to a real model provider: `skills/agentkit-provider/SKILL.md`
|
|
@@ -80,6 +80,8 @@ npm run agentkit -- channels connect telegram support-telegram
|
|
|
80
80
|
npm run agentkit -- channels add whatsapp support-whatsapp --provider zapster
|
|
81
81
|
npm run agentkit -- channels doctor support-telegram
|
|
82
82
|
npm run agentkit -- channels test support-telegram --message "hello"
|
|
83
|
+
npm run agentkit -- channels test-audio support-telegram --fixture voice-note
|
|
84
|
+
npm run agentkit -- transcribe smoke --provider groq
|
|
83
85
|
npm run agentkit -- channels deliveries list support-telegram
|
|
84
86
|
```
|
|
85
87
|
|
|
@@ -7,6 +7,8 @@ agentkit channels list
|
|
|
7
7
|
agentkit channels status <name>
|
|
8
8
|
agentkit channels doctor <name>
|
|
9
9
|
agentkit channels test <name> --message "hello"
|
|
10
|
+
agentkit channels test-audio <name> --fixture voice-note
|
|
11
|
+
agentkit transcribe smoke --provider groq
|
|
10
12
|
agentkit channels deliveries list <name> --since 24h
|
|
11
13
|
agentkit channels deliveries show <delivery-id>
|
|
12
14
|
agentkit channels buffers list <name>
|
|
@@ -42,7 +44,7 @@ skipped
|
|
|
42
44
|
|
|
43
45
|
Common errors:
|
|
44
46
|
|
|
45
|
-
- `channel_not_found`: webhook URL points to an unknown channel.
|
|
47
|
+
- `channel_not_found`: webhook URL points to an unknown channel. `channels test` is the official synthetic smoke and should resolve the current `.agentkit/deploy.json` channel.
|
|
46
48
|
- `channel_secret_missing`: required hosted secret is not set.
|
|
47
49
|
- `channel_signature_invalid`: webhook secret, token, or origin header mismatch.
|
|
48
50
|
- `channel_payload_invalid`: malformed or unsupported provider payload.
|
|
@@ -23,6 +23,8 @@ agentkit channels connect telegram support-telegram
|
|
|
23
23
|
agentkit channels doctor support-telegram
|
|
24
24
|
agentkit channels status support-telegram
|
|
25
25
|
agentkit channels test support-telegram --message "hello"
|
|
26
|
+
agentkit channels test-audio support-telegram --fixture voice-note
|
|
27
|
+
agentkit transcribe smoke --provider groq
|
|
26
28
|
agentkit channels deliveries list support-telegram
|
|
27
29
|
```
|
|
28
30
|
|
|
@@ -67,4 +69,4 @@ export default defineAgent({
|
|
|
67
69
|
});
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
AgentKit validates the Telegram webhook, normalizes `voice` and `audio` payloads, enqueues an audio job, then the retryable channel worker calls Telegram `getFile`, downloads the media with `TELEGRAM_BOT_TOKEN`, sends the bytes to the configured transcription provider, and runs the agent with transcript text. Telegram voice notes are usually OGG/Opus; use Groq in V1 for that path.
|
|
72
|
+
AgentKit validates the Telegram webhook, normalizes `voice` and `audio` payloads, enqueues an audio job, then the retryable channel worker calls Telegram `getFile`, downloads the media with `TELEGRAM_BOT_TOKEN`, sends the bytes to the configured transcription provider, and runs the agent with transcript text. Telegram voice notes are usually OGG/Opus; use Groq in V1 for that path. `test-audio` validates the audio channel ingress path; `transcribe smoke` validates the transcription provider separately.
|
|
@@ -18,6 +18,7 @@ Use this when the owner asks to prepare, test, or run hosted deploy.
|
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
npm run typecheck
|
|
21
|
+
npm run agentkit -- skills status
|
|
21
22
|
npm run agentkit -- inspect
|
|
22
23
|
npm run agentkit -- db migrate
|
|
23
24
|
npm run chat -- --message "hello"
|
|
@@ -41,4 +42,3 @@ Open the printed `Chat:` URL and report it to the owner.
|
|
|
41
42
|
## Production Handoff
|
|
42
43
|
|
|
43
44
|
Report changed files, required env/secret names, database schema changes, deploy order, smoke checks, rollback concerns, and whether the provider was still `test/fake`.
|
|
44
|
-
|