@keygraph/shannon 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -76,59 +76,30 @@ function isTemporalReady() {
76
76
  "localhost:7233"
77
77
  ]).includes("SERVING");
78
78
  }
79
- /** Check if the router container is running and healthy. */
80
- function isRouterReady() {
81
- return runOutput("docker", [
82
- "inspect",
83
- "--format",
84
- "{{.State.Health.Status}}",
85
- "shannon-router"
86
- ]) === "healthy";
87
- }
88
79
  /**
89
- * Ensure Temporal (and optionally router) are running via compose.
90
- * If Temporal is already up but router is needed and missing, starts router only.
80
+ * Ensure Temporal is running via compose.
91
81
  */
92
- async function ensureInfra(useRouter) {
93
- const temporalReady = isTemporalReady();
94
- const routerNeeded = useRouter && !isRouterReady();
95
- if (temporalReady && !routerNeeded) return;
96
- const composeArgs = [
82
+ async function ensureInfra() {
83
+ if (isTemporalReady()) return;
84
+ const composeFile = getComposeFile();
85
+ console.log("Starting Shannon infrastructure...");
86
+ execFileSync("docker", [
97
87
  "compose",
98
88
  "-f",
99
- getComposeFile()
100
- ];
101
- if (useRouter) composeArgs.push("--profile", "router");
102
- composeArgs.push("up", "-d");
103
- if (temporalReady && routerNeeded) console.log("Starting router...");
104
- else console.log("Starting Shannon infrastructure...");
105
- execFileSync("docker", composeArgs, { stdio: "inherit" });
106
- if (!temporalReady) {
107
- console.log("Waiting for Temporal to be ready...");
108
- for (let i = 0; i < 30; i++) {
109
- if (isTemporalReady()) {
110
- console.log("Temporal is ready!");
111
- break;
112
- }
113
- if (i === 29) {
114
- console.error("Timeout waiting for Temporal");
115
- process.exit(1);
116
- }
117
- await setTimeout$1(2e3);
118
- }
119
- }
120
- if (routerNeeded) {
121
- console.log("Waiting for router to be ready...");
122
- for (let i = 0; i < 15; i++) {
123
- if (isRouterReady()) {
124
- console.log("Router is ready!");
125
- return;
126
- }
127
- await setTimeout$1(2e3);
89
+ composeFile,
90
+ "up",
91
+ "-d"
92
+ ], { stdio: "inherit" });
93
+ console.log("Waiting for Temporal to be ready...");
94
+ for (let i = 0; i < 30; i++) {
95
+ if (isTemporalReady()) {
96
+ console.log("Temporal is ready!");
97
+ return;
128
98
  }
129
- console.error("Timeout waiting for router");
130
- process.exit(1);
99
+ await setTimeout$1(2e3);
131
100
  }
101
+ console.error("Timeout waiting for Temporal");
102
+ process.exit(1);
132
103
  }
133
104
  /**
134
105
  * Build the worker image locally (local mode only).
@@ -180,21 +151,20 @@ function addHostFlag() {
180
151
  }
181
152
  /**
182
153
  * Spawn the worker container in detached mode and return the process.
154
+ * When `opts.debug` is true, omits `--rm` so the container persists for log inspection.
183
155
  */
184
156
  function spawnWorker(opts) {
185
- const args = [
186
- "run",
187
- "-d",
188
- "--rm",
189
- "--name",
190
- opts.containerName,
191
- "--network",
192
- "shannon-net"
193
- ];
157
+ const args = ["run", "-d"];
158
+ if (!opts.debug) args.push("--rm");
159
+ args.push("--name", opts.containerName, "--network", "shannon-net");
194
160
  args.push(...addHostFlag());
195
161
  if (os.platform() === "linux" && process.getuid && process.getgid) args.push("-e", `SHANNON_HOST_UID=${process.getuid()}`, "-e", `SHANNON_HOST_GID=${process.getgid()}`);
196
162
  args.push("-v", `${opts.workspacesDir}:/app/workspaces`);
197
- args.push("-v", `${opts.repo.hostPath}:${opts.repo.containerPath}`);
163
+ args.push("-v", `${opts.repo.hostPath}:${opts.repo.containerPath}:ro`);
164
+ const workspacePath = path.join(opts.workspacesDir, opts.workspace);
165
+ args.push("-v", `${path.join(workspacePath, "deliverables")}:${opts.repo.containerPath}/.shannon/deliverables`);
166
+ args.push("-v", `${path.join(workspacePath, "scratchpad")}:${opts.repo.containerPath}/.shannon/scratchpad`);
167
+ args.push("-v", `${path.join(workspacePath, ".playwright-cli")}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
198
168
  if (opts.promptsDir) args.push("-v", `${opts.promptsDir}:/app/apps/worker/prompts:ro`);
199
169
  if (opts.config) args.push("-v", `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
200
170
  if (opts.outputDir) args.push("-v", `${opts.outputDir}:/app/output`);
@@ -206,10 +176,14 @@ function spawnWorker(opts) {
206
176
  args.push("--task-queue", opts.taskQueue);
207
177
  if (opts.config) args.push("--config", opts.config.containerPath);
208
178
  if (opts.outputDir) args.push("--output", "/app/output");
209
- if (opts.workspace) args.push("--workspace", opts.workspace);
179
+ args.push("--workspace", opts.workspace);
210
180
  if (opts.pipelineTesting) args.push("--pipeline-testing");
211
181
  return spawn("docker", args, {
212
- stdio: "pipe",
182
+ stdio: [
183
+ "ignore",
184
+ "ignore",
185
+ "inherit"
186
+ ],
213
187
  ...os.platform() === "win32" && { env: {
214
188
  ...process.env,
215
189
  MSYS_NO_PATHCONV: "1"
@@ -239,8 +213,6 @@ function stopInfra(clean) {
239
213
  "compose",
240
214
  "-f",
241
215
  getComposeFile(),
242
- "--profile",
243
- "router",
244
216
  "down"
245
217
  ];
246
218
  if (clean) args.push("-v");
@@ -445,11 +417,6 @@ async function setup() {
445
417
  {
446
418
  value: "vertex",
447
419
  label: "Claude via Google Vertex AI"
448
- },
449
- {
450
- value: "router",
451
- label: "Router",
452
- hint: "experimental"
453
420
  }
454
421
  ]
455
422
  });
@@ -465,7 +432,6 @@ async function setupProvider(provider) {
465
432
  case "custom_base_url": return setupCustomBaseUrl();
466
433
  case "bedrock": return setupBedrock();
467
434
  case "vertex": return setupVertex();
468
- case "router": return setupRouter();
469
435
  }
470
436
  }
471
437
  async function setupAnthropic() {
@@ -663,49 +629,6 @@ async function setupVertex() {
663
629
  }
664
630
  };
665
631
  }
666
- async function setupRouter() {
667
- const routerProvider = await p.select({
668
- message: "Router provider",
669
- options: [{
670
- value: "openai",
671
- label: "OpenAI"
672
- }, {
673
- value: "openrouter",
674
- label: "OpenRouter"
675
- }]
676
- });
677
- if (p.isCancel(routerProvider)) return cancelAndExit();
678
- const apiKey = await promptSecret(routerProvider === "openai" ? "Enter your OpenAI API key" : "Enter your OpenRouter API key");
679
- let defaultModel;
680
- if (routerProvider === "openai") {
681
- const model = await p.select({
682
- message: "Default model",
683
- options: [{
684
- value: "gpt-5.2",
685
- label: "GPT-5.2"
686
- }, {
687
- value: "gpt-5-mini",
688
- label: "GPT-5 Mini"
689
- }]
690
- });
691
- if (p.isCancel(model)) return cancelAndExit();
692
- defaultModel = `openai,${model}`;
693
- } else {
694
- const model = await p.select({
695
- message: "Default model",
696
- options: [{
697
- value: "google/gemini-3-flash-preview",
698
- label: "Google Gemini 3 Flash Preview"
699
- }]
700
- });
701
- if (p.isCancel(model)) return cancelAndExit();
702
- defaultModel = `openrouter,${model}`;
703
- }
704
- const router = { default: defaultModel };
705
- if (routerProvider === "openai") router.openai_key = apiKey;
706
- else router.openrouter_key = apiKey;
707
- return { router };
708
- }
709
632
  async function promptSecret(message) {
710
633
  const value = await p.password({
711
634
  message,
@@ -793,21 +716,6 @@ const CONFIG_MAP = [
793
716
  toml: "custom_base_url.auth_token",
794
717
  type: "string"
795
718
  },
796
- {
797
- env: "ROUTER_DEFAULT",
798
- toml: "router.default",
799
- type: "string"
800
- },
801
- {
802
- env: "OPENAI_API_KEY",
803
- toml: "router.openai_key",
804
- type: "string"
805
- },
806
- {
807
- env: "OPENROUTER_API_KEY",
808
- toml: "router.openrouter_key",
809
- type: "string"
810
- },
811
719
  {
812
720
  env: "ANTHROPIC_SMALL_MODEL",
813
721
  toml: "models.small",
@@ -906,13 +814,6 @@ function validateProviderFields(config, provider, errors) {
906
814
  validateModelTiers(config, "vertex", errors);
907
815
  break;
908
816
  }
909
- case "router": {
910
- if (!keys.includes("default")) errors.push("[router] missing required key: default");
911
- if (!keys.includes("openai_key") && !keys.includes("openrouter_key")) errors.push("[router] requires either openai_key or openrouter_key");
912
- const models = config.models;
913
- if (models && typeof models === "object" && Object.keys(models).length > 0) errors.push("[models] is not supported with [router]");
914
- break;
915
- }
916
817
  }
917
818
  }
918
819
  /** Bedrock and Vertex require a [models] section with all three tiers. */
@@ -965,8 +866,7 @@ function validateConfig(config) {
965
866
  "anthropic",
966
867
  "custom_base_url",
967
868
  "bedrock",
968
- "vertex",
969
- "router"
869
+ "vertex"
970
870
  ].filter((s) => {
971
871
  const section = config[s];
972
872
  return section && typeof section === "object" && Object.keys(section).length > 0;
@@ -1014,7 +914,6 @@ const FORWARD_VARS = [
1014
914
  "ANTHROPIC_API_KEY",
1015
915
  "ANTHROPIC_BASE_URL",
1016
916
  "ANTHROPIC_AUTH_TOKEN",
1017
- "ROUTER_DEFAULT",
1018
917
  "CLAUDE_CODE_OAUTH_TOKEN",
1019
918
  "CLAUDE_CODE_USE_BEDROCK",
1020
919
  "AWS_REGION",
@@ -1026,9 +925,7 @@ const FORWARD_VARS = [
1026
925
  "ANTHROPIC_SMALL_MODEL",
1027
926
  "ANTHROPIC_MEDIUM_MODEL",
1028
927
  "ANTHROPIC_LARGE_MODEL",
1029
- "CLAUDE_CODE_MAX_OUTPUT_TOKENS",
1030
- "OPENAI_API_KEY",
1031
- "OPENROUTER_API_KEY"
928
+ "CLAUDE_CODE_MAX_OUTPUT_TOKENS"
1032
929
  ];
1033
930
  /**
1034
931
  * Load credentials into process.env.
@@ -1054,10 +951,6 @@ function buildEnvFlags() {
1054
951
  }
1055
952
  return flags;
1056
953
  }
1057
- /** Check if router credentials are present in the environment. */
1058
- function isRouterConfigured() {
1059
- return !!(process.env.ROUTER_DEFAULT && (process.env.OPENAI_API_KEY || process.env.OPENROUTER_API_KEY));
1060
- }
1061
954
  /** Check if a custom Anthropic-compatible base URL is configured. */
1062
955
  function isCustomBaseUrlConfigured() {
1063
956
  return !!(process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_AUTH_TOKEN);
@@ -1070,7 +963,6 @@ function detectProviders() {
1070
963
  if (isCustomBaseUrlConfigured()) providers.push("Custom Base URL");
1071
964
  if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") providers.push("AWS Bedrock");
1072
965
  if (process.env.CLAUDE_CODE_USE_VERTEX === "1") providers.push("Google Vertex");
1073
- if (isRouterConfigured()) providers.push("Router");
1074
966
  return providers;
1075
967
  }
1076
968
  /**
@@ -1091,13 +983,10 @@ function validateCredentials() {
1091
983
  valid: true,
1092
984
  mode: "oauth"
1093
985
  };
1094
- if (isCustomBaseUrlConfigured()) {
1095
- process.env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_AUTH_TOKEN;
1096
- return {
1097
- valid: true,
1098
- mode: "custom-base-url"
1099
- };
1100
- }
986
+ if (isCustomBaseUrlConfigured()) return {
987
+ valid: true,
988
+ mode: "custom-base-url"
989
+ };
1101
990
  if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
1102
991
  const missing = [];
1103
992
  if (!process.env.AWS_REGION) missing.push("AWS_REGION");
@@ -1137,13 +1026,6 @@ function validateCredentials() {
1137
1026
  mode: "vertex"
1138
1027
  };
1139
1028
  }
1140
- if (isRouterConfigured()) {
1141
- process.env.ANTHROPIC_API_KEY = "router-mode";
1142
- return {
1143
- valid: true,
1144
- mode: "router"
1145
- };
1146
- }
1147
1029
  return {
1148
1030
  valid: false,
1149
1031
  mode: "api-key",
@@ -1207,14 +1089,6 @@ function resolveConfig(configArg) {
1207
1089
  containerPath: `/app/configs/${path.basename(hostPath)}`
1208
1090
  };
1209
1091
  }
1210
- /**
1211
- * Ensure the deliverables directory exists and is writable by the container user.
1212
- */
1213
- function ensureDeliverables(repoHostPath) {
1214
- const deliverables = path.join(repoHostPath, "deliverables");
1215
- fs.mkdirSync(deliverables, { recursive: true });
1216
- fs.chmodSync(deliverables, 511);
1217
- }
1218
1092
  //#endregion
1219
1093
  //#region src/splash.ts
1220
1094
  /**
@@ -1271,23 +1145,35 @@ async function start(args) {
1271
1145
  console.error(`ERROR: ${creds.error}`);
1272
1146
  process.exit(1);
1273
1147
  }
1274
- const useRouter = args.router || isRouterConfigured();
1275
1148
  const repo = resolveRepo(args.repo);
1276
1149
  const config = args.config ? resolveConfig(args.config) : void 0;
1277
- ensureDeliverables(repo.hostPath);
1278
1150
  const workspacesDir = getWorkspacesDir();
1279
1151
  fs.mkdirSync(workspacesDir, { recursive: true });
1280
1152
  fs.chmodSync(workspacesDir, 511);
1281
- if (useRouter) {
1282
- process.env.ANTHROPIC_BASE_URL = "http://shannon-router:3456";
1283
- process.env.ANTHROPIC_AUTH_TOKEN = "shannon-router-key";
1284
- }
1285
1153
  ensureImage(args.version);
1286
- await ensureInfra(useRouter);
1154
+ await ensureInfra();
1287
1155
  const suffix = randomSuffix();
1288
1156
  const taskQueue = `shannon-${suffix}`;
1289
1157
  const containerName = `shannon-worker-${suffix}`;
1290
1158
  const workspace = args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, "-")}_shannon-${Date.now()}`;
1159
+ const workspacePath = path.join(workspacesDir, workspace);
1160
+ fs.mkdirSync(workspacePath, { recursive: true });
1161
+ fs.chmodSync(workspacePath, 511);
1162
+ for (const dir of [
1163
+ "deliverables",
1164
+ "scratchpad",
1165
+ ".playwright-cli"
1166
+ ]) {
1167
+ const dirPath = path.join(workspacePath, dir);
1168
+ fs.mkdirSync(dirPath, { recursive: true });
1169
+ fs.chmodSync(dirPath, 511);
1170
+ }
1171
+ const shannonDir = path.join(repo.hostPath, ".shannon");
1172
+ for (const dir of [
1173
+ "deliverables",
1174
+ "scratchpad",
1175
+ ".playwright-cli"
1176
+ ]) fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
1291
1177
  const credentialsPath = getCredentialsPath();
1292
1178
  const hasCredentials = fs.existsSync(credentialsPath);
1293
1179
  if (hasCredentials) process.env.GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/google-sa-key.json";
@@ -1295,7 +1181,7 @@ async function start(args) {
1295
1181
  if (outputDir) fs.mkdirSync(outputDir, { recursive: true });
1296
1182
  const promptsDir = isLocal() ? path.resolve("apps/worker/prompts") : void 0;
1297
1183
  displaySplash(isLocal() ? void 0 : args.version);
1298
- spawnWorker({
1184
+ const proc = spawnWorker({
1299
1185
  version: args.version,
1300
1186
  url: args.url,
1301
1187
  repo,
@@ -1307,12 +1193,17 @@ async function start(args) {
1307
1193
  ...hasCredentials && { credentials: credentialsPath },
1308
1194
  ...promptsDir && { promptsDir },
1309
1195
  ...outputDir && { outputDir },
1310
- ...workspace && { workspace },
1311
- ...args.pipelineTesting && { pipelineTesting: true }
1312
- }).on("error", (err) => {
1313
- console.error(`Failed to start worker: ${err.message}`);
1314
- process.exit(1);
1196
+ workspace,
1197
+ ...args.pipelineTesting && { pipelineTesting: true },
1198
+ ...args.debug && { debug: true }
1315
1199
  });
1200
+ if (await new Promise((resolve) => {
1201
+ proc.once("exit", (code) => resolve(code ?? 1));
1202
+ proc.once("error", (err) => {
1203
+ console.error(`Failed to start worker: ${err.message}`);
1204
+ resolve(1);
1205
+ });
1206
+ }) !== 0) process.exit(1);
1316
1207
  const sessionJson = path.join(workspacesDir, workspace, "session.json");
1317
1208
  const isResume = fs.existsSync(sessionJson);
1318
1209
  let initialResumeCount = 0;
@@ -1339,7 +1230,7 @@ async function start(args) {
1339
1230
  started = true;
1340
1231
  workflowId = resumeAttempts.at(-1)?.workflowId ?? session.session?.originalWorkflowId ?? "";
1341
1232
  process.stdout.write("\r\x1B[K");
1342
- printInfo(args, useRouter, workspace, workflowId, repo.hostPath, workspacesDir);
1233
+ printInfo(args, workspace, workflowId, repo.hostPath, workspacesDir);
1343
1234
  return;
1344
1235
  }
1345
1236
  } catch {}
@@ -1354,6 +1245,7 @@ async function start(args) {
1354
1245
  try {
1355
1246
  execFileSync("docker", ["stop", containerName], { stdio: "pipe" });
1356
1247
  } catch {}
1248
+ if (args.debug) printDebugHint(containerName);
1357
1249
  };
1358
1250
  process.on("SIGINT", () => {
1359
1251
  cleanup();
@@ -1365,7 +1257,14 @@ async function start(args) {
1365
1257
  });
1366
1258
  process.on("exit", cleanup);
1367
1259
  }
1368
- function printInfo(args, routerActive, workspace, workflowId, repoPath, workspacesDir) {
1260
+ function printDebugHint(containerName) {
1261
+ console.log("");
1262
+ console.log(` Worker container preserved: ${containerName}`);
1263
+ console.log(` Inspect logs: docker logs ${containerName}`);
1264
+ console.log(` Remove: docker rm ${containerName}`);
1265
+ console.log("");
1266
+ }
1267
+ function printInfo(args, workspace, workflowId, repoPath, workspacesDir) {
1369
1268
  const logsCmd = isLocal() ? `./shannon logs ${workspace}` : `npx @keygraph/shannon logs ${workspace}`;
1370
1269
  const reportsPath = path.join(workspacesDir, workspace);
1371
1270
  console.log(` Target: ${args.url}`);
@@ -1373,7 +1272,6 @@ function printInfo(args, routerActive, workspace, workflowId, repoPath, workspac
1373
1272
  console.log(` Workspace: ${workspace}`);
1374
1273
  if (args.config) console.log(` Config: ${path.resolve(args.config)}`);
1375
1274
  if (args.pipelineTesting) console.log(" Mode: Pipeline Testing");
1376
- if (routerActive) console.log(" Router: Enabled");
1377
1275
  console.log("");
1378
1276
  console.log(" Monitor:");
1379
1277
  if (workflowId) console.log(` Web UI: http://localhost:8233/namespaces/default/workflows/${workflowId}`);
@@ -1521,7 +1419,7 @@ Options for 'start':
1521
1419
  -o, --output <path> Copy deliverables to this directory after run
1522
1420
  -w, --workspace <name> Named workspace (auto-resumes if exists)
1523
1421
  --pipeline-testing Use minimal prompts for fast testing
1524
- --router Route requests through claude-code-router
1422
+ --debug Preserve worker container after exit for log inspection
1525
1423
 
1526
1424
  Examples:
1527
1425
  ${prefix} start -u https://example.com -r ${mode === "local" ? "my-repo" : "./my-repo"}
@@ -1541,7 +1439,7 @@ function parseStartArgs(argv) {
1541
1439
  let workspace;
1542
1440
  let output;
1543
1441
  let pipelineTesting = false;
1544
- let router = false;
1442
+ let debug = false;
1545
1443
  for (let i = 0; i < argv.length; i++) {
1546
1444
  const arg = argv[i];
1547
1445
  const next = argv[i + 1];
@@ -1584,8 +1482,8 @@ function parseStartArgs(argv) {
1584
1482
  case "--pipeline-testing":
1585
1483
  pipelineTesting = true;
1586
1484
  break;
1587
- case "--router":
1588
- router = true;
1485
+ case "--debug":
1486
+ debug = true;
1589
1487
  break;
1590
1488
  default:
1591
1489
  console.error(`Unknown option: ${arg}`);
@@ -1602,7 +1500,7 @@ function parseStartArgs(argv) {
1602
1500
  url,
1603
1501
  repo,
1604
1502
  pipelineTesting,
1605
- router,
1503
+ debug,
1606
1504
  ...config && { config },
1607
1505
  ...workspace && { workspace },
1608
1506
  ...output && { output }
package/infra/compose.yml CHANGED
@@ -19,32 +19,5 @@ services:
19
19
  retries: 10
20
20
  start_period: 30s
21
21
 
22
- router:
23
- image: node:20-slim
24
- container_name: shannon-router
25
- profiles: ["router"]
26
- command: >
27
- sh -c "apt-get update && apt-get install -y gettext-base &&
28
- npm install -g @musistudio/claude-code-router &&
29
- mkdir -p /root/.claude-code-router &&
30
- envsubst < /config/router-config.json > /root/.claude-code-router/config.json &&
31
- ccr start"
32
- ports:
33
- - "127.0.0.1:3456:3456"
34
- volumes:
35
- - ./router-config.json:/config/router-config.json:ro
36
- environment:
37
- - HOST=0.0.0.0
38
- - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
39
- - OPENAI_API_KEY=${OPENAI_API_KEY:-}
40
- - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
41
- - ROUTER_DEFAULT=${ROUTER_DEFAULT:-openai,gpt-4o}
42
- healthcheck:
43
- test: ["CMD", "node", "-e", "require('http').get('http://localhost:3456/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
44
- interval: 10s
45
- timeout: 5s
46
- retries: 5
47
- start_period: 30s
48
-
49
22
  volumes:
50
23
  temporal-data:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keygraph/shannon",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Shannon - Autonomous white-box AI pentester for web applications and APIs by Keygraph",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -1,31 +0,0 @@
1
- {
2
- "HOST": "0.0.0.0",
3
- "APIKEY": "shannon-router-key",
4
- "LOG": true,
5
- "LOG_LEVEL": "info",
6
- "NON_INTERACTIVE_MODE": true,
7
- "API_TIMEOUT_MS": 600000,
8
- "Providers": [
9
- {
10
- "name": "openai",
11
- "api_base_url": "https://api.openai.com/v1/chat/completions",
12
- "api_key": "$OPENAI_API_KEY",
13
- "models": ["gpt-5.2", "gpt-5-mini"],
14
- "transformer": {
15
- "use": [["maxcompletiontokens", { "max_completion_tokens": 16384 }]]
16
- }
17
- },
18
- {
19
- "name": "openrouter",
20
- "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
21
- "api_key": "$OPENROUTER_API_KEY",
22
- "models": ["google/gemini-3-flash-preview"],
23
- "transformer": {
24
- "use": ["openrouter"]
25
- }
26
- }
27
- ],
28
- "Router": {
29
- "default": "$ROUTER_DEFAULT"
30
- }
31
- }