betahi-copilot-bridge 0.1.8 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <h1 align="center">copilot-bridge</h1>
2
2
 
3
3
  <p align="center">
4
- <a href="https://www.npmjs.com/package/betahi-copilot-bridge"><img src="https://img.shields.io/npm/v/betahi-copilot-bridge.svg?v=0.1.8" alt="npm version"></a>
4
+ <a href="https://www.npmjs.com/package/betahi-copilot-bridge"><img src="https://img.shields.io/npm/v/betahi-copilot-bridge.svg?v=0.20.0" alt="npm version"></a>
5
5
  <a href="https://github.com/betahi/copilot-bridge/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/betahi-copilot-bridge.svg" alt="license"></a>
6
6
  </p>
7
7
 
@@ -83,6 +83,7 @@ this block; the bridge regenerates it on every start.
83
83
  ```toml
84
84
  # >>> copilot-bridge managed block — auto-generated, do not edit between markers >>>
85
85
  model_provider = "bridge"
86
+ model_supports_reasoning_summaries = true
86
87
 
87
88
  [model_providers.bridge]
88
89
  name = "Copilot Bridge"
@@ -102,11 +103,6 @@ model = "gpt-5.3-codex"
102
103
  model_reasoning_effort = "high"
103
104
  ```
104
105
 
105
- If `model_reasoning_effort` is omitted, the bridge leaves it unset and does not
106
- send a reasoning effort for Codex requests (a startup info log also reminds you).
107
-
108
- `model_reasoning_effort` only affects Codex requests; it does not affect Claude.
109
-
110
106
  That's it — `codex exec '...'` will now route through the bridge to Copilot.
111
107
 
