@andreprado/agentkit 0.1.0-alpha.9 → 0.1.1
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 +904 -0
- package/src/runtime/channels/generic-webhook.ts +682 -0
- package/src/runtime/channels/net-guard.ts +480 -0
- package/src/runtime/channels/provider-fetch.ts +54 -0
- package/src/runtime/channels/slack.ts +652 -0
- package/src/runtime/channels/telegram.ts +379 -15
- package/src/runtime/channels/whatsapp-evolution.ts +1330 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1192 -0
- package/src/runtime/channels/whatsapp-zapster.ts +702 -40
- package/src/runtime/channels.ts +83 -3
- package/src/runtime/chat.ts +70 -44
- package/src/runtime/config.ts +512 -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/embeddings.ts +45 -7
- package/src/runtime/knowledge/ingest.ts +69 -6
- package/src/runtime/knowledge/retrieve.ts +25 -5
- package/src/runtime/knowledge/schema.ts +45 -1
- package/src/runtime/knowledge/vector.ts +30 -30
- 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/config.ts
CHANGED
|
@@ -13,25 +13,41 @@ import type {
|
|
|
13
13
|
AgentChannel,
|
|
14
14
|
ChannelProvider,
|
|
15
15
|
ChannelType,
|
|
16
|
+
DiscordChannelMode,
|
|
16
17
|
TelegramAllowedUpdate,
|
|
17
18
|
AgentKnowledgeEmbeddingProvider,
|
|
18
19
|
ChannelBufferMode,
|
|
20
|
+
AgentIntegration,
|
|
21
|
+
ChannelAudioMode,
|
|
22
|
+
TranscriptionProviderName,
|
|
23
|
+
ChannelReplyRouteConfig,
|
|
19
24
|
} from "../index";
|
|
25
|
+
import { MANAGED_COMPOSIO_TOOL_NAME } from "./integrations/composio";
|
|
20
26
|
import { AgentKitError } from "./errors";
|
|
27
|
+
import { isValidTimeZone } from "./prompt-context";
|
|
21
28
|
|
|
22
29
|
const CONFIG_FILE = "agentkit.config.ts";
|
|
23
30
|
const AGENT_RUNTIMES = new Set<AgentRuntime>(["local", "edge"]);
|
|
24
|
-
const PROVIDERS = new Set<AgentProviderName>(["test", "openai", "anthropic", "openrouter", "custom"]);
|
|
31
|
+
const PROVIDERS = new Set<AgentProviderName>(["test", "openai", "openai-codex", "anthropic", "openrouter", "opencode", "opencode-go", "custom"]);
|
|
25
32
|
const ACCESS_MODES = new Set<AccessMode>(["private", "token", "public"]);
|
|
26
33
|
const TOOL_VISIBILITIES = new Set<ToolVisibility>(["user", "internal"]);
|
|
27
|
-
const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp"]);
|
|
28
|
-
const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta"]);
|
|
34
|
+
const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp", "discord", "slack", "webhook"]);
|
|
35
|
+
const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta", "uazapi", "evolution", "discord", "slack", "generic"]);
|
|
29
36
|
const CHANNEL_BUFFER_MODES = new Set<ChannelBufferMode>(["off", "debounce"]);
|
|
37
|
+
const CHANNEL_AUDIO_MODES = new Set<ChannelAudioMode>(["off", "reject", "transcribe"]);
|
|
38
|
+
const DISCORD_CHANNEL_MODES = new Set<DiscordChannelMode>(["interactions", "bot"]);
|
|
30
39
|
const TELEGRAM_ALLOWED_UPDATES = new Set<TelegramAllowedUpdate>(["message"]);
|
|
31
40
|
const KNOWLEDGE_EMBEDDING_PROVIDERS = new Set<AgentKnowledgeEmbeddingProvider>(["none", "test", "openai"]);
|
|
41
|
+
const TRANSCRIPTION_PROVIDERS = new Set<TranscriptionProviderName>(["test", "openai", "groq"]);
|
|
32
42
|
const CHANNEL_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
43
|
+
const CHANNEL_REPLY_PATH_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
44
|
+
const INTEGRATION_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
45
|
+
const COMPOSIO_TOOLKIT_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
46
|
+
const COMPOSIO_TOOL_SLUG_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
33
47
|
const SECRET_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
48
|
+
const HTTP_HEADER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]*$/;
|
|
34
49
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
let configImportNonce = 0;
|
|
35
51
|
|
|
36
52
|
export type LoadedAgentCapsule = {
|
|
37
53
|
root: string;
|
|
@@ -107,16 +123,37 @@ function resolveLocalStoragePath(root: string, config: AgentConfig): string | un
|
|
|
107
123
|
return undefined;
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
// Every import of the config goes through a uniquely named temp module, and
|
|
127
|
+
// Node never evicts imported modules. Re-importing on each request (the dev and
|
|
128
|
+
// container servers load the capsule per request) grows memory without bound,
|
|
129
|
+
// so reuse the import while the config source is unchanged. Edits still reload.
|
|
130
|
+
const configModuleCache = new Map<string, { source: string; value: Promise<unknown> }>();
|
|
131
|
+
|
|
110
132
|
async function loadConfigModule(configPath: string): Promise<unknown> {
|
|
111
|
-
const
|
|
112
|
-
|
|
133
|
+
const source = await readFile(configPath, "utf8");
|
|
134
|
+
const cached = configModuleCache.get(configPath);
|
|
135
|
+
|
|
136
|
+
if (cached?.source === source) {
|
|
137
|
+
return cached.value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const value = importConfigModule(configPath, source);
|
|
141
|
+
configModuleCache.set(configPath, { source, value });
|
|
142
|
+
value.catch(() => {
|
|
143
|
+
if (configModuleCache.get(configPath)?.value === value) {
|
|
144
|
+
configModuleCache.delete(configPath);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
113
150
|
|
|
151
|
+
async function importConfigModule(configPath: string, source: string): Promise<unknown> {
|
|
114
152
|
try {
|
|
115
|
-
|
|
116
|
-
return module.default;
|
|
153
|
+
return await importConfigSource(configPath, source);
|
|
117
154
|
} catch (error) {
|
|
118
155
|
if (isMissingAgentKitSelfImport(error)) {
|
|
119
|
-
return loadConfigModuleWithLocalSelfImport(configPath, error);
|
|
156
|
+
return loadConfigModuleWithLocalSelfImport(configPath, source, error);
|
|
120
157
|
}
|
|
121
158
|
|
|
122
159
|
throw new AgentKitError(
|
|
@@ -127,8 +164,11 @@ async function loadConfigModule(configPath: string): Promise<unknown> {
|
|
|
127
164
|
}
|
|
128
165
|
}
|
|
129
166
|
|
|
130
|
-
async function loadConfigModuleWithLocalSelfImport(
|
|
131
|
-
|
|
167
|
+
async function loadConfigModuleWithLocalSelfImport(
|
|
168
|
+
configPath: string,
|
|
169
|
+
source: string,
|
|
170
|
+
originalError: unknown,
|
|
171
|
+
): Promise<unknown> {
|
|
132
172
|
const localAgentKitUrl = pathToFileURL(resolve(moduleDir, "../index.ts")).href;
|
|
133
173
|
const patchedSource = source.replace(
|
|
134
174
|
/from\s+["']@andreprado\/agentkit["']/g,
|
|
@@ -143,15 +183,20 @@ async function loadConfigModuleWithLocalSelfImport(configPath: string, originalE
|
|
|
143
183
|
);
|
|
144
184
|
}
|
|
145
185
|
|
|
186
|
+
return importConfigSource(configPath, patchedSource);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function importConfigSource(configPath: string, source: string): Promise<unknown> {
|
|
190
|
+
const nonce = nextConfigImportNonce();
|
|
146
191
|
const tempConfigPath = join(
|
|
147
192
|
dirname(configPath),
|
|
148
|
-
`.agentkit.config.loader-${process.pid}-${
|
|
193
|
+
`.agentkit.config.loader-${process.pid}-${nonce}-${Math.random().toString(16).slice(2)}.ts`,
|
|
149
194
|
);
|
|
150
195
|
|
|
151
196
|
try {
|
|
152
|
-
await writeFile(tempConfigPath,
|
|
197
|
+
await writeFile(tempConfigPath, source);
|
|
153
198
|
const configUrl = pathToFileURL(tempConfigPath);
|
|
154
|
-
configUrl.searchParams.set("t",
|
|
199
|
+
configUrl.searchParams.set("t", nonce);
|
|
155
200
|
const module = (await import(configUrl.href)) as { default?: unknown };
|
|
156
201
|
return module.default;
|
|
157
202
|
} catch (error) {
|
|
@@ -165,6 +210,11 @@ async function loadConfigModuleWithLocalSelfImport(configPath: string, originalE
|
|
|
165
210
|
}
|
|
166
211
|
}
|
|
167
212
|
|
|
213
|
+
function nextConfigImportNonce(): string {
|
|
214
|
+
configImportNonce += 1;
|
|
215
|
+
return `${Date.now()}-${configImportNonce}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
168
218
|
function isMissingAgentKitSelfImport(error: unknown): boolean {
|
|
169
219
|
const message = error instanceof Error ? error.message : String(error);
|
|
170
220
|
return message.includes("@andreprado/agentkit");
|
|
@@ -195,8 +245,17 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
|
|
|
195
245
|
throw invalidConfig('provider.model must be "fake" when provider.name is "test".');
|
|
196
246
|
}
|
|
197
247
|
|
|
248
|
+
if (value.timeZone !== undefined) {
|
|
249
|
+
expectString(value.timeZone, "timeZone");
|
|
250
|
+
|
|
251
|
+
if (!isValidTimeZone(value.timeZone)) {
|
|
252
|
+
throw invalidConfig('timeZone must be a valid IANA time zone such as "UTC" or "America/New_York".');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
198
256
|
expectString(value.instructions, "instructions");
|
|
199
257
|
expectStringArray(value.secrets, "secrets");
|
|
258
|
+
validateSecretNames(value.secrets, "secrets");
|
|
200
259
|
|
|
201
260
|
if (value.tools !== undefined) {
|
|
202
261
|
if (!Array.isArray(value.tools)) {
|
|
@@ -208,7 +267,9 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
|
|
|
208
267
|
}
|
|
209
268
|
}
|
|
210
269
|
|
|
211
|
-
|
|
270
|
+
validateIntegrations(value.integrations, value.runtime as AgentRuntime, value.tools);
|
|
271
|
+
validateTranscriptionConfig(value.transcription);
|
|
272
|
+
validateChannels(value.channels, value.runtime as AgentRuntime, Boolean(value.transcription));
|
|
212
273
|
validateKnowledgeConfig(value.knowledge);
|
|
213
274
|
|
|
214
275
|
if (!isRecord(value.access) || !ACCESS_MODES.has(value.access.mode as AccessMode)) {
|
|
@@ -227,10 +288,181 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
|
|
|
227
288
|
throw invalidConfig("storage.path must be a string when provided.");
|
|
228
289
|
}
|
|
229
290
|
|
|
291
|
+
if (value.storage.driver === "sqlite" && typeof value.storage.path !== "string") {
|
|
292
|
+
throw invalidConfig('storage.path is required when storage.driver is "sqlite".');
|
|
293
|
+
}
|
|
294
|
+
|
|
230
295
|
validateDatabaseConfig(value.storage.database);
|
|
231
296
|
validateFilesConfig(value.storage.files);
|
|
232
297
|
}
|
|
233
298
|
|
|
299
|
+
function validateIntegrations(
|
|
300
|
+
value: unknown,
|
|
301
|
+
runtime: AgentRuntime,
|
|
302
|
+
tools: AgentConfig["tools"] = [],
|
|
303
|
+
): void {
|
|
304
|
+
if (value === undefined) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!Array.isArray(value)) {
|
|
309
|
+
throw invalidConfig("integrations must be an array when provided.");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (value.length > 0 && runtime !== "edge") {
|
|
313
|
+
throw invalidConfig('managed integrations require runtime: "edge". Use BYO defineTool wrappers for local-only integrations.');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let composioCount = 0;
|
|
317
|
+
|
|
318
|
+
for (const [index, integration] of value.entries()) {
|
|
319
|
+
if (!isRecord(integration)) {
|
|
320
|
+
throw invalidConfig(`integrations[${index}] must be an object.`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (integration.provider !== "composio" || integration.type !== "composio" || integration.mode !== "managed") {
|
|
324
|
+
throw invalidConfig(`integrations[${index}] must be composioManaged({...}) or { type: "composio", provider: "composio", mode: "managed", ... }.`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
composioCount += 1;
|
|
328
|
+
validateComposioManagedIntegration(integration as unknown as AgentIntegration, index);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (composioCount > 1) {
|
|
332
|
+
throw invalidConfig("Only one managed Composio integration is supported per agent.");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const hasExplicitComposioTools = value.some(
|
|
336
|
+
(integration) => isRecord(integration) && integration.provider === "composio" && isRecord(integration.tools),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (hasExplicitComposioTools && tools.some((tool) => isRecord(tool) && tool.name === MANAGED_COMPOSIO_TOOL_NAME)) {
|
|
340
|
+
throw invalidConfig(`tools must not declare reserved managed Composio tool name "${MANAGED_COMPOSIO_TOOL_NAME}".`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function validateComposioManagedIntegration(value: AgentIntegration, index: number): void {
|
|
345
|
+
expectString(value.name, `integrations[${index}].name`);
|
|
346
|
+
|
|
347
|
+
if (!INTEGRATION_NAME_PATTERN.test(value.name)) {
|
|
348
|
+
throw invalidConfig(`integrations[${index}].name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!Array.isArray(value.toolkits) || value.toolkits.length === 0) {
|
|
352
|
+
throw invalidConfig(`integrations[${index}].toolkits must include at least one Composio toolkit slug.`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const toolkits = new Set<string>();
|
|
356
|
+
|
|
357
|
+
for (const [toolkitIndex, toolkit] of value.toolkits.entries()) {
|
|
358
|
+
expectString(toolkit, `integrations[${index}].toolkits[${toolkitIndex}]`);
|
|
359
|
+
|
|
360
|
+
if (!COMPOSIO_TOOLKIT_PATTERN.test(toolkit)) {
|
|
361
|
+
throw invalidConfig(`integrations[${index}].toolkits[${toolkitIndex}] must be a lowercase Composio toolkit slug.`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (toolkits.has(toolkit)) {
|
|
365
|
+
throw invalidConfig(`integrations[${index}].toolkits must not contain duplicates.`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
toolkits.add(toolkit);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (value.tools !== undefined) {
|
|
372
|
+
if (!isRecord(value.tools)) {
|
|
373
|
+
throw invalidConfig(`integrations[${index}].tools must be an object when provided.`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (const [toolkit, tools] of Object.entries(value.tools)) {
|
|
377
|
+
if (!toolkits.has(toolkit)) {
|
|
378
|
+
throw invalidConfig(`integrations[${index}].tools.${toolkit} must reference a toolkit listed in integrations[${index}].toolkits.`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
382
|
+
throw invalidConfig(`integrations[${index}].tools.${toolkit} must include at least one Composio action slug.`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const seenTools = new Set<string>();
|
|
386
|
+
|
|
387
|
+
for (const [toolIndex, tool] of tools.entries()) {
|
|
388
|
+
expectString(tool, `integrations[${index}].tools.${toolkit}[${toolIndex}]`);
|
|
389
|
+
|
|
390
|
+
if (!COMPOSIO_TOOL_SLUG_PATTERN.test(tool)) {
|
|
391
|
+
throw invalidConfig(`integrations[${index}].tools.${toolkit}[${toolIndex}] must be an uppercase Composio action slug.`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (seenTools.has(tool)) {
|
|
395
|
+
throw invalidConfig(`integrations[${index}].tools.${toolkit} must not contain duplicates.`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
seenTools.add(tool);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (value.tags !== undefined) {
|
|
404
|
+
validateIntegrationTags(value.tags, `integrations[${index}].tags`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (value.confirmExternalWrites !== undefined && typeof value.confirmExternalWrites !== "boolean") {
|
|
408
|
+
throw invalidConfig(`integrations[${index}].confirmExternalWrites must be a boolean when provided.`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function validateIntegrationTags(value: unknown, label: string): void {
|
|
413
|
+
if (!isRecord(value)) {
|
|
414
|
+
throw invalidConfig(`${label} must be an object when provided.`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (value.enable !== undefined) {
|
|
418
|
+
expectStringArray(value.enable, `${label}.enable`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (value.disable !== undefined) {
|
|
422
|
+
expectStringArray(value.disable, `${label}.disable`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function validateTranscriptionConfig(value: unknown): void {
|
|
427
|
+
if (value === undefined) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!isRecord(value)) {
|
|
432
|
+
throw invalidConfig("transcription must be an object when provided.");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!TRANSCRIPTION_PROVIDERS.has(value.provider as TranscriptionProviderName)) {
|
|
436
|
+
throw invalidConfig(`transcription.provider must be one of: ${Array.from(TRANSCRIPTION_PROVIDERS).join(", ")}.`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
expectString(value.model, "transcription.model");
|
|
440
|
+
|
|
441
|
+
if (value.secret !== undefined) {
|
|
442
|
+
expectString(value.secret, "transcription.secret");
|
|
443
|
+
validateSecretNames([value.secret], "transcription.secret");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (value.language !== undefined) {
|
|
447
|
+
expectString(value.language, "transcription.language");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (value.prompt !== undefined) {
|
|
451
|
+
expectString(value.prompt, "transcription.prompt");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (value.limits !== undefined) {
|
|
455
|
+
if (!isRecord(value.limits)) {
|
|
456
|
+
throw invalidConfig("transcription.limits must be an object when provided.");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
validateOptionalPositiveInteger(value.limits.maxDurationSeconds, "transcription.limits.maxDurationSeconds");
|
|
460
|
+
validateOptionalPositiveInteger(value.limits.maxBytes, "transcription.limits.maxBytes");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
validateOptionalPositiveInteger(value.rawAudioTtlSeconds, "transcription.rawAudioTtlSeconds");
|
|
464
|
+
}
|
|
465
|
+
|
|
234
466
|
function validateKnowledgeConfig(value: unknown): void {
|
|
235
467
|
if (value === undefined) {
|
|
236
468
|
return;
|
|
@@ -359,7 +591,7 @@ function validateKnowledgeRetrieval(value: unknown): void {
|
|
|
359
591
|
}
|
|
360
592
|
}
|
|
361
593
|
|
|
362
|
-
function validateChannels(value: unknown, runtime: AgentRuntime): void {
|
|
594
|
+
function validateChannels(value: unknown, runtime: AgentRuntime, hasTranscriptionConfig: boolean): void {
|
|
363
595
|
if (value === undefined) {
|
|
364
596
|
return;
|
|
365
597
|
}
|
|
@@ -375,7 +607,7 @@ function validateChannels(value: unknown, runtime: AgentRuntime): void {
|
|
|
375
607
|
const names = new Set<string>();
|
|
376
608
|
|
|
377
609
|
for (const [index, channel] of value.entries()) {
|
|
378
|
-
validateChannel(channel, index);
|
|
610
|
+
validateChannel(channel, index, hasTranscriptionConfig);
|
|
379
611
|
|
|
380
612
|
if (names.has(channel.name)) {
|
|
381
613
|
throw invalidConfig(`channels[${index}].name must be unique within the Agent Capsule.`);
|
|
@@ -383,9 +615,13 @@ function validateChannels(value: unknown, runtime: AgentRuntime): void {
|
|
|
383
615
|
|
|
384
616
|
names.add(channel.name);
|
|
385
617
|
}
|
|
618
|
+
|
|
619
|
+
for (const [index, channel] of value.entries()) {
|
|
620
|
+
validateChannelReplyRoute(channel, index, names);
|
|
621
|
+
}
|
|
386
622
|
}
|
|
387
623
|
|
|
388
|
-
function validateChannel(value: unknown, index: number): asserts value is AgentChannel {
|
|
624
|
+
function validateChannel(value: unknown, index: number, hasTranscriptionConfig: boolean): asserts value is AgentChannel {
|
|
389
625
|
if (!isRecord(value)) {
|
|
390
626
|
throw invalidConfig(`channels[${index}] must be an object.`);
|
|
391
627
|
}
|
|
@@ -408,9 +644,27 @@ function validateChannel(value: unknown, index: number): asserts value is AgentC
|
|
|
408
644
|
|
|
409
645
|
expectStringArray(value.secrets, `channels[${index}].secrets`);
|
|
410
646
|
validateSecretNames(value.secrets, `channels[${index}].secrets`);
|
|
647
|
+
validateChannelReplyTo(value.replyTo, index);
|
|
411
648
|
validateChannelLimits(value.limits, index);
|
|
412
649
|
validateChannelBuffer(value.buffer, index);
|
|
413
650
|
|
|
651
|
+
if (value.type === "discord") {
|
|
652
|
+
validateDiscordChannel(value, index);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (value.type === "slack") {
|
|
657
|
+
validateSlackChannel(value, index);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (value.type === "webhook") {
|
|
662
|
+
validateGenericWebhookChannel(value, index);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
validateChannelAudio(value.audio, index, hasTranscriptionConfig);
|
|
667
|
+
|
|
414
668
|
if (value.type === "website") {
|
|
415
669
|
validateWebsiteChannel(value, index);
|
|
416
670
|
return;
|
|
@@ -424,6 +678,31 @@ function validateChannel(value: unknown, index: number): asserts value is AgentC
|
|
|
424
678
|
validateWhatsappChannel(value, index);
|
|
425
679
|
}
|
|
426
680
|
|
|
681
|
+
function validateChannelAudio(value: unknown, index: number, hasTranscriptionConfig: boolean): void {
|
|
682
|
+
if (value === undefined) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (!isRecord(value)) {
|
|
687
|
+
throw invalidConfig(`channels[${index}].audio must be an object when provided.`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (!CHANNEL_AUDIO_MODES.has(value.mode as ChannelAudioMode)) {
|
|
691
|
+
throw invalidConfig(`channels[${index}].audio.mode must be "off", "reject", or "transcribe".`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (value.mode === "transcribe" && !hasTranscriptionConfig) {
|
|
695
|
+
throw invalidConfig(`channels[${index}].audio.mode "transcribe" requires top-level transcription config.`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (value.mode !== "off") {
|
|
699
|
+
expectString(value.fallbackMessage, `channels[${index}].audio.fallbackMessage`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
validateOptionalPositiveInteger(value.maxDurationSeconds, `channels[${index}].audio.maxDurationSeconds`);
|
|
703
|
+
validateOptionalPositiveInteger(value.maxBytes, `channels[${index}].audio.maxBytes`);
|
|
704
|
+
}
|
|
705
|
+
|
|
427
706
|
function validateWebsiteChannel(value: Record<string, unknown>, index: number): void {
|
|
428
707
|
if (value.provider !== "agentkit") {
|
|
429
708
|
throw invalidConfig(`channels[${index}].provider must be "agentkit" when type is "website".`);
|
|
@@ -456,11 +735,15 @@ function validateTelegramChannel(value: Record<string, unknown>, index: number):
|
|
|
456
735
|
|
|
457
736
|
function validateWhatsappChannel(value: Record<string, unknown>, index: number): void {
|
|
458
737
|
if (value.type !== "whatsapp") {
|
|
459
|
-
throw invalidConfig(`channels[${index}].type must be "website", "telegram", or "
|
|
738
|
+
throw invalidConfig(`channels[${index}].type must be "website", "telegram", "whatsapp", "discord", "slack", or "webhook".`);
|
|
460
739
|
}
|
|
461
740
|
|
|
462
741
|
if (value.provider === "zapster") {
|
|
463
|
-
expectRequiredSecrets(
|
|
742
|
+
expectRequiredSecrets(
|
|
743
|
+
value.secrets,
|
|
744
|
+
["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"],
|
|
745
|
+
`channels[${index}]`,
|
|
746
|
+
);
|
|
464
747
|
return;
|
|
465
748
|
}
|
|
466
749
|
|
|
@@ -473,7 +756,205 @@ function validateWhatsappChannel(value: Record<string, unknown>, index: number):
|
|
|
473
756
|
return;
|
|
474
757
|
}
|
|
475
758
|
|
|
476
|
-
|
|
759
|
+
if (value.provider === "uazapi") {
|
|
760
|
+
expectRequiredSecrets(
|
|
761
|
+
value.secrets,
|
|
762
|
+
["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"],
|
|
763
|
+
`channels[${index}]`,
|
|
764
|
+
);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (value.provider === "evolution") {
|
|
769
|
+
expectRequiredSecrets(
|
|
770
|
+
value.secrets,
|
|
771
|
+
["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"],
|
|
772
|
+
`channels[${index}]`,
|
|
773
|
+
);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
throw invalidConfig(`channels[${index}].provider must be "zapster", "meta", "uazapi", or "evolution" when type is "whatsapp".`);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function validateDiscordChannel(value: Record<string, unknown>, index: number): void {
|
|
781
|
+
if (value.provider !== "discord") {
|
|
782
|
+
throw invalidConfig(`channels[${index}].provider must be "discord" when type is "discord".`);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (value.mode !== undefined && !DISCORD_CHANNEL_MODES.has(value.mode as DiscordChannelMode)) {
|
|
786
|
+
throw invalidConfig(`channels[${index}].mode must be "interactions" or "bot" when type is "discord".`);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (value.audio !== undefined) {
|
|
790
|
+
throw invalidConfig(`channels[${index}].audio is not supported when type is "discord".`);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
expectRequiredSecrets(
|
|
794
|
+
value.secrets,
|
|
795
|
+
value.mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"],
|
|
796
|
+
`channels[${index}]`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function validateSlackChannel(value: Record<string, unknown>, index: number): void {
|
|
801
|
+
if (value.provider !== "slack") {
|
|
802
|
+
throw invalidConfig(`channels[${index}].provider must be "slack" when type is "slack".`);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (value.audio !== undefined) {
|
|
806
|
+
throw invalidConfig(`channels[${index}].audio is not supported when type is "slack".`);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
expectRequiredSecrets(value.secrets, ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], `channels[${index}]`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function validateGenericWebhookChannel(value: Record<string, unknown>, index: number): void {
|
|
813
|
+
if (value.provider !== "generic") {
|
|
814
|
+
throw invalidConfig(`channels[${index}].provider must be "generic" when type is "webhook".`);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (value.audio !== undefined) {
|
|
818
|
+
throw invalidConfig(`channels[${index}].audio is not supported when type is "webhook".`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (value.direction !== undefined && value.direction !== "inbound" && value.direction !== "outbound") {
|
|
822
|
+
throw invalidConfig(`channels[${index}].direction must be "inbound" or "outbound" when type is "webhook".`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (value.direction === "outbound") {
|
|
826
|
+
if (value.buffer !== undefined) {
|
|
827
|
+
throw invalidConfig(`channels[${index}].buffer is not supported for outbound webhook channels.`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (value.replyTo !== undefined) {
|
|
831
|
+
throw invalidConfig(`channels[${index}].replyTo is not supported for outbound webhook channels.`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
expectString(value.urlSecret, `channels[${index}].urlSecret`);
|
|
835
|
+
validateSecretNames([value.urlSecret], `channels[${index}].urlSecret`);
|
|
836
|
+
validateGenericWebhookOutputAuth(value.auth, index);
|
|
837
|
+
expectRequiredSecrets(
|
|
838
|
+
value.secrets,
|
|
839
|
+
[value.urlSecret, ...genericWebhookOutputAuthSecrets(value.auth)],
|
|
840
|
+
`channels[${index}]`,
|
|
841
|
+
);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (value.urlSecret !== undefined || value.auth !== undefined) {
|
|
846
|
+
throw invalidConfig(`channels[${index}].urlSecret and auth are only supported for outbound webhook channels.`);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
expectRequiredSecrets(value.secrets, ["AGENTKIT_WEBHOOK_SECRET"], `channels[${index}]`);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function validateChannelReplyTo(value: unknown, index: number): void {
|
|
853
|
+
if (value === undefined) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (!isRecord(value)) {
|
|
858
|
+
throw invalidConfig(`channels[${index}].replyTo must be an object when provided.`);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
expectString(value.channel, `channels[${index}].replyTo.channel`);
|
|
862
|
+
|
|
863
|
+
if (!CHANNEL_NAME_PATTERN.test(value.channel)) {
|
|
864
|
+
throw invalidConfig(`channels[${index}].replyTo.channel must be a valid channel name.`);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (value.recipientFrom !== undefined) {
|
|
868
|
+
expectString(value.recipientFrom, `channels[${index}].replyTo.recipientFrom`);
|
|
869
|
+
|
|
870
|
+
if (!CHANNEL_REPLY_PATH_PATTERN.test(String(value.recipientFrom).replace(/^payload\./, ""))) {
|
|
871
|
+
throw invalidConfig(`channels[${index}].replyTo.recipientFrom must be a dotted JSON field path.`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function validateChannelReplyRoute(
|
|
877
|
+
channel: unknown,
|
|
878
|
+
index: number,
|
|
879
|
+
names: Set<string>,
|
|
880
|
+
): void {
|
|
881
|
+
if (!isRecord(channel) || !isRecord(channel.replyTo)) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const replyTo = channel.replyTo as ChannelReplyRouteConfig;
|
|
886
|
+
|
|
887
|
+
if (!names.has(replyTo.channel)) {
|
|
888
|
+
throw invalidConfig(`channels[${index}].replyTo.channel must name another channel in this Agent Capsule.`);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (replyTo.channel === channel.name) {
|
|
892
|
+
throw invalidConfig(`channels[${index}].replyTo.channel must not point back to the same channel.`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function validateGenericWebhookOutputAuth(value: unknown, index: number): void {
|
|
897
|
+
if (!isRecord(value)) {
|
|
898
|
+
throw invalidConfig(`channels[${index}].auth must be an object for outbound webhook channels.`);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (value.type === "none") {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (value.type === "bearer") {
|
|
906
|
+
expectString(value.tokenSecret, `channels[${index}].auth.tokenSecret`);
|
|
907
|
+
validateSecretNames([value.tokenSecret], `channels[${index}].auth.tokenSecret`);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (value.type === "header") {
|
|
912
|
+
expectString(value.headerName, `channels[${index}].auth.headerName`);
|
|
913
|
+
|
|
914
|
+
if (!HTTP_HEADER_NAME_PATTERN.test(value.headerName)) {
|
|
915
|
+
throw invalidConfig(`channels[${index}].auth.headerName must be an HTTP header name.`);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
expectString(value.valueSecret, `channels[${index}].auth.valueSecret`);
|
|
919
|
+
validateSecretNames([value.valueSecret], `channels[${index}].auth.valueSecret`);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (value.type === "hmac") {
|
|
924
|
+
if (value.headerName !== undefined) {
|
|
925
|
+
expectString(value.headerName, `channels[${index}].auth.headerName`);
|
|
926
|
+
|
|
927
|
+
if (!HTTP_HEADER_NAME_PATTERN.test(value.headerName)) {
|
|
928
|
+
throw invalidConfig(`channels[${index}].auth.headerName must be an HTTP header name.`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
expectString(value.secret, `channels[${index}].auth.secret`);
|
|
933
|
+
validateSecretNames([value.secret], `channels[${index}].auth.secret`);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
throw invalidConfig(`channels[${index}].auth.type must be "none", "bearer", "header", or "hmac".`);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function genericWebhookOutputAuthSecrets(value: unknown): string[] {
|
|
941
|
+
if (!isRecord(value)) {
|
|
942
|
+
return [];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (value.type === "bearer" && typeof value.tokenSecret === "string") {
|
|
946
|
+
return [value.tokenSecret];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (value.type === "header" && typeof value.valueSecret === "string") {
|
|
950
|
+
return [value.valueSecret];
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (value.type === "hmac" && typeof value.secret === "string") {
|
|
954
|
+
return [value.secret];
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return [];
|
|
477
958
|
}
|
|
478
959
|
|
|
479
960
|
function validateChannelLimits(value: unknown, index: number): void {
|
|
@@ -536,6 +1017,16 @@ function validateSecretNames(secrets: string[], field: string): void {
|
|
|
536
1017
|
}
|
|
537
1018
|
}
|
|
538
1019
|
|
|
1020
|
+
function validateOptionalPositiveInteger(value: unknown, field: string): void {
|
|
1021
|
+
if (value === undefined) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1026
|
+
throw invalidConfig(`${field} must be a positive integer when provided.`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
539
1030
|
function expectRequiredSecrets(value: unknown, requiredSecrets: string[], field: string): void {
|
|
540
1031
|
const declaredSecrets = new Set(Array.isArray(value) ? value : []);
|
|
541
1032
|
const missingSecrets = requiredSecrets.filter((secret) => !declaredSecrets.has(secret));
|
|
@@ -620,6 +1111,7 @@ function validateTool(value: unknown, index: number): asserts value is AgentTool
|
|
|
620
1111
|
|
|
621
1112
|
if (value.secrets !== undefined) {
|
|
622
1113
|
expectStringArray(value.secrets, `tools[${index}].secrets`);
|
|
1114
|
+
validateSecretNames(value.secrets, `tools[${index}].secrets`);
|
|
623
1115
|
}
|
|
624
1116
|
|
|
625
1117
|
if (value.permissions !== undefined) {
|