@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.
Files changed (41) hide show
  1. package/README.md +8 -0
  2. package/docs/guides/add-channel.md +65 -0
  3. package/docs/guides/add-managed-composio.md +137 -0
  4. package/docs/guides/channel-security.md +32 -0
  5. package/docs/guides/channels-production-handoff.md +1 -1
  6. package/docs/guides/connect-telegram.md +60 -0
  7. package/docs/guides/connect-whatsapp-zapster.md +65 -0
  8. package/docs/guides/prepare-deploy.md +3 -1
  9. package/docs/llms-full.txt +13 -1
  10. package/docs/llms.txt +7 -0
  11. package/package.json +1 -1
  12. package/src/cli/commands/channels.ts +121 -7
  13. package/src/cli/commands/transcribe.ts +171 -0
  14. package/src/cli/deploy-readiness.ts +59 -3
  15. package/src/cli/help.ts +15 -0
  16. package/src/cli/index.ts +130 -0
  17. package/src/create-project.ts +4 -32
  18. package/src/index.ts +231 -0
  19. package/src/runtime/channel-test-harness.ts +2 -0
  20. package/src/runtime/channels/telegram.ts +326 -10
  21. package/src/runtime/channels/whatsapp-zapster.ts +319 -0
  22. package/src/runtime/channels.ts +47 -1
  23. package/src/runtime/chat.ts +2 -1
  24. package/src/runtime/config.ts +214 -4
  25. package/src/runtime/core/manifest.ts +61 -2
  26. package/src/runtime/deploy-readiness.ts +31 -1
  27. package/src/runtime/dev-server.ts +72 -4
  28. package/src/runtime/inspect.ts +142 -4
  29. package/src/runtime/integrations/composio.ts +257 -0
  30. package/src/runtime/skills.ts +95 -0
  31. package/src/runtime/targets/cloudflare/build.ts +162 -5
  32. package/src/runtime/tool-runner.ts +2 -1
  33. package/src/runtime/transcription.ts +483 -0
  34. package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
  35. package/src/templates/skills/agentkit-channels/SKILL.md +36 -1
  36. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +16 -1
  37. package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
  38. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +29 -0
  39. package/src/templates/skills/agentkit-deploy/SKILL.md +1 -1
  40. package/src/templates/skills/agentkit-integrations/SKILL.md +66 -0
  41. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +3 -2
@@ -16,7 +16,11 @@ import type {
16
16
  TelegramAllowedUpdate,
17
17
  AgentKnowledgeEmbeddingProvider,
18
18
  ChannelBufferMode,
19
+ AgentIntegration,
20
+ ChannelAudioMode,
21
+ TranscriptionProviderName,
19
22
  } from "../index";
23
+ import { MANAGED_COMPOSIO_TOOL_NAME } from "./integrations/composio";
20
24
  import { AgentKitError } from "./errors";
21
25
  import { isValidTimeZone } from "./prompt-context";
22
26
 
@@ -28,9 +32,14 @@ const TOOL_VISIBILITIES = new Set<ToolVisibility>(["user", "internal"]);
28
32
  const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp"]);
29
33
  const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta"]);
30
34
  const CHANNEL_BUFFER_MODES = new Set<ChannelBufferMode>(["off", "debounce"]);
35
+ const CHANNEL_AUDIO_MODES = new Set<ChannelAudioMode>(["off", "reject", "transcribe"]);
31
36
  const TELEGRAM_ALLOWED_UPDATES = new Set<TelegramAllowedUpdate>(["message"]);
32
37
  const KNOWLEDGE_EMBEDDING_PROVIDERS = new Set<AgentKnowledgeEmbeddingProvider>(["none", "test", "openai"]);
38
+ const TRANSCRIPTION_PROVIDERS = new Set<TranscriptionProviderName>(["test", "openai", "groq"]);
33
39
  const CHANNEL_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
40
+ const INTEGRATION_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
41
+ const COMPOSIO_TOOLKIT_PATTERN = /^[a-z][a-z0-9_-]*$/;
42
+ const COMPOSIO_TOOL_SLUG_PATTERN = /^[A-Z][A-Z0-9_]*$/;
34
43
  const SECRET_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
35
44
  const moduleDir = dirname(fileURLToPath(import.meta.url));
36
45
  let configImportNonce = 0;
@@ -230,7 +239,9 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
230
239
  }
231
240
  }
232
241
 
