@andreprado/agentkit 0.1.0-alpha.9 → 0.1.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 +18 -1
- package/docs/guides/add-channel.md +251 -7
- package/docs/guides/add-knowledge.md +10 -0
- package/docs/guides/add-managed-composio.md +165 -0
- package/docs/guides/add-tool.md +10 -3
- package/docs/guides/channel-security.md +162 -32
- package/docs/guides/connect-discord.md +178 -0
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/connect-telegram.md +61 -1
- package/docs/guides/connect-whatsapp-evolution.md +121 -0
- package/docs/guides/connect-whatsapp-uazapi.md +139 -0
- package/docs/guides/connect-whatsapp-zapster.md +119 -16
- package/docs/guides/create-agent.md +31 -4
- package/docs/guides/debug-channel.md +159 -0
- package/docs/guides/improve-from-production.md +151 -0
- package/docs/guides/prepare-deploy.md +32 -14
- package/docs/guides/replay-production-traces.md +72 -0
- package/docs/guides/run-evals.md +95 -25
- package/docs/guides/security-rules.md +9 -5
- package/docs/guides/send-feedback.md +135 -0
- package/docs/guides/use-jev.md +67 -0
- package/docs/guides/use-provider.md +70 -3
- package/docs/llms-full.txt +295 -25
- package/docs/llms.txt +54 -7
- package/package.json +3 -7
- package/src/cli/args.ts +23 -2
- package/src/cli/cloud-client.ts +121 -9
- package/src/cli/commands/channels.ts +856 -36
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/commands/provider.ts +47 -0
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/deploy-chat-ui.ts +232 -18
- package/src/cli/deploy-readiness.ts +227 -14
- package/src/cli/help.ts +67 -9
- package/src/cli/index.ts +740 -35
- package/src/cli/new-command.ts +41 -0
- package/src/cloud/client.ts +4 -3
- package/src/cloud/contracts.ts +1 -1
- package/src/create-project.ts +18 -35
- package/src/index.ts +565 -11
- package/src/providers/codex-auth.ts +111 -0
- package/src/providers/pi.ts +88 -19
- package/src/providers/test.ts +36 -0
- package/src/providers/types.ts +8 -0
- package/src/runtime/channel-test-harness.ts +21 -1
- package/src/runtime/channels/discord.ts +896 -0
- package/src/runtime/channels/generic-webhook.ts +974 -0
- package/src/runtime/channels/slack.ts +646 -0
- package/src/runtime/channels/telegram.ts +343 -12
- package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1323 -0
- package/src/runtime/channels/whatsapp-zapster.ts +674 -40
- package/src/runtime/channels.ts +83 -3
- package/src/runtime/chat.ts +70 -44
- package/src/runtime/config.ts +489 -20
- package/src/runtime/core/manifest.ts +75 -5
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/deploy-readiness.ts +34 -4
- package/src/runtime/dev-server.ts +639 -39
- package/src/runtime/env.ts +8 -3
- package/src/runtime/evals.ts +445 -74
- package/src/runtime/improve.ts +868 -0
- package/src/runtime/inspect.ts +173 -4
- package/src/runtime/integrations/composio.ts +425 -0
- package/src/runtime/knowledge/retrieve.ts +25 -5
- package/src/runtime/knowledge/schema.ts +45 -1
- package/src/runtime/prompt-context.ts +141 -0
- package/src/runtime/runtime-contract.ts +71 -7
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +1010 -208
- package/src/runtime/targets/container/server.ts +1 -1
- package/src/runtime/targets/vps/deploy.ts +26 -9
- package/src/runtime/tool-runner.ts +9 -1
- package/src/runtime/tools.ts +26 -2
- package/src/runtime/transcription.ts +483 -0
- package/src/storage/sqlite.ts +7 -2
- package/src/templates/blank.ts +37 -9
- package/src/templates/dentista.ts +40 -14
- package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
- package/src/templates/skills/agentkit-channels/SKILL.md +66 -1
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
- package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
- package/src/templates/skills/agentkit-database/SKILL.md +11 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
- package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
- package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
- package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
- package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
- package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
- package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
- package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
- package/src/templates/skills/agentkit-security/SKILL.md +5 -2
- package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
- package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
- package/src/templates/support.ts +42 -12
- package/docs/guides/agentkit-skills-architecture.md +0 -471
- package/docs/guides/channels-implementation-map.md +0 -243
- package/docs/guides/channels-production-handoff.md +0 -101
- package/docs/portable-deploy-release-checklist.md +0 -41
package/src/runtime/inspect.ts
CHANGED
|
@@ -4,8 +4,16 @@ import type { AgentChannel, AgentProvider, AgentRuntime, AccessMode } from "../i
|
|
|
4
4
|
import type { LoadedAgentCapsule } from "./config";
|
|
5
5
|
import { loadAgentCapsule } from "./config";
|
|
6
6
|
import { loadCapsuleEnv } from "./env";
|
|
7
|
+
import {
|
|
8
|
+
createManagedComposioTools,
|
|
9
|
+
managedComposioRequiredSecrets,
|
|
10
|
+
resolveManagedComposioIntegrations,
|
|
11
|
+
} from "./integrations/composio";
|
|
7
12
|
import { resolveKnowledgeConfig } from "./knowledge/config";
|
|
8
13
|
import { KNOWLEDGE_SEARCH_TOOL_NAME } from "./knowledge/tool";
|
|
14
|
+
import { resolveRuntimeTimeZone } from "./prompt-context";
|
|
15
|
+
import { checkDefaultSkillStatus, type DefaultSkillStatus } from "./skills";
|
|
16
|
+
import { resolveTranscriptionConfig } from "./transcription";
|
|
9
17
|
|
|
10
18
|
export type SecretState = "set" | "missing";
|
|
11
19
|
|
|
@@ -13,9 +21,27 @@ export type AgentInspectState = {
|
|
|
13
21
|
agent: string;
|
|
14
22
|
runtime: AgentRuntime;
|
|
15
23
|
provider: AgentProvider;
|
|
24
|
+
timeZone: {
|
|
25
|
+
configured: string | null;
|
|
26
|
+
effective: string;
|
|
27
|
+
};
|
|
16
28
|
prompt: string;
|
|
17
29
|
tools: string[];
|
|
30
|
+
integrations: AgentInspectIntegration[];
|
|
18
31
|
channels: AgentInspectChannel[];
|
|
32
|
+
transcription: {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
provider: string | null;
|
|
35
|
+
model: string | null;
|
|
36
|
+
secret: string | null;
|
|
37
|
+
language: string | null;
|
|
38
|
+
prompt: string | null;
|
|
39
|
+
limits: {
|
|
40
|
+
maxDurationSeconds: number | null;
|
|
41
|
+
maxBytes: number | null;
|
|
42
|
+
};
|
|
43
|
+
rawAudioTtlSeconds: number | null;
|
|
44
|
+
};
|
|
19
45
|
knowledge: {
|
|
20
46
|
enabled: boolean;
|
|
21
47
|
sources: string[];
|
|
@@ -52,6 +78,8 @@ export type AgentInspectState = {
|
|
|
52
78
|
secrets: Record<string, SecretState>;
|
|
53
79
|
managedSecrets: string[];
|
|
54
80
|
userSecrets: string[];
|
|
81
|
+
skills: AgentInspectSkills;
|
|
82
|
+
warnings: AgentInspectWarning[];
|
|
55
83
|
};
|
|
56
84
|
|
|
57
85
|
export type AgentInspectDatabase = {
|
|
@@ -73,8 +101,39 @@ export type AgentInspectChannel = {
|
|
|
73
101
|
name: string;
|
|
74
102
|
type: AgentChannel["type"];
|
|
75
103
|
provider: AgentChannel["provider"];
|
|
104
|
+
direction?: Extract<AgentChannel, { type: "webhook" }>["direction"];
|
|
76
105
|
secrets: string[];
|
|
77
106
|
buffer?: AgentChannel["buffer"];
|
|
107
|
+
audio?: AgentChannel["audio"];
|
|
108
|
+
replyTo?: Extract<AgentChannel, { replyTo?: unknown }>["replyTo"];
|
|
109
|
+
urlSecret?: string;
|
|
110
|
+
auth?: {
|
|
111
|
+
type: string;
|
|
112
|
+
headerName?: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type AgentInspectSkills = {
|
|
117
|
+
status: "current" | "missing" | "outdated" | "not_checked";
|
|
118
|
+
checked: number;
|
|
119
|
+
missing: string[];
|
|
120
|
+
outdated: string[];
|
|
121
|
+
syncCommand: string | null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type AgentInspectWarning = {
|
|
125
|
+
code: string;
|
|
126
|
+
message: string;
|
|
127
|
+
command?: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export type AgentInspectIntegration = {
|
|
131
|
+
name: string;
|
|
132
|
+
provider: "composio";
|
|
133
|
+
mode: "managed";
|
|
134
|
+
toolkits: string[];
|
|
135
|
+
tools: string[];
|
|
136
|
+
userId: string;
|
|
78
137
|
};
|
|
79
138
|
|
|
80
139
|
export async function inspectAgentCapsule(
|
|
@@ -82,17 +141,20 @@ export async function inspectAgentCapsule(
|
|
|
82
141
|
env: Record<string, string | undefined> = process.env,
|
|
83
142
|
): Promise<AgentInspectState> {
|
|
84
143
|
const capsule = await loadAgentCapsule(cwd);
|
|
85
|
-
return buildInspectState(capsule, await loadCapsuleEnv(capsule.root, env));
|
|
144
|
+
return buildInspectState(capsule, await loadCapsuleEnv(capsule.root, env), await checkDefaultSkillStatus(capsule.root));
|
|
86
145
|
}
|
|
87
146
|
|
|
88
147
|
export function buildInspectState(
|
|
89
148
|
capsule: LoadedAgentCapsule,
|
|
90
149
|
env: Record<string, string | undefined> = process.env,
|
|
150
|
+
skillStatus: DefaultSkillStatus | null = null,
|
|
91
151
|
): AgentInspectState {
|
|
92
152
|
const declaredSecrets = new Set(capsule.config.secrets);
|
|
93
153
|
const knowledge = resolveKnowledgeConfig(capsule.config);
|
|
154
|
+
const integrationTools = createManagedComposioTools(capsule.config);
|
|
155
|
+
const transcription = resolveTranscriptionConfig(capsule.config.transcription);
|
|
94
156
|
|
|
95
|
-
for (const tool of capsule.config.tools ?? []) {
|
|
157
|
+
for (const tool of [...integrationTools, ...(capsule.config.tools ?? [])]) {
|
|
96
158
|
for (const secret of tool.secrets ?? []) {
|
|
97
159
|
declaredSecrets.add(secret);
|
|
98
160
|
}
|
|
@@ -108,8 +170,15 @@ export function buildInspectState(
|
|
|
108
170
|
declaredSecrets.add(knowledge.embedding.secret);
|
|
109
171
|
}
|
|
110
172
|
|
|
173
|
+
if (transcription.secret) {
|
|
174
|
+
declaredSecrets.add(transcription.secret);
|
|
175
|
+
}
|
|
176
|
+
|
|
111
177
|
const managedSecrets = managedCloudSecrets(capsule);
|
|
112
|
-
const
|
|
178
|
+
const managedSecretSet = new Set(managedSecrets);
|
|
179
|
+
const userSecrets = Array.from(declaredSecrets)
|
|
180
|
+
.filter((secret) => !managedSecretSet.has(secret))
|
|
181
|
+
.sort();
|
|
113
182
|
const secrets: Record<string, SecretState> = {};
|
|
114
183
|
|
|
115
184
|
for (const secret of userSecrets) {
|
|
@@ -118,19 +187,63 @@ export function buildInspectState(
|
|
|
118
187
|
|
|
119
188
|
const database = inspectDatabase(capsule);
|
|
120
189
|
|
|
190
|
+
const skills = inspectSkills(skillStatus);
|
|
191
|
+
const warnings = inspectWarnings(capsule, transcription, skills);
|
|
192
|
+
|
|
121
193
|
return {
|
|
122
194
|
agent: capsule.config.name,
|
|
123
195
|
runtime: capsule.config.runtime,
|
|
124
196
|
provider: capsule.config.provider,
|
|
197
|
+
timeZone: {
|
|
198
|
+
configured: capsule.config.timeZone ?? null,
|
|
199
|
+
effective: resolveRuntimeTimeZone({
|
|
200
|
+
timeZone: capsule.config.timeZone,
|
|
201
|
+
env,
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
125
204
|
prompt: toCapsulePath(capsule.root, capsule.instructionsPath),
|
|
126
|
-
tools: (capsule.config.tools ?? []).map((tool) => tool.name),
|
|
205
|
+
tools: [...integrationTools, ...(capsule.config.tools ?? [])].map((tool) => tool.name),
|
|
206
|
+
integrations: resolveManagedComposioIntegrations(capsule.config).map((integration) => ({
|
|
207
|
+
name: integration.name,
|
|
208
|
+
provider: integration.provider,
|
|
209
|
+
mode: integration.mode,
|
|
210
|
+
toolkits: integration.toolkits,
|
|
211
|
+
tools: integration.allowedTools,
|
|
212
|
+
userId: integration.userId,
|
|
213
|
+
confirmExternalWrites: integration.confirmExternalWrites !== false,
|
|
214
|
+
})),
|
|
127
215
|
channels: (capsule.config.channels ?? []).map((channel) => ({
|
|
128
216
|
name: channel.name,
|
|
129
217
|
type: channel.type,
|
|
130
218
|
provider: channel.provider,
|
|
219
|
+
...("direction" in channel && channel.direction ? { direction: channel.direction } : {}),
|
|
131
220
|
secrets: [...channel.secrets],
|
|
132
221
|
...(channel.buffer ? { buffer: channel.buffer } : {}),
|
|
222
|
+
...(channel.audio ? { audio: channel.audio } : {}),
|
|
223
|
+
...(channel.replyTo ? { replyTo: channel.replyTo } : {}),
|
|
224
|
+
...("urlSecret" in channel && channel.urlSecret ? { urlSecret: channel.urlSecret } : {}),
|
|
225
|
+
...("auth" in channel && channel.auth
|
|
226
|
+
? {
|
|
227
|
+
auth: {
|
|
228
|
+
type: channel.auth.type,
|
|
229
|
+
...("headerName" in channel.auth && channel.auth.headerName ? { headerName: channel.auth.headerName } : {}),
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: {}),
|
|
133
233
|
})),
|
|
234
|
+
transcription: {
|
|
235
|
+
enabled: transcription.enabled,
|
|
236
|
+
provider: transcription.enabled ? transcription.provider : null,
|
|
237
|
+
model: transcription.enabled ? transcription.model : null,
|
|
238
|
+
secret: transcription.secret,
|
|
239
|
+
language: transcription.language,
|
|
240
|
+
prompt: transcription.prompt,
|
|
241
|
+
limits: {
|
|
242
|
+
maxDurationSeconds: transcription.limits.maxDurationSeconds ?? null,
|
|
243
|
+
maxBytes: transcription.limits.maxBytes ?? null,
|
|
244
|
+
},
|
|
245
|
+
rawAudioTtlSeconds: transcription.rawAudioTtlSeconds,
|
|
246
|
+
},
|
|
134
247
|
knowledge: {
|
|
135
248
|
enabled: knowledge.enabled,
|
|
136
249
|
sources: knowledge.sources.map((source) => source.value),
|
|
@@ -162,6 +275,8 @@ export function buildInspectState(
|
|
|
162
275
|
secrets,
|
|
163
276
|
managedSecrets,
|
|
164
277
|
userSecrets,
|
|
278
|
+
skills,
|
|
279
|
+
warnings,
|
|
165
280
|
};
|
|
166
281
|
}
|
|
167
282
|
|
|
@@ -191,6 +306,56 @@ function inspectDatabase(capsule: LoadedAgentCapsule): AgentInspectDatabase {
|
|
|
191
306
|
};
|
|
192
307
|
}
|
|
193
308
|
|
|
309
|
+
function inspectSkills(status: DefaultSkillStatus | null): AgentInspectSkills {
|
|
310
|
+
if (!status) {
|
|
311
|
+
return {
|
|
312
|
+
status: "not_checked",
|
|
313
|
+
checked: 0,
|
|
314
|
+
missing: [],
|
|
315
|
+
outdated: [],
|
|
316
|
+
syncCommand: null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
status: status.status,
|
|
322
|
+
checked: status.checked,
|
|
323
|
+
missing: status.missing,
|
|
324
|
+
outdated: status.outdated,
|
|
325
|
+
syncCommand: status.status === "current" ? null : "npm run agentkit -- skills sync",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function inspectWarnings(
|
|
330
|
+
capsule: LoadedAgentCapsule,
|
|
331
|
+
transcription: ReturnType<typeof resolveTranscriptionConfig>,
|
|
332
|
+
skills: AgentInspectSkills,
|
|
333
|
+
): AgentInspectWarning[] {
|
|
334
|
+
const warnings: AgentInspectWarning[] = [];
|
|
335
|
+
|
|
336
|
+
if (skills.status === "missing" || skills.status === "outdated") {
|
|
337
|
+
warnings.push({
|
|
338
|
+
code: "local_skills_outdated",
|
|
339
|
+
message: "Local AgentKit skills are older than or missing from the installed package docs.",
|
|
340
|
+
command: "npm run agentkit -- skills sync",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (
|
|
345
|
+
transcription.enabled &&
|
|
346
|
+
transcription.provider === "openai" &&
|
|
347
|
+
(capsule.config.channels ?? []).some((channel) => channel.type === "telegram" && channel.audio?.mode === "transcribe")
|
|
348
|
+
) {
|
|
349
|
+
warnings.push({
|
|
350
|
+
code: "telegram_audio_openai_format",
|
|
351
|
+
message: "Telegram voice notes arrive as OGG/Opus; use Groq for the default Telegram voice-note transcription path.",
|
|
352
|
+
command: "Set transcription.provider to \"groq\" and secret to \"GROQ_API_KEY\".",
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return warnings;
|
|
357
|
+
}
|
|
358
|
+
|
|
194
359
|
function managedCloudSecrets(capsule: LoadedAgentCapsule): string[] {
|
|
195
360
|
const secrets = new Set<string>();
|
|
196
361
|
|
|
@@ -199,5 +364,9 @@ function managedCloudSecrets(capsule: LoadedAgentCapsule): string[] {
|
|
|
199
364
|
secrets.add("TURSO_DATABASE_URL");
|
|
200
365
|
}
|
|
201
366
|
|
|
367
|
+
for (const secret of managedComposioRequiredSecrets(capsule.config)) {
|
|
368
|
+
secrets.add(secret);
|
|
369
|
+
}
|
|
370
|
+
|
|
202
371
|
return Array.from(secrets).sort();
|
|
203
372
|
}
|
|
@@ -0,0 +1,425 @@
|
|
|
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
|
+
confirmed?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ManagedComposioToolOutput = {
|
|
27
|
+
successful: boolean;
|
|
28
|
+
data: unknown;
|
|
29
|
+
error: unknown;
|
|
30
|
+
logId: string | null;
|
|
31
|
+
toolkit: string;
|
|
32
|
+
toolSlug: string;
|
|
33
|
+
userId: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ComposioActionGuide = {
|
|
37
|
+
summary: string;
|
|
38
|
+
argumentNotes: string[];
|
|
39
|
+
dangerousDefaults?: string[];
|
|
40
|
+
examples?: Array<Record<string, unknown>>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function resolveManagedComposioIntegrations(
|
|
44
|
+
config: Pick<AgentConfig, "name" | "integrations">,
|
|
45
|
+
options: ManagedComposioResolutionOptions = {},
|
|
46
|
+
): ResolvedComposioManagedIntegration[] {
|
|
47
|
+
return (config.integrations ?? [])
|
|
48
|
+
.filter((integration): integration is ComposioManagedIntegration => integration.provider === "composio" && integration.mode === "managed")
|
|
49
|
+
.map((integration) => ({
|
|
50
|
+
...integration,
|
|
51
|
+
userId: options.userId ?? managedComposioUserId(config.name, options.userIdScope),
|
|
52
|
+
allowedTools: allowedComposioTools(integration),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasManagedComposioIntegration(config: Pick<AgentConfig, "name" | "integrations">): boolean {
|
|
57
|
+
return resolveManagedComposioIntegrations(config).length > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function managedComposioRequiredSecrets(config: Pick<AgentConfig, "name" | "integrations">): string[] {
|
|
61
|
+
return hasManagedComposioIntegration(config) ? [MANAGED_COMPOSIO_API_KEY_SECRET] : [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createManagedComposioTools(
|
|
65
|
+
config: Pick<AgentConfig, "name" | "integrations">,
|
|
66
|
+
options: ManagedComposioResolutionOptions = {},
|
|
67
|
+
): AgentTool[] {
|
|
68
|
+
const integrations = resolveManagedComposioIntegrations(config, options);
|
|
69
|
+
|
|
70
|
+
return integrations.flatMap((integration) => {
|
|
71
|
+
if (integration.allowedTools.length === 0) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const toolkitByTool = new Map<string, string>();
|
|
76
|
+
|
|
77
|
+
for (const [toolkit, tools] of Object.entries(integration.tools ?? {})) {
|
|
78
|
+
for (const tool of tools) {
|
|
79
|
+
toolkitByTool.set(tool, toolkit);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const tool: AgentTool<ManagedComposioToolInput, ManagedComposioToolOutput> = {
|
|
84
|
+
name: MANAGED_COMPOSIO_TOOL_NAME,
|
|
85
|
+
description: [
|
|
86
|
+
"Executes one explicitly allowed AgentKit-managed Composio action for this deployed agent. Use only for configured connected apps.",
|
|
87
|
+
integration.confirmExternalWrites === false
|
|
88
|
+
? "External write confirmation is disabled for this integration by agentkit.config.ts."
|
|
89
|
+
: "For write actions, pass confirmed: true only after the user confirms the exact external change.",
|
|
90
|
+
describeAllowedComposioActions(integration.allowedTools),
|
|
91
|
+
].join("\n\n"),
|
|
92
|
+
visibility: "user",
|
|
93
|
+
secrets: [MANAGED_COMPOSIO_API_KEY_SECRET],
|
|
94
|
+
permissions: integration.allowedTools.map(
|
|
95
|
+
(toolName) => `composio:${toolName.toLowerCase()}:${isComposioWriteAction(toolName) ? "write" : "read"}`,
|
|
96
|
+
),
|
|
97
|
+
timeoutMs: 60_000,
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
toolSlug: {
|
|
102
|
+
type: "string",
|
|
103
|
+
enum: integration.allowedTools,
|
|
104
|
+
description: "Allowed Composio action slug to execute.",
|
|
105
|
+
},
|
|
106
|
+
arguments: {
|
|
107
|
+
type: "object",
|
|
108
|
+
additionalProperties: true,
|
|
109
|
+
description:
|
|
110
|
+
"Structured arguments for the selected Composio action. Use the action notes in the tool description; do not rely on Composio defaults for dates, times, durations, recipients, or write behavior.",
|
|
111
|
+
},
|
|
112
|
+
confirmed: {
|
|
113
|
+
type: "boolean",
|
|
114
|
+
description:
|
|
115
|
+
"Set true only after explicit user confirmation for external write actions such as create, update, delete, send, patch, move, insert, clear, remove, import, or quick add.",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: ["toolSlug", "arguments"],
|
|
119
|
+
additionalProperties: false,
|
|
120
|
+
},
|
|
121
|
+
outputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
successful: { type: "boolean" },
|
|
125
|
+
data: { type: ["object", "array", "string", "number", "boolean", "null"] },
|
|
126
|
+
error: { type: ["object", "string", "null"] },
|
|
127
|
+
logId: { type: ["string", "null"] },
|
|
128
|
+
toolkit: { type: "string" },
|
|
129
|
+
toolSlug: { type: "string" },
|
|
130
|
+
userId: { type: "string" },
|
|
131
|
+
},
|
|
132
|
+
required: ["successful", "toolkit", "toolSlug", "userId"],
|
|
133
|
+
additionalProperties: true,
|
|
134
|
+
},
|
|
135
|
+
async execute(input, ctx) {
|
|
136
|
+
const toolkit = toolkitByTool.get(input.toolSlug);
|
|
137
|
+
|
|
138
|
+
if (!toolkit || !integration.allowedTools.includes(input.toolSlug)) {
|
|
139
|
+
throw new AgentKitError(
|
|
140
|
+
"composio_tool_not_allowed",
|
|
141
|
+
`Composio action "${input.toolSlug}" is not allowed by this agent's managed Composio settings.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
validateComposioArguments(input.toolSlug, input.arguments);
|
|
146
|
+
validateComposioConfirmation(input.toolSlug, input.confirmed, integration);
|
|
147
|
+
|
|
148
|
+
const result = await executeComposioTool({
|
|
149
|
+
apiKey: ctx.secrets[MANAGED_COMPOSIO_API_KEY_SECRET],
|
|
150
|
+
userId: integration.userId,
|
|
151
|
+
toolSlug: input.toolSlug,
|
|
152
|
+
arguments: input.arguments,
|
|
153
|
+
signal: ctx.signal,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
successful: result.successful,
|
|
158
|
+
data: result.data ?? null,
|
|
159
|
+
error: result.error ?? null,
|
|
160
|
+
logId: result.log_id ?? null,
|
|
161
|
+
toolkit,
|
|
162
|
+
toolSlug: input.toolSlug,
|
|
163
|
+
userId: integration.userId,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
render(output) {
|
|
167
|
+
if (output.successful) {
|
|
168
|
+
return `Composio action ${output.toolSlug} completed.`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
text: `Composio action ${output.toolSlug} did not complete successfully.`,
|
|
173
|
+
blocks: [
|
|
174
|
+
{
|
|
175
|
+
type: "card",
|
|
176
|
+
title: "Composio connection or action issue",
|
|
177
|
+
subtitle: typeof output.error === "string" ? output.error : undefined,
|
|
178
|
+
fields: [
|
|
179
|
+
{ label: "Toolkit", value: output.toolkit },
|
|
180
|
+
{ label: "Action", value: output.toolSlug },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return [tool];
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function managedComposioUserId(agentName: string, scope = "local"): string {
|
|
193
|
+
return `ak_agent_${stableHash(`${scope}:${agentName}`).slice(0, 24)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function allowedComposioTools(integration: ComposioManagedIntegration): string[] {
|
|
197
|
+
return Array.from(new Set(Object.values(integration.tools ?? {}).flat())).sort();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function describeAllowedComposioActions(actions: string[]): string {
|
|
201
|
+
const lines = ["Allowed Composio actions:"];
|
|
202
|
+
|
|
203
|
+
for (const action of actions) {
|
|
204
|
+
const guide = COMPOSIO_ACTION_GUIDES[action];
|
|
205
|
+
|
|
206
|
+
if (!guide) {
|
|
207
|
+
lines.push(`- ${action}: Pass explicit arguments from the current Composio action schema. Do not invent fields or rely on hidden defaults.`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
lines.push(`- ${action}: ${guide.summary}`);
|
|
212
|
+
|
|
213
|
+
for (const note of guide.argumentNotes) {
|
|
214
|
+
lines.push(` - ${note}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const warning of guide.dangerousDefaults ?? []) {
|
|
218
|
+
lines.push(` - Dangerous default: ${warning}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const example of guide.examples ?? []) {
|
|
222
|
+
lines.push(` - Example arguments: ${JSON.stringify(example)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function validateComposioArguments(toolSlug: string, args: Record<string, unknown>): void {
|
|
230
|
+
if (toolSlug !== "GOOGLECALENDAR_CREATE_EVENT") {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const start = args.start_datetime;
|
|
235
|
+
|
|
236
|
+
if (typeof start !== "string" || !isUtcDateTime(start)) {
|
|
237
|
+
throw new AgentKitError(
|
|
238
|
+
"composio_argument_invalid",
|
|
239
|
+
"GOOGLECALENDAR_CREATE_EVENT requires start_datetime as an explicit UTC RFC3339 timestamp ending in Z or +00:00. Convert user-local times using the runtime time zone before calling Composio.",
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const minutes = args.event_duration_minutes;
|
|
244
|
+
const hours = args.event_duration_hour;
|
|
245
|
+
|
|
246
|
+
if (!isPositiveNumber(minutes) && !isPositiveNumber(hours)) {
|
|
247
|
+
throw new AgentKitError(
|
|
248
|
+
"composio_argument_required",
|
|
249
|
+
"GOOGLECALENDAR_CREATE_EVENT requires explicit event_duration_minutes or event_duration_hour. Composio defaults event_duration_minutes to 30, so AgentKit blocks implicit duration.",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function validateComposioConfirmation(
|
|
255
|
+
toolSlug: string,
|
|
256
|
+
confirmed: boolean | undefined,
|
|
257
|
+
integration: ComposioManagedIntegration,
|
|
258
|
+
): void {
|
|
259
|
+
if (integration.confirmExternalWrites === false || !isComposioWriteAction(toolSlug)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (confirmed !== true) {
|
|
264
|
+
throw new AgentKitError(
|
|
265
|
+
"composio_confirmation_required",
|
|
266
|
+
`Composio action ${toolSlug} changes an external system. Ask the user to confirm the exact action and call again with confirmed: true.`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function isComposioWriteAction(toolSlug: string): boolean {
|
|
272
|
+
return /_(?:BATCH|CLEAR|CREATE|DELETE|DUPLICATE|IMPORT|INSERT|MOVE|PATCH|QUICK_ADD|REMOVE|REVOKE|SEND|UPDATE)(?:_|$)/.test(
|
|
273
|
+
toolSlug,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function isUtcDateTime(value: string): boolean {
|
|
278
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|\+00:00)$/.test(value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isPositiveNumber(value: unknown): boolean {
|
|
282
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const COMPOSIO_ACTION_GUIDES: Record<string, ComposioActionGuide> = {
|
|
286
|
+
GOOGLECALENDAR_EVENTS_LIST: {
|
|
287
|
+
summary: "List events from a calendar before answering schedule/availability questions.",
|
|
288
|
+
argumentNotes: [
|
|
289
|
+
"calendarId is required by Composio; use primary unless the user selected a different calendar.",
|
|
290
|
+
"Use explicit timeMin and timeMax RFC3339 bounds for the requested window.",
|
|
291
|
+
"Pass timeZone when the user or business timezone matters.",
|
|
292
|
+
],
|
|
293
|
+
examples: [
|
|
294
|
+
{
|
|
295
|
+
calendarId: "primary",
|
|
296
|
+
timeMin: "2026-06-03T00:00:00-03:00",
|
|
297
|
+
timeMax: "2026-06-04T00:00:00-03:00",
|
|
298
|
+
singleEvents: true,
|
|
299
|
+
orderBy: "startTime",
|
|
300
|
+
timeZone: "America/Sao_Paulo",
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
GOOGLECALENDAR_CREATE_EVENT: {
|
|
305
|
+
summary: "Create an event only after confirming the exact title, date, start time, duration, timezone, and attendees/location when relevant.",
|
|
306
|
+
argumentNotes: [
|
|
307
|
+
"start_datetime is required and must be UTC RFC3339, for example 2026-06-03T15:00:00Z for 12:00 in America/Sao_Paulo.",
|
|
308
|
+
"calendar_id defaults to primary; pass it explicitly when known.",
|
|
309
|
+
"event_duration_minutes or event_duration_hour must be explicit.",
|
|
310
|
+
"Set timezone to the user/business IANA timezone when known.",
|
|
311
|
+
],
|
|
312
|
+
dangerousDefaults: [
|
|
313
|
+
"event_duration_minutes defaults to 30 if omitted.",
|
|
314
|
+
"calendar_id defaults to primary if omitted.",
|
|
315
|
+
],
|
|
316
|
+
examples: [
|
|
317
|
+
{
|
|
318
|
+
calendar_id: "primary",
|
|
319
|
+
summary: "Client call",
|
|
320
|
+
start_datetime: "2026-06-03T15:00:00Z",
|
|
321
|
+
event_duration_minutes: 60,
|
|
322
|
+
timezone: "America/Sao_Paulo",
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
GOOGLECALENDAR_UPDATE_EVENT: {
|
|
327
|
+
summary: "Update an existing event only after identifying the event and confirming the exact change.",
|
|
328
|
+
argumentNotes: [
|
|
329
|
+
"Read or find the event first so event_id/calendar_id are correct.",
|
|
330
|
+
"When changing time, use explicit UTC RFC3339 values and preserve duration unless the user changed it.",
|
|
331
|
+
"Do not infer cancellation/deletion from vague wording.",
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
GMAIL_FETCH_EMAILS: {
|
|
335
|
+
summary: "Fetch emails for read/search requests.",
|
|
336
|
+
argumentNotes: ["Use narrow query/window arguments when available; do not fetch broad mailbox history unnecessarily."],
|
|
337
|
+
},
|
|
338
|
+
GMAIL_SEND_EMAIL: {
|
|
339
|
+
summary: "Send email only after confirming recipients, subject, body, and attachments/links.",
|
|
340
|
+
argumentNotes: ["Pass recipient fields explicitly and avoid using draft-like placeholders in production sends."],
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
async function executeComposioTool(input: {
|
|
345
|
+
apiKey: string | undefined;
|
|
346
|
+
userId: string;
|
|
347
|
+
toolSlug: string;
|
|
348
|
+
arguments: Record<string, unknown>;
|
|
349
|
+
signal: AbortSignal;
|
|
350
|
+
}): Promise<{ successful: boolean; data?: unknown; error?: unknown; log_id?: string }> {
|
|
351
|
+
if (!input.apiKey) {
|
|
352
|
+
throw new AgentKitError("composio_secret_missing", "AgentKit-managed Composio requires COMPOSIO_API_KEY at runtime.");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const response = await fetch(
|
|
356
|
+
`https://backend.composio.dev/api/v3.1/tools/execute/${encodeURIComponent(input.toolSlug)}`,
|
|
357
|
+
{
|
|
358
|
+
method: "POST",
|
|
359
|
+
headers: {
|
|
360
|
+
"content-type": "application/json",
|
|
361
|
+
"x-api-key": input.apiKey,
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify({
|
|
364
|
+
user_id: input.userId,
|
|
365
|
+
arguments: input.arguments,
|
|
366
|
+
}),
|
|
367
|
+
signal: input.signal,
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
const payload = await response.json().catch(() => null);
|
|
371
|
+
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
throw new AgentKitError(
|
|
374
|
+
"composio_execution_failed",
|
|
375
|
+
`Composio action ${input.toolSlug} failed with HTTP ${response.status}: ${readComposioError(payload)}`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!isRecord(payload)) {
|
|
380
|
+
return { successful: true, data: payload };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
successful: payload.successful !== false,
|
|
385
|
+
data: payload.data,
|
|
386
|
+
error: payload.error,
|
|
387
|
+
log_id: typeof payload.log_id === "string" ? payload.log_id : undefined,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function readComposioError(payload: unknown): string {
|
|
392
|
+
if (isRecord(payload)) {
|
|
393
|
+
if (typeof payload.error === "string") {
|
|
394
|
+
return payload.error;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (isRecord(payload.error) && typeof payload.error.message === "string") {
|
|
398
|
+
return payload.error.message;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return "unknown error";
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function stableHash(value: string): string {
|
|
406
|
+
let first = 2166136261;
|
|
407
|
+
let second = 0x811c9dc5;
|
|
408
|
+
let third = 0x01000193;
|
|
409
|
+
|
|
410
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
411
|
+
const code = value.charCodeAt(index);
|
|
412
|
+
first ^= code;
|
|
413
|
+
first = Math.imul(first, 16777619);
|
|
414
|
+
second ^= code + index;
|
|
415
|
+
second = Math.imul(second, 1597334677);
|
|
416
|
+
third ^= code * (index + 1);
|
|
417
|
+
third = Math.imul(third, 2246822519);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return [first, second, third].map((hash) => (hash >>> 0).toString(16).padStart(8, "0")).join("");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
424
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
425
|
+
}
|