@andreprado/agentkit 0.1.0-alpha.15 → 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 +65 -0
- package/docs/guides/add-managed-composio.md +137 -0
- package/docs/guides/channel-security.md +32 -0
- package/docs/guides/channels-production-handoff.md +1 -1
- package/docs/guides/connect-telegram.md +60 -0
- package/docs/guides/connect-whatsapp-zapster.md +65 -0
- 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 +121 -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 +231 -0
- package/src/runtime/channel-test-harness.ts +2 -0
- package/src/runtime/channels/telegram.ts +326 -10
- package/src/runtime/channels/whatsapp-zapster.ts +319 -0
- package/src/runtime/channels.ts +47 -1
- package/src/runtime/chat.ts +2 -1
- package/src/runtime/config.ts +214 -4
- package/src/runtime/core/manifest.ts +61 -2
- package/src/runtime/deploy-readiness.ts +31 -1
- package/src/runtime/dev-server.ts +72 -4
- package/src/runtime/inspect.ts +142 -4
- package/src/runtime/integrations/composio.ts +257 -0
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +162 -5
- package/src/runtime/tool-runner.ts +2 -1
- package/src/runtime/transcription.ts +483 -0
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +36 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +16 -1
- package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +29 -0
- 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
package/src/runtime/inspect.ts
CHANGED
|
@@ -4,9 +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";
|
|
9
14
|
import { resolveRuntimeTimeZone } from "./prompt-context";
|
|
15
|
+
import { checkDefaultSkillStatus, type DefaultSkillStatus } from "./skills";
|
|
16
|
+
import { resolveTranscriptionConfig } from "./transcription";
|
|
10
17
|
|
|
11
18
|
export type SecretState = "set" | "missing";
|
|
12
19
|
|
|
@@ -20,7 +27,21 @@ export type AgentInspectState = {
|
|
|
20
27
|
};
|
|
21
28
|
prompt: string;
|
|
22
29
|
tools: string[];
|
|
30
|
+
integrations: AgentInspectIntegration[];
|
|
23
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
|
+
};
|
|
24
45
|
knowledge: {
|
|
25
46
|
enabled: boolean;
|
|
26
47
|
sources: string[];
|
|
@@ -57,6 +78,8 @@ export type AgentInspectState = {
|
|
|
57
78
|
secrets: Record<string, SecretState>;
|
|
58
79
|
managedSecrets: string[];
|
|
59
80
|
userSecrets: string[];
|
|
81
|
+
skills: AgentInspectSkills;
|
|
82
|
+
warnings: AgentInspectWarning[];
|
|
60
83
|
};
|
|
61
84
|
|
|
62
85
|
export type AgentInspectDatabase = {
|
|
@@ -80,6 +103,30 @@ export type AgentInspectChannel = {
|
|
|
80
103
|
provider: AgentChannel["provider"];
|
|
81
104
|
secrets: string[];
|
|
82
105
|
buffer?: AgentChannel["buffer"];
|
|
106
|
+
audio?: AgentChannel["audio"];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type AgentInspectSkills = {
|
|
110
|
+
status: "current" | "missing" | "outdated" | "not_checked";
|
|
111
|
+
checked: number;
|
|
112
|
+
missing: string[];
|
|
113
|
+
outdated: string[];
|
|
114
|
+
syncCommand: string | null;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type AgentInspectWarning = {
|
|
118
|
+
code: string;
|
|
119
|
+
message: string;
|
|
120
|
+
command?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type AgentInspectIntegration = {
|
|
124
|
+
name: string;
|
|
125
|
+
provider: "composio";
|
|
126
|
+
mode: "managed";
|
|
127
|
+
toolkits: string[];
|
|
128
|
+
tools: string[];
|
|
129
|
+
userId: string;
|
|
83
130
|
};
|
|
84
131
|
|
|
85
132
|
export async function inspectAgentCapsule(
|
|
@@ -87,17 +134,20 @@ export async function inspectAgentCapsule(
|
|
|
87
134
|
env: Record<string, string | undefined> = process.env,
|
|
88
135
|
): Promise<AgentInspectState> {
|
|
89
136
|
const capsule = await loadAgentCapsule(cwd);
|
|
90
|
-
return buildInspectState(capsule, await loadCapsuleEnv(capsule.root, env));
|
|
137
|
+
return buildInspectState(capsule, await loadCapsuleEnv(capsule.root, env), await checkDefaultSkillStatus(capsule.root));
|
|
91
138
|
}
|
|
92
139
|
|
|
93
140
|
export function buildInspectState(
|
|
94
141
|
capsule: LoadedAgentCapsule,
|
|
95
142
|
env: Record<string, string | undefined> = process.env,
|
|
143
|
+
skillStatus: DefaultSkillStatus | null = null,
|
|
96
144
|
): AgentInspectState {
|
|
97
145
|
const declaredSecrets = new Set(capsule.config.secrets);
|
|
98
146
|
const knowledge = resolveKnowledgeConfig(capsule.config);
|
|
147
|
+
const integrationTools = createManagedComposioTools(capsule.config);
|
|
148
|
+
const transcription = resolveTranscriptionConfig(capsule.config.transcription);
|
|
99
149
|
|
|
100
|
-
for (const tool of capsule.config.tools ?? []) {
|
|
150
|
+
for (const tool of [...integrationTools, ...(capsule.config.tools ?? [])]) {
|
|
101
151
|
for (const secret of tool.secrets ?? []) {
|
|
102
152
|
declaredSecrets.add(secret);
|
|
103
153
|
}
|
|
@@ -113,8 +163,15 @@ export function buildInspectState(
|
|
|
113
163
|
declaredSecrets.add(knowledge.embedding.secret);
|
|
114
164
|
}
|
|
115
165
|
|
|
166
|
+
if (transcription.secret) {
|
|
167
|
+
declaredSecrets.add(transcription.secret);
|
|
168
|
+
}
|
|
169
|
+
|
|
116
170
|
const managedSecrets = managedCloudSecrets(capsule);
|
|
117
|
-
const
|
|
171
|
+
const managedSecretSet = new Set(managedSecrets);
|
|
172
|
+
const userSecrets = Array.from(declaredSecrets)
|
|
173
|
+
.filter((secret) => !managedSecretSet.has(secret))
|
|
174
|
+
.sort();
|
|
118
175
|
const secrets: Record<string, SecretState> = {};
|
|
119
176
|
|
|
120
177
|
for (const secret of userSecrets) {
|
|
@@ -123,6 +180,9 @@ export function buildInspectState(
|
|
|
123
180
|
|
|
124
181
|
const database = inspectDatabase(capsule);
|
|
125
182
|
|
|
183
|
+
const skills = inspectSkills(skillStatus);
|
|
184
|
+
const warnings = inspectWarnings(capsule, transcription, skills);
|
|
185
|
+
|
|
126
186
|
return {
|
|
127
187
|
agent: capsule.config.name,
|
|
128
188
|
runtime: capsule.config.runtime,
|
|
@@ -135,14 +195,36 @@ export function buildInspectState(
|
|
|
135
195
|
}),
|
|
136
196
|
},
|
|
137
197
|
prompt: toCapsulePath(capsule.root, capsule.instructionsPath),
|
|
138
|
-
tools: (capsule.config.tools ?? []).map((tool) => tool.name),
|
|
198
|
+
tools: [...integrationTools, ...(capsule.config.tools ?? [])].map((tool) => tool.name),
|
|
199
|
+
integrations: resolveManagedComposioIntegrations(capsule.config).map((integration) => ({
|
|
200
|
+
name: integration.name,
|
|
201
|
+
provider: integration.provider,
|
|
202
|
+
mode: integration.mode,
|
|
203
|
+
toolkits: integration.toolkits,
|
|
204
|
+
tools: integration.allowedTools,
|
|
205
|
+
userId: integration.userId,
|
|
206
|
+
})),
|
|
139
207
|
channels: (capsule.config.channels ?? []).map((channel) => ({
|
|
140
208
|
name: channel.name,
|
|
141
209
|
type: channel.type,
|
|
142
210
|
provider: channel.provider,
|
|
143
211
|
secrets: [...channel.secrets],
|
|
144
212
|
...(channel.buffer ? { buffer: channel.buffer } : {}),
|
|
213
|
+
...(channel.audio ? { audio: channel.audio } : {}),
|
|
145
214
|
})),
|
|
215
|
+
transcription: {
|
|
216
|
+
enabled: transcription.enabled,
|
|
217
|
+
provider: transcription.enabled ? transcription.provider : null,
|
|
218
|
+
model: transcription.enabled ? transcription.model : null,
|
|
219
|
+
secret: transcription.secret,
|
|
220
|
+
language: transcription.language,
|
|
221
|
+
prompt: transcription.prompt,
|
|
222
|
+
limits: {
|
|
223
|
+
maxDurationSeconds: transcription.limits.maxDurationSeconds ?? null,
|
|
224
|
+
maxBytes: transcription.limits.maxBytes ?? null,
|
|
225
|
+
},
|
|
226
|
+
rawAudioTtlSeconds: transcription.rawAudioTtlSeconds,
|
|
227
|
+
},
|
|
146
228
|
knowledge: {
|
|
147
229
|
enabled: knowledge.enabled,
|
|
148
230
|
sources: knowledge.sources.map((source) => source.value),
|
|
@@ -174,6 +256,8 @@ export function buildInspectState(
|
|
|
174
256
|
secrets,
|
|
175
257
|
managedSecrets,
|
|
176
258
|
userSecrets,
|
|
259
|
+
skills,
|
|
260
|
+
warnings,
|
|
177
261
|
};
|
|
178
262
|
}
|
|
179
263
|
|
|
@@ -203,6 +287,56 @@ function inspectDatabase(capsule: LoadedAgentCapsule): AgentInspectDatabase {
|
|
|
203
287
|
};
|
|
204
288
|
}
|
|
205
289
|
|
|
290
|
+
function inspectSkills(status: DefaultSkillStatus | null): AgentInspectSkills {
|
|
291
|
+
if (!status) {
|
|
292
|
+
return {
|
|
293
|
+
status: "not_checked",
|
|
294
|
+
checked: 0,
|
|
295
|
+
missing: [],
|
|
296
|
+
outdated: [],
|
|
297
|
+
syncCommand: null,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
status: status.status,
|
|
303
|
+
checked: status.checked,
|
|
304
|
+
missing: status.missing,
|
|
305
|
+
outdated: status.outdated,
|
|
306
|
+
syncCommand: status.status === "current" ? null : "npm run agentkit -- skills sync",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function inspectWarnings(
|
|
311
|
+
capsule: LoadedAgentCapsule,
|
|
312
|
+
transcription: ReturnType<typeof resolveTranscriptionConfig>,
|
|
313
|
+
skills: AgentInspectSkills,
|
|
314
|
+
): AgentInspectWarning[] {
|
|
315
|
+
const warnings: AgentInspectWarning[] = [];
|
|
316
|
+
|
|
317
|
+
if (skills.status === "missing" || skills.status === "outdated") {
|
|
318
|
+
warnings.push({
|
|
319
|
+
code: "local_skills_outdated",
|
|
320
|
+
message: "Local AgentKit skills are older than or missing from the installed package docs.",
|
|
321
|
+
command: "npm run agentkit -- skills sync",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (
|
|
326
|
+
transcription.enabled &&
|
|
327
|
+
transcription.provider === "openai" &&
|
|
328
|
+
(capsule.config.channels ?? []).some((channel) => channel.type === "telegram" && channel.audio?.mode === "transcribe")
|
|
329
|
+
) {
|
|
330
|
+
warnings.push({
|
|
331
|
+
code: "telegram_audio_openai_format",
|
|
332
|
+
message: "Telegram voice notes arrive as OGG/Opus; use Groq for the default Telegram voice-note transcription path.",
|
|
333
|
+
command: "Set transcription.provider to \"groq\" and secret to \"GROQ_API_KEY\".",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return warnings;
|
|
338
|
+
}
|
|
339
|
+
|
|
206
340
|
function managedCloudSecrets(capsule: LoadedAgentCapsule): string[] {
|
|
207
341
|
const secrets = new Set<string>();
|
|
208
342
|
|
|
@@ -211,5 +345,9 @@ function managedCloudSecrets(capsule: LoadedAgentCapsule): string[] {
|
|
|
211
345
|
secrets.add("TURSO_DATABASE_URL");
|
|
212
346
|
}
|
|
213
347
|
|
|
348
|
+
for (const secret of managedComposioRequiredSecrets(capsule.config)) {
|
|
349
|
+
secrets.add(secret);
|
|
350
|
+
}
|
|
351
|
+
|
|
214
352
|
return Array.from(secrets).sort();
|
|
215
353
|
}
|
|
@@ -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
|
+
}
|