233
- validateChannels(value.channels, value.runtime as AgentRuntime);
242
+ validateIntegrations(value.integrations, value.runtime as AgentRuntime, value.tools);
243
+ validateTranscriptionConfig(value.transcription);
244
+ validateChannels(value.channels, value.runtime as AgentRuntime, Boolean(value.transcription));
234
245
  validateKnowledgeConfig(value.knowledge);
235
246
 
236
247
  if (!isRecord(value.access) || !ACCESS_MODES.has(value.access.mode as AccessMode)) {
@@ -253,6 +264,169 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
253
264
  validateFilesConfig(value.storage.files);
254
265
  }
255
266
 
267
+ function validateIntegrations(
268
+ value: unknown,
269
+ runtime: AgentRuntime,
270
+ tools: AgentConfig["tools"] = [],
271
+ ): void {
272
+ if (value === undefined) {
273
+ return;
274
+ }
275
+
276
+ if (!Array.isArray(value)) {
277
+ throw invalidConfig("integrations must be an array when provided.");
278
+ }
279
+
280
+ if (value.length > 0 && runtime !== "edge") {
281
+ throw invalidConfig('managed integrations require runtime: "edge". Use BYO defineTool wrappers for local-only integrations.');
282
+ }
283
+
284
+ let composioCount = 0;
285
+
286
+ for (const [index, integration] of value.entries()) {
287
+ if (!isRecord(integration)) {
288
+ throw invalidConfig(`integrations[${index}] must be an object.`);
289
+ }
290
+
291
+ if (integration.provider !== "composio" || integration.type !== "composio" || integration.mode !== "managed") {
292
+ throw invalidConfig(`integrations[${index}] must be composioManaged({...}) or { type: "composio", provider: "composio", mode: "managed", ... }.`);
293
+ }
294
+
295
+ composioCount += 1;
296
+ validateComposioManagedIntegration(integration as unknown as AgentIntegration, index);
297
+ }
298
+
299
+ if (composioCount > 1) {
300
+ throw invalidConfig("Only one managed Composio integration is supported per agent.");
301
+ }
302
+
303
+ const hasExplicitComposioTools = value.some(
304
+ (integration) => isRecord(integration) && integration.provider === "composio" && isRecord(integration.tools),
305
+ );
306
+
307
+ if (hasExplicitComposioTools && tools.some((tool) => isRecord(tool) && tool.name === MANAGED_COMPOSIO_TOOL_NAME)) {
308
+ throw invalidConfig(`tools must not declare reserved managed Composio tool name "${MANAGED_COMPOSIO_TOOL_NAME}".`);
309
+ }
310
+ }
311
+
312
+ function validateComposioManagedIntegration(value: AgentIntegration, index: number): void {
313
+ expectString(value.name, `integrations[${index}].name`);
314
+
315
+ if (!INTEGRATION_NAME_PATTERN.test(value.name)) {
316
+ throw invalidConfig(`integrations[${index}].name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`);
317
+ }
318
+
319
+ if (!Array.isArray(value.toolkits) || value.toolkits.length === 0) {
320
+ throw invalidConfig(`integrations[${index}].toolkits must include at least one Composio toolkit slug.`);
321
+ }
322
+
323
+ const toolkits = new Set<string>();
324
+
325
+ for (const [toolkitIndex, toolkit] of value.toolkits.entries()) {
326
+ expectString(toolkit, `integrations[${index}].toolkits[${toolkitIndex}]`);
327
+
328
+ if (!COMPOSIO_TOOLKIT_PATTERN.test(toolkit)) {
329
+ throw invalidConfig(`integrations[${index}].toolkits[${toolkitIndex}] must be a lowercase Composio toolkit slug.`);
330
+ }
331
+
332
+ if (toolkits.has(toolkit)) {
333
+ throw invalidConfig(`integrations[${index}].toolkits must not contain duplicates.`);
334
+ }
335
+
336
+ toolkits.add(toolkit);
337
+ }
338
+
339
+ if (value.tools !== undefined) {
340
+ if (!isRecord(value.tools)) {
341
+ throw invalidConfig(`integrations[${index}].tools must be an object when provided.`);
342
+ }
343
+
344
+ for (const [toolkit, tools] of Object.entries(value.tools)) {
345
+ if (!toolkits.has(toolkit)) {
346
+ throw invalidConfig(`integrations[${index}].tools.${toolkit} must reference a toolkit listed in integrations[${index}].toolkits.`);
347
+ }
348
+
349
+ if (!Array.isArray(tools) || tools.length === 0) {
350
+ throw invalidConfig(`integrations[${index}].tools.${toolkit} must include at least one Composio action slug.`);
351
+ }
352
+
353
+ const seenTools = new Set<string>();
354
+
355
+ for (const [toolIndex, tool] of tools.entries()) {
356
+ expectString(tool, `integrations[${index}].tools.${toolkit}[${toolIndex}]`);
357
+
358
+ if (!COMPOSIO_TOOL_SLUG_PATTERN.test(tool)) {
359
+ throw invalidConfig(`integrations[${index}].tools.${toolkit}[${toolIndex}] must be an uppercase Composio action slug.`);
360
+ }
361
+
362
+ if (seenTools.has(tool)) {
363
+ throw invalidConfig(`integrations[${index}].tools.${toolkit} must not contain duplicates.`);
364
+ }
365
+
366
+ seenTools.add(tool);
367
+ }
368
+ }
369
+ }
370
+
371
+ if (value.tags !== undefined) {
372
+ validateIntegrationTags(value.tags, `integrations[${index}].tags`);
373
+ }
374
+ }
375
+
376
+ function validateIntegrationTags(value: unknown, label: string): void {
377
+ if (!isRecord(value)) {
378
+ throw invalidConfig(`${label} must be an object when provided.`);
379
+ }
380
+
381
+ if (value.enable !== undefined) {
382
+ expectStringArray(value.enable, `${label}.enable`);
383
+ }
384
+
385
+ if (value.disable !== undefined) {
386
+ expectStringArray(value.disable, `${label}.disable`);
387
+ }
388
+ }
389
+
390
+ function validateTranscriptionConfig(value: unknown): void {
391
+ if (value === undefined) {
392
+ return;
393
+ }
394
+
395
+ if (!isRecord(value)) {
396
+ throw invalidConfig("transcription must be an object when provided.");
397
+ }
398
+
399
+ if (!TRANSCRIPTION_PROVIDERS.has(value.provider as TranscriptionProviderName)) {
400
+ throw invalidConfig(`transcription.provider must be one of: ${Array.from(TRANSCRIPTION_PROVIDERS).join(", ")}.`);
401
+ }
402
+
403
+ expectString(value.model, "transcription.model");
404
+
405
+ if (value.secret !== undefined) {
406
+ expectString(value.secret, "transcription.secret");
407
+ validateSecretNames([value.secret], "transcription.secret");
408
+ }
409
+
410
+ if (value.language !== undefined) {
411
+ expectString(value.language, "transcription.language");
412
+ }
413
+
414
+ if (value.prompt !== undefined) {
415
+ expectString(value.prompt, "transcription.prompt");
416
+ }
417
+
418
+ if (value.limits !== undefined) {
419
+ if (!isRecord(value.limits)) {
420
+ throw invalidConfig("transcription.limits must be an object when provided.");
421
+ }
422
+
423
+ validateOptionalPositiveInteger(value.limits.maxDurationSeconds, "transcription.limits.maxDurationSeconds");
424
+ validateOptionalPositiveInteger(value.limits.maxBytes, "transcription.limits.maxBytes");
425
+ }
426
+
427
+ validateOptionalPositiveInteger(value.rawAudioTtlSeconds, "transcription.rawAudioTtlSeconds");
428
+ }
429
+
256
430
  function validateKnowledgeConfig(value: unknown): void {
257
431
  if (value === undefined) {
258
432
  return;
@@ -381,7 +555,7 @@ function validateKnowledgeRetrieval(value: unknown): void {
381
555
  }
382
556
  }
383
557
 
384
- function validateChannels(value: unknown, runtime: AgentRuntime): void {
558
+ function validateChannels(value: unknown, runtime: AgentRuntime, hasTranscriptionConfig: boolean): void {
385
559
  if (value === undefined) {
386
560
  return;
387
561
  }
@@ -397,7 +571,7 @@ function validateChannels(value: unknown, runtime: AgentRuntime): void {
397
571
  const names = new Set<string>();
398
572
 
399
573
  for (const [index, channel] of value.entries()) {
400
- validateChannel(channel, index);
574
+ validateChannel(channel, index, hasTranscriptionConfig);
401
575
 
402
576
  if (names.has(channel.name)) {
403
577
  throw invalidConfig(`channels[${index}].name must be unique within the Agent Capsule.`);
@@ -407,7 +581,7 @@ function validateChannels(value: unknown, runtime: AgentRuntime): void {
407
581
  }
408
582
  }
409
583
 
410
- function validateChannel(value: unknown, index: number): asserts value is AgentChannel {
584
+ function validateChannel(value: unknown, index: number, hasTranscriptionConfig: boolean): asserts value is AgentChannel {
411
585
  if (!isRecord(value)) {
412
586
  throw invalidConfig(`channels[${index}] must be an object.`);
413
587
  }
@@ -432,6 +606,7 @@ function validateChannel(value: unknown, index: number): asserts value is AgentC
432
606
  validateSecretNames(value.secrets, `channels[${index}].secrets`);
433
607
  validateChannelLimits(value.limits, index);
434
608
  validateChannelBuffer(value.buffer, index);
609
+ validateChannelAudio(value.audio, index, hasTranscriptionConfig);
435
610
 
436
611
  if (value.type === "website") {
437
612
  validateWebsiteChannel(value, index);
@@ -446,6 +621,31 @@ function validateChannel(value: unknown, index: number): asserts value is AgentC
446
621
  validateWhatsappChannel(value, index);
447
622
  }
448
623
 
624
+ function validateChannelAudio(value: unknown, index: number, hasTranscriptionConfig: boolean): void {
625
+ if (value === undefined) {
626
+ return;
627
+ }
628
+
629
+ if (!isRecord(value)) {
630
+ throw invalidConfig(`channels[${index}].audio must be an object when provided.`);
631
+ }
632
+
633
+ if (!CHANNEL_AUDIO_MODES.has(value.mode as ChannelAudioMode)) {
634
+ throw invalidConfig(`channels[${index}].audio.mode must be "off", "reject", or "transcribe".`);
635
+ }
636
+
637
+ if (value.mode === "transcribe" && !hasTranscriptionConfig) {
638
+ throw invalidConfig(`channels[${index}].audio.mode "transcribe" requires top-level transcription config.`);
639
+ }
640
+
641
+ if (value.mode !== "off") {
642
+ expectString(value.fallbackMessage, `channels[${index}].audio.fallbackMessage`);
643
+ }
644
+
645
+ validateOptionalPositiveInteger(value.maxDurationSeconds, `channels[${index}].audio.maxDurationSeconds`);
646
+ validateOptionalPositiveInteger(value.maxBytes, `channels[${index}].audio.maxBytes`);
647
+ }
648
+
449
649
  function validateWebsiteChannel(value: Record<string, unknown>, index: number): void {
450
650
  if (value.provider !== "agentkit") {
451
651
  throw invalidConfig(`channels[${index}].provider must be "agentkit" when type is "website".`);
@@ -562,6 +762,16 @@ function validateSecretNames(secrets: string[], field: string): void {
562
762
  }
563
763
  }
564
764
 
765
+ function validateOptionalPositiveInteger(value: unknown, field: string): void {
766
+ if (value === undefined) {
767
+ return;
768
+ }
769
+
770
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
771
+ throw invalidConfig(`${field} must be a positive integer when provided.`);
772
+ }
773
+ }
774
+
565
775
  function expectRequiredSecrets(value: unknown, requiredSecrets: string[], field: string): void {
566
776
  const declaredSecrets = new Set(Array.isArray(value) ? value : []);
567
777
  const missingSecrets = requiredSecrets.filter((secret) => !declaredSecrets.has(secret));
@@ -2,8 +2,14 @@ import { relative, resolve } from "node:path";
2
2
 
3
3
  import type { AgentConfig, AgentTool } from "../../index";
4
4
  import type { LoadedAgentCapsule } from "../config";
5
+ import {
6
+ createManagedComposioTools,
7
+ managedComposioRequiredSecrets,
8
+ resolveManagedComposioIntegrations,
9
+ } from "../integrations/composio";
5
10
  import { resolveKnowledgeConfig } from "../knowledge/config";
6
11
  import { KNOWLEDGE_SEARCH_TOOL_NAME } from "../knowledge/tool";
12
+ import { resolveTranscriptionConfig } from "../transcription";
7
13
 
8
14
  export type ManifestSchema = {
9
15
  source: string;
@@ -27,7 +33,28 @@ export type SharedAgentManifest = {
27
33
  permissions: string[];
28
34
  timeoutMs: number | null;
29
35
  }>;
36
+ integrations: Array<{
37
+ name: string;
38
+ provider: "composio";
39
+ mode: "managed";
40
+ toolkits: string[];
41
+ tools: string[];
42
+ userId: string;
43
+ }>;
30
44
  channels: NonNullable<AgentConfig["channels"]>;
45
+ transcription: {
46
+ enabled: boolean;
47
+ provider: string | null;
48
+ model: string | null;
49
+ secret: string | null;
50
+ language: string | null;
51
+ prompt: string | null;
52
+ limits: {
53
+ maxDurationSeconds: number | null;
54
+ maxBytes: number | null;
55
+ };
56
+ rawAudioTtlSeconds: number | null;
57
+ };
31
58
  knowledge: {
32
59
  enabled: boolean;
33
60
  sources: Array<{
@@ -80,6 +107,9 @@ export function buildSharedAgentManifest(
80
107
  const database = buildDatabaseManifest(capsule, options.databaseSchema ?? null);
81
108
  const files = buildFilesManifest(capsule);
82
109
  const knowledge = resolveKnowledgeConfig(capsule.config);
110
+ const integrationTools = createManagedComposioTools(capsule.config);
111
+ const tools = [...integrationTools, ...(capsule.config.tools ?? [])];
112
+ const transcription = resolveTranscriptionConfig(capsule.config.transcription);
83
113
 
84
114
  requiredSecrets.add("AGENTKIT_PRIVATE_TOKEN");
85
115
 
@@ -100,7 +130,15 @@ export function buildSharedAgentManifest(
100
130
  requiredSecrets.add(knowledge.embedding.secret);
101
131
  }
102
132
 
103
- for (const tool of capsule.config.tools ?? []) {
133
+ if (transcription.secret) {
134
+ requiredSecrets.add(transcription.secret);
135
+ }
136
+
137
+ for (const secret of managedComposioRequiredSecrets(capsule.config)) {
138
+ requiredSecrets.add(secret);
139
+ }
140
+
141
+ for (const tool of tools) {
104
142
  for (const secret of tool.secrets ?? []) {
105
143
  requiredSecrets.add(secret);
106
144
  }
@@ -120,7 +158,7 @@ export function buildSharedAgentManifest(
120
158
  timeZone: capsule.config.timeZone ?? null,
121
159
  prompt: toCapsulePath(capsule, capsule.instructionsPath),
122
160
  secrets: Array.from(requiredSecrets).sort(),
123
- tools: (capsule.config.tools ?? []).map((tool) => ({
161
+ tools: tools.map((tool) => ({
124
162
  name: tool.name,
125
163
  description: tool.description,
126
164
  inputSchema: tool.inputSchema,
@@ -129,7 +167,28 @@ export function buildSharedAgentManifest(
129
167
  permissions: [...(tool.permissions ?? [])].sort(),
130
168
  timeoutMs: tool.timeoutMs ?? null,
131
169
  })),
170
+ integrations: resolveManagedComposioIntegrations(capsule.config).map((integration) => ({
171
+ name: integration.name,
172
+ provider: integration.provider,
173
+ mode: integration.mode,
174
+ toolkits: integration.toolkits,
175
+ tools: integration.allowedTools,
176
+ userId: integration.userId,
177
+ })),
132
178
  channels: capsule.config.channels ?? [],
179
+ transcription: {
180
+ enabled: transcription.enabled,
181
+ provider: transcription.enabled ? transcription.provider : null,
182
+ model: transcription.enabled ? transcription.model : null,
183
+ secret: transcription.secret,
184
+ language: transcription.language,
185
+ prompt: transcription.prompt,
186
+ limits: {
187
+ maxDurationSeconds: transcription.limits.maxDurationSeconds ?? null,
188
+ maxBytes: transcription.limits.maxBytes ?? null,
189
+ },
190
+ rawAudioTtlSeconds: transcription.rawAudioTtlSeconds,
191
+ },
133
192
  knowledge: {
134
193
  enabled: knowledge.enabled,
135
194
  sources: knowledge.sources.map((source) => ({
@@ -3,6 +3,10 @@ import { createHash } from "node:crypto";
3
3
  import { loadAgentCapsule } from "./config";
4
4
  import { buildSharedAgentManifest } from "./core/manifest";
5
5
  import { listCapsuleEnv } from "./env";
6
+ import {
7
+ MANAGED_COMPOSIO_API_KEY_SECRET,
8
+ resolveManagedComposioIntegrations,
9
+ } from "./integrations/composio";
6
10
 
7
11
  export type DeployReadinessSecretSource = {
8
12
  dotenv: boolean;
@@ -30,6 +34,14 @@ export type DeployReadinessContext = {
30
34
  embeddingProvider: string;
31
35
  databaseDriver: "none" | "turso";
32
36
  };
37
+ integrations: {
38
+ managedComposio: {
39
+ enabled: boolean;
40
+ toolkits: string[];
41
+ toolCount: number;
42
+ userId: string | null;
43
+ };
44
+ };
33
45
  };
34
46
 
35
47
  export async function loadDeployReadinessContext(
@@ -38,6 +50,7 @@ export async function loadDeployReadinessContext(
38
50
  ): Promise<DeployReadinessContext> {
39
51
  const capsule = await loadAgentCapsule(cwd);
40
52
  const manifest = buildSharedAgentManifest(capsule);
53
+ const managedComposio = resolveManagedComposioIntegrations(capsule.config)[0];
41
54
  const dotenvNames = new Set(await listCapsuleEnv(capsule.root));
42
55
  const requiredSecrets = manifest.secrets;
43
56
  const localSecrets: Record<string, DeployReadinessSecretSource> = {};
@@ -57,6 +70,7 @@ export async function loadDeployReadinessContext(
57
70
  isAgentKitManagedDeploySecret(name, {
58
71
  accessMode: capsule.config.access.mode,
59
72
  databaseDriver: manifest.storage.database.driver,
73
+ managedComposioEnabled: Boolean(managedComposio),
60
74
  }),
61
75
  );
62
76
  const managed = new Set(agentKitManagedSecrets);
@@ -82,6 +96,14 @@ export async function loadDeployReadinessContext(
82
96
  embeddingProvider: manifest.knowledge.embedding.provider,
83
97
  databaseDriver: manifest.storage.database.driver,
84
98
  },
99
+ integrations: {
100
+ managedComposio: {
101
+ enabled: Boolean(managedComposio),
102
+ toolkits: managedComposio?.toolkits ?? [],
103
+ toolCount: managedComposio?.allowedTools.length ?? 0,
104
+ userId: managedComposio?.userId ?? null,
105
+ },
106
+ },
85
107
  };
86
108
  }
87
109
 
@@ -91,7 +113,11 @@ export function stableCloudProjectId(name: string, accountId?: string | null): s
91
113
 
92
114
  function isAgentKitManagedDeploySecret(
93
115
  name: string,
94
- context: { accessMode: DeployReadinessContext["accessMode"]; databaseDriver: "none" | "turso" },
116
+ context: {
117
+ accessMode: DeployReadinessContext["accessMode"];
118
+ databaseDriver: "none" | "turso";
119
+ managedComposioEnabled: boolean;
120
+ },
95
121
  ): boolean {
96
122
  if (context.databaseDriver === "turso" && (name === "TURSO_DATABASE_URL" || name === "TURSO_AUTH_TOKEN")) {
97
123
  return true;
@@ -101,5 +127,9 @@ function isAgentKitManagedDeploySecret(
101
127
  return true;
102
128
  }
103
129
 
130
+ if (context.managedComposioEnabled && name === MANAGED_COMPOSIO_API_KEY_SECRET) {
131
+ return true;
132
+ }
133
+
104
134
  return false;
105
135
  }
@@ -7,7 +7,7 @@ import { Readable } from "node:stream";
7
7
 
8
8
  import { openCapsuleStore, type ConversationRecord } from "../storage/sqlite";
9
9
  import type { AgentChannel, ChannelProvider, ChannelType } from "../index";
10
- import type { ChannelAdapter, RawWebhookEvent } from "./channels";
10
+ import type { ChannelAdapter, NormalizedChannelMessage, RawWebhookEvent } from "./channels";
11
11
  import { telegramChannelAdapter } from "./channels/telegram";
12
12
  import { websiteChannelAdapter } from "./channels/website";
13
13
  import { metaWhatsappChannelAdapter } from "./channels/whatsapp-meta";
@@ -20,6 +20,7 @@ import { loadCapsuleEnv } from "./env";
20
20
  import { buildInspectState } from "./inspect";
21
21
  import { syncConfiguredKnowledgeSources } from "./knowledge/ingest";
22
22
  import { runToolFromCwd } from "./tool-runner";
23
+ import { resolveTranscriptionConfig, transcribeAudio } from "./transcription";
23
24
 
24
25
  export type AgentDevServerOptions = {
25
26
  port?: number;
@@ -515,7 +516,9 @@ async function handlePortableChannel(
515
516
 
516
517
  channelRuntime.dedupeKeys.add(event.dedupeKey);
517
518
 
518
- if (event.kind !== "message" || !event.message) {
519
+ const processedMessage = await processDevChannelMessage(capsule, adapter, channel, event.message, secrets);
520
+
521
+ if (event.kind !== "message" || !processedMessage) {
519
522
  deliveries.push({ state: "skipped", dedupeKey: event.dedupeKey, unsupportedReason: event.unsupportedReason });
520
523
  continue;
521
524
  }
@@ -531,7 +534,7 @@ async function handlePortableChannel(
531
534
  bufferDevChannelMessage(capsule, channelRuntime, {
532
535
  bufferKey: `${channel.name}:${event.externalIdentity.key}`,
533
536
  conversationId,
534
- content: event.message.content,
537
+ content: processedMessage.content,
535
538
  receivedAt: event.receivedAt,
536
539
  buffer,
537
540
  });
@@ -545,7 +548,7 @@ async function handlePortableChannel(
545
548
  }
546
549
 
547
550
  const result = await runAgentMessage(capsule, {
548
- message: event.message.content,
551
+ message: processedMessage.content,
549
552
  conversationId,
550
553
  signal: request.signal,
551
554
  });
@@ -560,6 +563,71 @@ async function handlePortableChannel(
560
563
  return jsonResponse({ deliveries });
561
564
  }
562
565
 
566
+ async function processDevChannelMessage(
567
+ capsule: LoadedAgentCapsule,
568
+ adapter: ChannelAdapter,
569
+ channel: AgentChannel,
570
+ message: NormalizedChannelMessage | undefined,
571
+ secrets: Record<string, string>,
572
+ ): Promise<NormalizedChannelMessage | null> {
573
+ if (!message) {
574
+ return null;
575
+ }
576
+
577
+ if (message.contentType === "text" && message.content) {
578
+ return message;
579
+ }
580
+
581
+ if (message.contentType !== "audio" || !message.audio || channel.audio?.mode !== "transcribe") {
582
+ return null;
583
+ }
584
+
585
+ if (!adapter.downloadMedia) {
586
+ return null;
587
+ }
588
+
589
+ const env = await loadCapsuleEnv(capsule.root);
590
+ const transcriptionConfig = resolveTranscriptionConfig(capsule.config.transcription);
591
+ const secretName = transcriptionConfig.secret;
592
+ const download = await adapter.downloadMedia({
593
+ type: channel.type,
594
+ provider: channel.provider,
595
+ channelId: channel.name,
596
+ message,
597
+ secrets: {
598
+ ...secrets,
599
+ ...(secretName ? { [secretName]: env[secretName] ?? "" } : {}),
600
+ ...(env.AGENTKIT_TELEGRAM_API_BASE_URL ? { AGENTKIT_TELEGRAM_API_BASE_URL: env.AGENTKIT_TELEGRAM_API_BASE_URL } : {}),
601
+ },
602
+ });
603
+
604
+ if (!download.ok) {
605
+ return null;
606
+ }
607
+
608
+ const transcription = await transcribeAudio({
609
+ config: transcriptionConfig,
610
+ secrets: {
611
+ ...(secretName ? { [secretName]: env[secretName] ?? "" } : {}),
612
+ },
613
+ audio: download.audio,
614
+ filename: download.filename,
615
+ ...(download.mimeType ? { mimeType: download.mimeType } : {}),
616
+ ...(download.durationSeconds !== undefined ? { durationSeconds: download.durationSeconds } : {}),
617
+ });
618
+
619
+ if (!transcription.ok) {
620
+ return null;
621
+ }
622
+
623
+ return {
624
+ role: "user",
625
+ contentType: "text",
626
+ content: `The user sent an audio message.\n\nTranscript:\n${transcription.text}`,
627
+ providerMessageId: message.providerMessageId,
628
+ };
629
+ }
630
+
563
631
  function bufferDevChannelMessage(
564
632
  capsule: LoadedAgentCapsule,
565
633
  runtime: DevChannelRuntimeState,