112
108
  Use `--no-codex-setup` to skip the managed-block writer entirely (e.g. if you
@@ -194,16 +190,23 @@ accepts upstream.
194
190
 
195
191
  ### Claude family — translated to chat completions
196
192
 
197
- | Model | Reasoning efforts | Notes |
198
- | -------------------- | --------------------------------------- | -------------------------------------- |
199
- | `claude-opus-4.7` | `low`, `medium`, `high`, `xhigh`, `max` | Effort sent as `output_config.effort`. |
200
- | `claude-opus-4.6` | `low`, `medium`, `high` | |
201
- | `claude-opus-4.6-1m` | `low`, `medium`, `high` | 1M-token context window. |
202
- | `claude-sonnet-4.6` | `low`, `medium`, `high` | |
203
- | `claude-opus-4.5` | — | Reasoning not accepted upstream. |
204
- | `claude-sonnet-4.5` | | Reasoning not accepted upstream. |
205
- | `claude-sonnet-4` | — | Reasoning not accepted upstream. |
206
- | `claude-haiku-4.5` | — | Reasoning not accepted upstream. |
193
+ | Model | Reasoning efforts | Notes |
194
+ | -------------------------------- | --------------------------------------- | -------------------------------------- |
195
+ | `claude-opus-4.7` | `medium` | Effort sent as `output_config.effort`. |
196
+ | `claude-opus-4.7-1m` | `low`, `medium`, `high`, `xhigh` | 1M-token context window; effort sent as `output_config.effort`. |
197
+ | `claude-opus-4.7-high` | `high` | Fixed high reasoning; effort sent as `output_config.effort`. |
198
+ | `claude-opus-4.7-xhigh` | `xhigh` | Fixed extra-high reasoning; effort sent as `output_config.effort`. |
199
+ | `claude-opus-4.6` | `low`, `medium`, `high` | |
200
+ | `claude-opus-4.6-1m` | `low`, `medium`, `high` | 1M-token context window. |
201
+ | `claude-sonnet-4.6` | `low`, `medium`, `high` | |
202
+ | `claude-opus-4.5` | — | Reasoning not accepted upstream. |
203
+ | `claude-sonnet-4.5` | — | Reasoning not accepted upstream. |
204
+ | `claude-sonnet-4` | — | Reasoning not accepted upstream. |
205
+ | `claude-haiku-4.5` | — | Reasoning not accepted upstream. |
206
+
207
+ For Claude Opus 4.7, both Codex CLI and Claude Code can use
208
+ `claude-opus-4.7` with reasoning effort `high` or `xhigh`; the bridge routes the
209
+ request to the matching upstream reasoning variant.
207
210
 
208
211
  ### Gemini family — translated to chat completions
209
212
 
package/dist/main.js CHANGED
@@ -439,6 +439,18 @@ const BEGIN_MARK = "# >>> copilot-bridge managed block — auto-generated, do no
439
439
  const END_MARK = "# <<< copilot-bridge managed block — edits outside this block are preserved <<<";
440
440
  const LEGACY_BEGIN_MARK = "# >>> copilot-bridge managed (do not edit) >>>";
441
441
  const LEGACY_END_MARK = "# <<< copilot-bridge managed (do not edit) <<<";
442
+ function normalizeCodexConfigReasoningEffort(value) {
443
+ switch (value?.toLowerCase()) {
444
+ case "none":
445
+ case "minimal":
446
+ case "low":
447
+ case "medium":
448
+ case "high":
449
+ case "xhigh": return value.toLowerCase();
450
+ case "max": return "xhigh";
451
+ default: return;
452
+ }
453
+ }
442
454
  function tomlEscape(value) {
443
455
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
444
456
  }
@@ -447,6 +459,7 @@ function buildManagedBlock(input) {
447
459
  const lines = [];
448
460
  lines.push(BEGIN_MARK);
449
461
  if (settings.setAsDefault) lines.push(`model_provider = "${tomlEscape(settings.providerId)}"`);
462
+ lines.push("model_supports_reasoning_summaries = true");
450
463
  lines.push("");
451
464
  lines.push(`[model_providers.${settings.providerId}]`);
452
465
  lines.push(`name = "${tomlEscape(settings.providerName)}"`);
@@ -502,11 +515,25 @@ function setTopScalar(topSection, key, value) {
502
515
  if (topSection.length === 0) return `${line}\n`;
503
516
  return `${line}\n${topSection.startsWith("\n") ? "" : ""}${topSection}`;
504
517
  }
518
+ function removeTopKey(topSection, key) {
519
+ const lines = topSection.split("\n");
520
+ const re = /* @__PURE__ */ new RegExp(`^\\s*${key}\\s*=`);
521
+ return lines.filter((line) => !re.test(line)).join("\n");
522
+ }
523
+ function sanitizeTopReasoningEffort(topSection) {
524
+ const match = topSection.match(scalarRegex("model_reasoning_effort"));
525
+ if (!match) return topSection;
526
+ const normalized = normalizeCodexConfigReasoningEffort(match[1]);
527
+ if (!normalized) return removeTopKey(topSection, "model_reasoning_effort");
528
+ if (normalized === match[1]) return topSection;
529
+ return topSection.replace(scalarRegex("model_reasoning_effort"), `model_reasoning_effort = "${tomlEscape(normalized)}"`);
530
+ }
505
531
  function applyUserScalars(content, input) {
506
532
  const { top, rest } = splitTopSection(content);
507
- let nextTop = top;
533
+ let nextTop = removeTopKey(top, "model_supports_reasoning_summaries");
534
+ nextTop = sanitizeTopReasoningEffort(nextTop);
508
535
  nextTop = setTopScalar(nextTop, "model", input.model);
509
- nextTop = setTopScalar(nextTop, "model_reasoning_effort", input.modelReasoningEffort);
536
+ nextTop = setTopScalar(nextTop, "model_reasoning_effort", normalizeCodexConfigReasoningEffort(input.modelReasoningEffort));
510
537
  if (nextTop === top) return content;
511
538
  if (rest.length === 0) return nextTop.endsWith("\n") ? nextTop : `${nextTop}\n`;
512
539
  const sep = nextTop.endsWith("\n") ? "" : "\n";
@@ -649,21 +676,43 @@ const MODEL_CAPABILITIES = [
649
676
  default: "medium"
650
677
  }
651
678
  },
652
- {
653
- id: "claude-opus-4.7",
654
- fallback: "chat-completions",
655
- reasoningField: "output_config.effort",
656
- reasoning: {
679
+ ...[
680
+ {
681
+ id: "claude-opus-4.7",
682
+ supported: ["medium"],
683
+ defaultEffort: "medium"
684
+ },
685
+ {
686
+ id: "claude-opus-4.7-1m-internal",
687
+ aliases: ["claude-opus-4.7-1m"],
657
688
  supported: [
658
689
  "low",
659
690
  "medium",
660
691
  "high",
661
- "xhigh",
662
- "max"
692
+ "xhigh"
663
693
  ],
664
- default: "medium"
694
+ defaultEffort: "medium"
695
+ },
696
+ {
697
+ id: "claude-opus-4.7-high",
698
+ supported: ["high"],
699
+ defaultEffort: "high"
700
+ },
701
+ {
702
+ id: "claude-opus-4.7-xhigh",
703
+ supported: ["xhigh"],
704
+ defaultEffort: "xhigh"
665
705
  }
666
- },
706
+ ].map(({ id, aliases, supported, defaultEffort }) => ({
707
+ id,
708
+ aliases,
709
+ fallback: "chat-completions",
710
+ reasoningField: "output_config.effort",
711
+ reasoning: {
712
+ supported,
713
+ default: defaultEffort
714
+ }
715
+ })),
667
716
  {
668
717
  id: "claude-opus-4.6",
669
718
  fallback: "chat-completions",
@@ -1155,7 +1204,7 @@ const normalizeClaudeOpus47Effort = (value) => {
1155
1204
  default: return;
1156
1205
  }
1157
1206
  };
1158
- const getEnvValueCaseInsensitive = (env, key) => {
1207
+ const getEnvValueCaseInsensitive$1 = (env, key) => {
1159
1208
  const direct = env[key];
1160
1209
  if (typeof direct === "string") return direct;
1161
1210
  const lower = key.toLowerCase();
@@ -1163,7 +1212,7 @@ const getEnvValueCaseInsensitive = (env, key) => {
1163
1212
  };
1164
1213
  const getConfiguredReasoningEffort = (client, claudeSettingsEnv) => {
1165
1214
  if (client !== "claude") return;
1166
- return process.env.MODEL_REASONING_EFFORT ?? getEnvValueCaseInsensitive(claudeSettingsEnv, "MODEL_REASONING_EFFORT");
1215
+ return process.env.MODEL_REASONING_EFFORT ?? getEnvValueCaseInsensitive$1(claudeSettingsEnv, "MODEL_REASONING_EFFORT");
1167
1216
  };
1168
1217
  const getRequestedReasoningEffort = (payload, claudeSettingsEnv, client) => {
1169
1218
  const configuredReasoningEffort = getConfiguredReasoningEffort(client, claudeSettingsEnv);
@@ -1173,7 +1222,9 @@ const getRequestedReasoningEffort = (payload, claudeSettingsEnv, client) => {
1173
1222
  const getRequestedClaudeOpus47Effort = (payload, claudeSettingsEnv, client) => {
1174
1223
  if (!isClaudeOpus47Model$1(payload.model)) return;
1175
1224
  const configuredReasoningEffort = getConfiguredReasoningEffort(client, claudeSettingsEnv);
1176
- return payload.output_config?.effort ?? normalizeClaudeOpus47Effort(payload.reasoning_effort) ?? normalizeClaudeOpus47Effort(configuredReasoningEffort);
1225
+ const requested = normalizeClaudeOpus47Effort(payload.reasoning_effort) ?? normalizeClaudeOpus47Effort(configuredReasoningEffort) ?? payload.output_config?.effort;
1226
+ if (!requested) return;
1227
+ return clampReasoningEffort(payload.model, requested)?.effort;
1177
1228
  };
1178
1229
  const sanitizeUserIdentifier = (user) => {
1179
1230
  if (!user) return;
@@ -1366,20 +1417,21 @@ embeddingRoutes.post("/", async (c) => {
1366
1417
  const normalizeModelId = (modelId) => modelId.trim().toLowerCase().replaceAll(/[\s._-]+/g, "");
1367
1418
  const stripSnapshotSuffix = (modelId) => {
1368
1419
  const id = modelId.trim().toLowerCase().replace(/\[1m\]$/, "");
1369
- const claudeSnapshotMatch = id.match(/^claude-(opus|sonnet|haiku)-(\d)-(\d)(-1m)?(?:-\d{8})?$/);
1420
+ if (id === "claude-opus-4.7-1m") return "claude-opus-4.7-1m-internal";
1421
+ const claudeSnapshotMatch = id.match(/^claude-(opus|sonnet|haiku)-(\d)-(\d)((?:-[a-z0-9]+)*?)(?:-\d{8})?$/);
1370
1422
  if (claudeSnapshotMatch) {
1371
- const [, family, major, minor, contextWindow = ""] = claudeSnapshotMatch;
1372
- return `claude-${family}-${major}.${minor}${contextWindow}`;
1423
+ const [, family, major, minor, suffix = ""] = claudeSnapshotMatch;
1424
+ return `claude-${family}-${major}.${minor}${suffix}`;
1373
1425
  }
1374
1426
  return id;
1375
1427
  };
1376
1428
  const getAliasCandidates = (modelId) => {
1377
1429
  const canonicalModelId = stripSnapshotSuffix(modelId);
1378
1430
  const aliases = new Set([canonicalModelId]);
1379
- const canonicalClaudeMatch = canonicalModelId.match(/^claude-(opus|sonnet|haiku)-(\d+)\.(\d+)(-1m)?$/);
1431
+ const canonicalClaudeMatch = canonicalModelId.match(/^claude-(opus|sonnet|haiku)-(\d+)\.(\d+)((?:-[a-z0-9]+)*)$/);
1380
1432
  if (canonicalClaudeMatch) {
1381
- const [, family, major, minor, contextWindow = ""] = canonicalClaudeMatch;
1382
- aliases.add(`claude-${family}-${major}-${minor}${contextWindow}`);
1433
+ const [, family, major, minor, suffix = ""] = canonicalClaudeMatch;
1434
+ aliases.add(`claude-${family}-${major}-${minor}${suffix}`);
1383
1435
  return [...aliases];
1384
1436
  }
1385
1437
  const familyMatch = canonicalModelId.match(/^[a-z]+(?:-[a-z]+)*-\d+(?:\.\d+)?/);
@@ -1526,12 +1578,42 @@ const normalizeClaudeModelAlias = (model) => {
1526
1578
  const trimmed = model.trim().toLowerCase();
1527
1579
  if (trimmed === "opus[1m]") return "claude-opus-4.6-1m";
1528
1580
  const normalized = trimmed.replace(/\[1m\]$/, "");
1529
- if (normalized === "opus") return "claude-opus";
1530
- if (normalized === "sonnet") return "claude-sonnet";
1531
- if (normalized === "haiku") return "claude-haiku";
1532
- if (/^claude-(opus|sonnet|haiku)-\d-\d(?:-1m)?$/.test(normalized)) return normalized.replace(/^claude-(opus|sonnet|haiku)-(\d)-(\d)(-1m)?$/, "claude-$1-$2.$3$4");
1533
- if (/^claude-opus-4-7-\d{8}$/.test(normalized)) return "claude-opus-4.7";
1534
- return normalized;
1581
+ const prefixed = normalized.startsWith("opus-") ? `claude-${normalized}` : normalized;
1582
+ if (prefixed === "opus") return "claude-opus";
1583
+ if (prefixed === "sonnet") return "claude-sonnet";
1584
+ if (prefixed === "haiku") return "claude-haiku";
1585
+ const claudeSnapshotMatch = prefixed.match(/^claude-(opus|sonnet|haiku)-(\d)-(\d)((?:-[a-z0-9]+)*?)(?:-\d{8})?$/);
1586
+ if (claudeSnapshotMatch) {
1587
+ const [, family, major, minor, suffix = ""] = claudeSnapshotMatch;
1588
+ return `claude-${family}-${major}.${minor}${suffix}`;
1589
+ }
1590
+ return prefixed;
1591
+ };
1592
+ function normalizeClaudeReasoningEffortForRouting(value) {
1593
+ switch (value?.toLowerCase()) {
1594
+ case "low":
1595
+ case "medium":
1596
+ case "high":
1597
+ case "xhigh":
1598
+ case "max": return value.toLowerCase();
1599
+ default: return;
1600
+ }
1601
+ }
1602
+ const getEnvValueCaseInsensitive = (env, key) => {
1603
+ const direct = env[key];
1604
+ if (typeof direct === "string") return direct;
1605
+ const lower = key.toLowerCase();
1606
+ return Object.entries(env).find(([k]) => k.toLowerCase() === lower)?.[1];
1607
+ };
1608
+ const getConfiguredClaudeReasoningEffort = (settings) => process.env.MODEL_REASONING_EFFORT ?? getEnvValueCaseInsensitive(settings?.env ?? {}, "MODEL_REASONING_EFFORT");
1609
+ const routeClaudeOpus47ByEffort = (model, requestedEffort) => {
1610
+ if (model !== "claude-opus-4.7") return model;
1611
+ switch (normalizeClaudeReasoningEffortForRouting(requestedEffort)) {
1612
+ case "high": return "claude-opus-4.7-high";
1613
+ case "xhigh":
1614
+ case "max": return "claude-opus-4.7-xhigh";
1615
+ default: return model;
1616
+ }
1535
1617
  };
1536
1618
  const getConfiguredClaudeDefaultModel = (settings) => {
1537
1619
  if (!settings) return;
@@ -1551,8 +1633,8 @@ const resolveClaudeRequestedModel = (model, settings) => {
1551
1633
  if (model.trim().toLowerCase() !== "definitely-not-a-real-model") return model;
1552
1634
  return getConfiguredClaudeDefaultModel(settings) ?? model;
1553
1635
  };
1554
- function translateModelName(model, settings) {
1555
- return resolveUpstreamModelId(normalizeClaudeModelAlias(resolveClaudeRequestedModel(model, settings)));
1636
+ function translateModelName(model, settings, requestedReasoningEffort) {
1637
+ return resolveUpstreamModelId(routeClaudeOpus47ByEffort(normalizeClaudeModelAlias(resolveClaudeRequestedModel(model, settings)), requestedReasoningEffort ?? getConfiguredClaudeReasoningEffort(settings)));
1556
1638
  }
1557
1639
  function isClaudeModel(modelId) {
1558
1640
  return modelId.startsWith("claude-");
@@ -1582,12 +1664,12 @@ function getClaudeOpus47Effort(payload) {
1582
1664
  return "xhigh";
1583
1665
  }
1584
1666
  function translateThinking(payload, settings) {
1585
- if (!isClaudeOpus47Model(translateModelName(payload.model, settings))) return;
1667
+ if (!isClaudeOpus47Model(translateModelName(payload.model, settings, payload.reasoning_effort))) return;
1586
1668
  if (payload.thinking?.type === "adaptive") return { type: "adaptive" };
1587
1669
  return payload.thinking?.type === "enabled" ? { type: "adaptive" } : void 0;
1588
1670
  }
1589
1671
  function translateOutputConfig(payload, settings) {
1590
- if (!isClaudeOpus47Model(translateModelName(payload.model, settings))) return;
1672
+ if (!isClaudeOpus47Model(translateModelName(payload.model, settings, payload.reasoning_effort))) return;
1591
1673
  const effort = getClaudeOpus47Effort(payload);
1592
1674
  return effort ? { effort } : void 0;
1593
1675
  }
@@ -1603,7 +1685,7 @@ function normalizeReasoningEffort(value) {
1603
1685
  }
1604
1686
  }
1605
1687
  function translateReasoningEffort(payload, settings) {
1606
- const modelId = translateModelName(payload.model, settings);
1688
+ const modelId = translateModelName(payload.model, settings, payload.reasoning_effort);
1607
1689
  if (isClaudeOpus47Model(modelId)) return;
1608
1690
  if (payload.reasoning_effort) return sanitizeReasoningEffortForModel(modelId, normalizeReasoningEffort(payload.reasoning_effort));
1609
1691
  if (isClaudeModel(modelId)) return;
@@ -1616,7 +1698,7 @@ function translateReasoningEffort(payload, settings) {
1616
1698
  return sanitizeReasoningEffortForModel(modelId, "xhigh");
1617
1699
  }
1618
1700
  function translateToOpenAI(payload, settings, toolNameMapper) {
1619
- const model = translateModelName(payload.model, settings);
1701
+ const model = translateModelName(payload.model, settings, payload.reasoning_effort);
1620
1702
  const mapper = toolNameMapper ?? createAnthropicToolNameMapper(payload.tools, { ...getToolNameMapperOptionsForModel(model) });
1621
1703
  const tools = translateAnthropicToolsToOpenAI(payload.tools, mapper);
1622
1704
  return {
@@ -2532,9 +2614,19 @@ const removeReasoningEffort = (reasoning) => {
2532
2614
  return Object.keys(next).length > 0 ? next : void 0;
2533
2615
  };
2534
2616
  const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2535
- const normalizeCodexResponsesRequest = (payload) => {
2617
+ const routeClaudeOpus47ByReasoningEffort = (model, effort) => {
2618
+ if (model !== "claude-opus-4.7") return model;
2619
+ switch (typeof effort === "string" ? effort.toLowerCase() : void 0) {
2620
+ case "high": return "claude-opus-4.7-high";
2621
+ case "xhigh":
2622
+ case "max": return "claude-opus-4.7-xhigh";
2623
+ default: return model;
2624
+ }
2625
+ };
2626
+ const normalizeCodexResponsesRequest = (payload, configuredReasoningEffort) => {
2536
2627
  const parsed = codexResponsesRequestSchema.parse(payload);
2537
- const canonical = resolveModelId(parsed.model);
2628
+ const requestedReasoningEffort = (isPlainObject(parsed.reasoning) ? parsed.reasoning : void 0)?.effort ?? configuredReasoningEffort;
2629
+ const canonical = routeClaudeOpus47ByReasoningEffort(resolveModelId(parsed.model), requestedReasoningEffort);
2538
2630
  const capability = getModelCapability(canonical);
2539
2631
  if (!capability) return parsed;
2540
2632
  const next = {
@@ -2545,21 +2637,30 @@ const normalizeCodexResponsesRequest = (payload) => {
2545
2637
  if ("reasoning" in next) delete next.reasoning;
2546
2638
  return next;
2547
2639
  }
2548
- if ("reasoning" in next) if (!isPlainObject(next.reasoning)) delete next.reasoning;
2640
+ if ("reasoning" in next) if (!isPlainObject(next.reasoning)) if (configuredReasoningEffort === void 0 || configuredReasoningEffort === null) delete next.reasoning;
2641
+ else {
2642
+ const clamped = clampReasoningEffort(canonical, configuredReasoningEffort);
2643
+ if (clamped) next.reasoning = { effort: clamped.effort };
2644
+ }
2549
2645
  else {
2550
2646
  const incoming = next.reasoning;
2551
- if (incoming.effort === void 0 || incoming.effort === null) {
2647
+ const effort = incoming.effort ?? configuredReasoningEffort;
2648
+ if (effort === void 0 || effort === null) {
2552
2649
  const reasoning = removeReasoningEffort(incoming);
2553
2650
  if (reasoning) next.reasoning = reasoning;
2554
2651
  else delete next.reasoning;
2555
2652
  } else {
2556
- const clamped = clampReasoningEffort(canonical, incoming.effort);
2653
+ const clamped = clampReasoningEffort(canonical, effort);
2557
2654
  if (clamped) next.reasoning = {
2558
2655
  ...incoming,
2559
2656
  effort: clamped.effort
2560
2657
  };
2561
2658
  }
2562
2659
  }
2660
+ else if (configuredReasoningEffort !== void 0 && configuredReasoningEffort !== null) {
2661
+ const clamped = clampReasoningEffort(canonical, configuredReasoningEffort);
2662
+ if (clamped) next.reasoning = { effort: clamped.effort };
2663
+ }
2563
2664
  const text = isPlainObject(next.text) ? next.text : void 0;
2564
2665
  if (text && capability.textVerbosity && "verbosity" in text) {
2565
2666
  const { supported, default: defaultVerbosity } = capability.textVerbosity;
@@ -2597,6 +2698,9 @@ const logResponsesUpstreamError = async (message, response, context) => {
2597
2698
  request: JSON.stringify(summarizeResponsesRequestForDiagnostics(context.request))
2598
2699
  });
2599
2700
  };
2701
+ const readConfiguredCodexReasoningEffort = async () => {
2702
+ return normalizeCodexConfigReasoningEffort((await readCodexUserConfigFromDisk(process.env.CODEX_CONFIG_PATH ?? CODEX_DEFAULTS.configPath)).modelReasoningEffort);
2703
+ };
2600
2704
  responsesRoutes.post("/", async (c) => {
2601
2705
  try {
2602
2706
  await checkRateLimit();
@@ -2604,7 +2708,7 @@ responsesRoutes.post("/", async (c) => {
2604
2708
  if (error instanceof RateLimitError) return c.json({ error: { message: error.message } }, 429);
2605
2709
  throw error;
2606
2710
  }
2607
- const payload = normalizeCodexResponsesRequest(await c.req.json());
2711
+ const payload = normalizeCodexResponsesRequest(await c.req.json(), await readConfiguredCodexReasoningEffort());
2608
2712
  const provider = getCopilotProviderContext(c.get("config"));
2609
2713
  const search = new URL(c.req.url).search;
2610
2714
  const capability = getModelCapability(payload.model);
@@ -2758,6 +2862,8 @@ const startServer = (config) => serve({
2758
2862
 
2759
2863
  //#endregion
2760
2864
  //#region src/start.ts
2865
+ const getPublicModelId = (id) => id === "claude-opus-4.7-1m-internal" ? "claude-opus-4.7-1m" : id;
2866
+ const unique = (ids) => [...new Set(ids)];
2761
2867
  const fetchAvailableModels = async (config) => {
2762
2868
  try {
2763
2869
  const response = await fetchCopilot(getCopilotProviderContext(config), "/models", {
@@ -2897,10 +3003,13 @@ const start = defineCommand({
2897
3003
  consola.warn(`Could not update codex config (${codexConfigPath}):`, error);
2898
3004
  }
2899
3005
  const models = await fetchAvailableModels(config);
2900
- const supportedIds = new Set(MODEL_CAPABILITIES.map((m) => m.id));
2901
- const pickable = models.length > 0 ? models.filter((id) => supportedIds.has(id)) : MODEL_CAPABILITIES.map((m) => m.id);
2902
- const finalPickable = pickable.length > 0 ? pickable : MODEL_CAPABILITIES.map((m) => m.id);
2903
- if (models.length > 0) consola.info(`Available models:\n${models.map((id) => `- ${id}${supportedIds.has(id) ? " (bridge-supported)" : ""}`).join("\n")}`);
3006
+ const supportedIds = new Set(MODEL_CAPABILITIES.flatMap((m) => [m.id, ...m.aliases ?? []]));
3007
+ const fallbackModelIds = unique(MODEL_CAPABILITIES.map((m) => getPublicModelId(m.id)));
3008
+ const pickable = models.length > 0 ? unique(models.filter((id) => supportedIds.has(id)).map((id) => getPublicModelId(id))) : fallbackModelIds;
3009
+ const finalPickable = pickable.length > 0 ? pickable : fallbackModelIds;
3010
+ if (models.length > 0) consola.info(`Available models:\n${models.map((id) => {
3011
+ return `- ${getPublicModelId(id)}${supportedIds.has(id) ? " (bridge-supported)" : ""}`;
3012
+ }).join("\n")}`);
2904
3013
  else consola.warn("Could not fetch model list from upstream Copilot API");
2905
3014
  consola.box([
2906
3015
  `🌐 Usage viewer`,