betahi-copilot-bridge 0.1.3 → 0.1.4
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 +21 -9
- package/dist/main.js +155 -73
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">copilot-bridge</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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" alt="npm version"></a>
|
|
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
|
+
</p>
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
> Use GitHub Copilot as a local OpenAI/Anthropic-compatible API, so [Codex CLI](https://github.com/openai/codex), [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) and Continue can talk to Copilot with minimal configuration.
|
|
9
9
|
|
|
10
10
|
> [!WARNING]
|
|
11
11
|
> This is a reverse-engineered bridge for the GitHub Copilot API. It is not
|
|
@@ -123,7 +123,20 @@ Bridge endpoints: `POST /v1/messages`, `POST /v1/messages/count_tokens`.
|
|
|
123
123
|
or port. Pass `--no-claude-setup` to skip this. All other keys in your
|
|
124
124
|
settings file (model overrides, plugins, marketplaces) are preserved.
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
Minimal recommended config is:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"env": {
|
|
131
|
+
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4142",
|
|
132
|
+
"ANTHROPIC_AUTH_TOKEN": "dummy",
|
|
133
|
+
"ANTHROPIC_MODEL": "claude-opus-4.7",
|
|
134
|
+
"MODEL_REASONING_EFFORT": "medium"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Optional slot-specific:
|
|
127
140
|
|
|
128
141
|
```json
|
|
129
142
|
{
|
|
@@ -134,8 +147,7 @@ If you prefer to wire it yourself:
|
|
|
134
147
|
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4.6",
|
|
135
148
|
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4.5",
|
|
136
149
|
"MODEL_REASONING_EFFORT": "medium"
|
|
137
|
-
}
|
|
138
|
-
"model": "claude-opus-4.7"
|
|
150
|
+
}
|
|
139
151
|
}
|
|
140
152
|
```
|
|
141
153
|
|
package/dist/main.js
CHANGED
|
@@ -326,15 +326,21 @@ const parsePortFromBaseUrl = (baseUrl) => {
|
|
|
326
326
|
return;
|
|
327
327
|
}
|
|
328
328
|
};
|
|
329
|
-
const
|
|
329
|
+
const getClaudeSettings = async () => {
|
|
330
330
|
const merged = {};
|
|
331
|
+
let model;
|
|
331
332
|
for (const filePath of getClaudeSettingsPaths()) {
|
|
332
333
|
const settings = await readClaudeSettingsFile(filePath);
|
|
334
|
+
if (typeof settings?.model === "string") model = settings.model;
|
|
333
335
|
if (!settings?.env) continue;
|
|
334
336
|
for (const [key, value] of Object.entries(settings.env)) if (typeof value === "string") merged[key] = value;
|
|
335
337
|
}
|
|
336
|
-
return
|
|
338
|
+
return {
|
|
339
|
+
env: merged,
|
|
340
|
+
model
|
|
341
|
+
};
|
|
337
342
|
};
|
|
343
|
+
const getClaudeSettingsEnv = async () => (await getClaudeSettings()).env;
|
|
338
344
|
/**
|
|
339
345
|
* Update `~/.claude/settings.json` so its env block points Claude Code at the
|
|
340
346
|
* running bridge. Preserves all unrelated keys (model overrides, plugins,
|
|
@@ -386,6 +392,22 @@ async function applyClaudeConfig(input) {
|
|
|
386
392
|
};
|
|
387
393
|
}
|
|
388
394
|
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/lib/claude-launch.ts
|
|
397
|
+
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
398
|
+
const pickClaudeLaunchDefaults = (availableModels, preferredModel) => {
|
|
399
|
+
return { model: preferredModel && availableModels.includes(preferredModel) ? preferredModel : availableModels.includes("gpt-5.3-codex") ? "gpt-5.3-codex" : availableModels[0] ?? "gpt-5.3-codex" };
|
|
400
|
+
};
|
|
401
|
+
const buildClaudeLaunchCommand = (input) => {
|
|
402
|
+
const env = {
|
|
403
|
+
ANTHROPIC_BASE_URL: input.baseUrl,
|
|
404
|
+
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
405
|
+
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
406
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
407
|
+
};
|
|
408
|
+
return `${Object.entries(env).map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ")} && claude --model ${shellQuote(input.model)}`;
|
|
409
|
+
};
|
|
410
|
+
|
|
389
411
|
//#endregion
|
|
390
412
|
//#region src/lib/codex-config.ts
|
|
391
413
|
const BEGIN_MARK = "# >>> copilot-bridge managed block — auto-generated, do not edit between markers >>>";
|
|
@@ -585,6 +607,10 @@ const MODEL_CAPABILITIES = [
|
|
|
585
607
|
"xhigh"
|
|
586
608
|
],
|
|
587
609
|
default: "medium"
|
|
610
|
+
},
|
|
611
|
+
textVerbosity: {
|
|
612
|
+
supported: ["medium"],
|
|
613
|
+
default: "medium"
|
|
588
614
|
}
|
|
589
615
|
},
|
|
590
616
|
{
|
|
@@ -696,6 +722,8 @@ const CAPABILITY_BY_ID = new Map(MODEL_CAPABILITIES.flatMap((m) => {
|
|
|
696
722
|
const resolveModelId = (modelId) => CAPABILITY_BY_ID.get(modelId)?.id ?? modelId;
|
|
697
723
|
const getModelCapability = (modelId) => CAPABILITY_BY_ID.get(modelId);
|
|
698
724
|
const isReasoningEffort = (value) => value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
|
|
725
|
+
const isTextVerbosity = (value) => value === "low" || value === "medium" || value === "high";
|
|
726
|
+
const normalizeRequestedReasoningEffort = (requested) => requested === "minimal" ? "low" : requested;
|
|
699
727
|
const clampReasoningEffort = (modelId, requested) => {
|
|
700
728
|
const capability = getModelCapability(modelId);
|
|
701
729
|
if (!capability?.reasoning) return void 0;
|
|
@@ -704,14 +732,16 @@ const clampReasoningEffort = (modelId, requested) => {
|
|
|
704
732
|
effort: defaultEffort,
|
|
705
733
|
changed: false
|
|
706
734
|
};
|
|
707
|
-
|
|
735
|
+
const normalizedRequested = normalizeRequestedReasoningEffort(requested);
|
|
736
|
+
if (!isReasoningEffort(normalizedRequested)) return {
|
|
708
737
|
effort: defaultEffort,
|
|
709
738
|
changed: true,
|
|
710
739
|
reason: "unsupported-effort"
|
|
711
740
|
};
|
|
712
|
-
if (supported.includes(
|
|
713
|
-
effort:
|
|
714
|
-
changed:
|
|
741
|
+
if (supported.includes(normalizedRequested)) return {
|
|
742
|
+
effort: normalizedRequested,
|
|
743
|
+
changed: normalizedRequested !== requested,
|
|
744
|
+
reason: normalizedRequested !== requested ? "unsupported-effort" : void 0
|
|
715
745
|
};
|
|
716
746
|
const order = [
|
|
717
747
|
"none",
|
|
@@ -721,7 +751,7 @@ const clampReasoningEffort = (modelId, requested) => {
|
|
|
721
751
|
"xhigh",
|
|
722
752
|
"max"
|
|
723
753
|
];
|
|
724
|
-
const reqIdx = order.indexOf(
|
|
754
|
+
const reqIdx = order.indexOf(normalizedRequested);
|
|
725
755
|
const supportedSorted = [...supported].sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
726
756
|
const lowest = supportedSorted[0];
|
|
727
757
|
const highest = supportedSorted[supportedSorted.length - 1];
|
|
@@ -1007,36 +1037,9 @@ const usesMaxCompletionTokens = (modelId) => modelId.startsWith("gpt-5");
|
|
|
1007
1037
|
const isClaudeOpus47Model$1 = (modelId) => modelId.startsWith("claude-opus-4.7");
|
|
1008
1038
|
const MAX_USER_LENGTH = 64;
|
|
1009
1039
|
const defaultReasoningEffort = (modelId) => usesMaxCompletionTokens(modelId) ? "medium" : void 0;
|
|
1010
|
-
const getAllowedReasoningEfforts = (modelId) => {
|
|
1011
|
-
if (modelId.startsWith("gpt-5.5")) return [
|
|
1012
|
-
"none",
|
|
1013
|
-
"low",
|
|
1014
|
-
"medium",
|
|
1015
|
-
"high",
|
|
1016
|
-
"xhigh"
|
|
1017
|
-
];
|
|
1018
|
-
if (modelId.startsWith("gpt-5.4-mini")) return [
|
|
1019
|
-
"none",
|
|
1020
|
-
"low",
|
|
1021
|
-
"medium"
|
|
1022
|
-
];
|
|
1023
|
-
if (modelId.startsWith("gpt-5.4") || modelId.startsWith("gpt-5.3-codex")) return [
|
|
1024
|
-
"low",
|
|
1025
|
-
"medium",
|
|
1026
|
-
"high",
|
|
1027
|
-
"xhigh"
|
|
1028
|
-
];
|
|
1029
|
-
if (usesMaxCompletionTokens(modelId)) return [
|
|
1030
|
-
"low",
|
|
1031
|
-
"medium",
|
|
1032
|
-
"high",
|
|
1033
|
-
"xhigh"
|
|
1034
|
-
];
|
|
1035
|
-
return [];
|
|
1036
|
-
};
|
|
1037
1040
|
const sanitizeReasoningEffortForModel = (modelId, reasoningEffort) => {
|
|
1038
1041
|
if (!reasoningEffort) return;
|
|
1039
|
-
return
|
|
1042
|
+
return clampReasoningEffort(modelId, reasoningEffort)?.effort;
|
|
1040
1043
|
};
|
|
1041
1044
|
const normalizeReasoningEffort$1 = (value) => {
|
|
1042
1045
|
switch (value?.toLowerCase()) {
|
|
@@ -1071,7 +1074,7 @@ const getConfiguredReasoningEffort = (client, claudeSettingsEnv) => {
|
|
|
1071
1074
|
};
|
|
1072
1075
|
const getRequestedReasoningEffort = (payload, claudeSettingsEnv, client) => {
|
|
1073
1076
|
const configuredReasoningEffort = getConfiguredReasoningEffort(client, claudeSettingsEnv);
|
|
1074
|
-
const defaultEffort = client === "claude" ?
|
|
1077
|
+
const defaultEffort = client === "claude" ? clampReasoningEffort(payload.model, void 0)?.effort : defaultReasoningEffort(payload.model);
|
|
1075
1078
|
const requestedReasoningEffort = payload.reasoning_effort ?? normalizeReasoningEffort$1(configuredReasoningEffort);
|
|
1076
1079
|
return sanitizeReasoningEffortForModel(payload.model, requestedReasoningEffort) ?? defaultEffort;
|
|
1077
1080
|
};
|
|
@@ -1090,19 +1093,15 @@ const buildRequestPayload = (payload, claudeSettingsEnv, client) => {
|
|
|
1090
1093
|
const reasoningEffort = usesMaxCompletionTokens(payload.model) && payload.tools !== null && payload.tools !== void 0 && payload.tools.length > 0 ? void 0 : requestedReasoningEffort;
|
|
1091
1094
|
if (!usesMaxCompletionTokens(payload.model) || payload.max_tokens === null || payload.max_tokens === void 0) {
|
|
1092
1095
|
const shouldAttachReasoningEffort = !isClaudeOpus47Model$1(payload.model);
|
|
1093
|
-
|
|
1096
|
+
return {
|
|
1094
1097
|
...payload,
|
|
1095
1098
|
output_config: requestedClaudeOpus47Effort ? {
|
|
1096
1099
|
...payload.output_config,
|
|
1097
1100
|
effort: requestedClaudeOpus47Effort
|
|
1098
1101
|
} : payload.output_config,
|
|
1099
|
-
reasoning_effort: shouldAttachReasoningEffort ?
|
|
1102
|
+
reasoning_effort: shouldAttachReasoningEffort ? reasoningEffort : void 0,
|
|
1100
1103
|
user: sanitizeUserIdentifier(payload.user)
|
|
1101
1104
|
};
|
|
1102
|
-
return !shouldAttachReasoningEffort || reasoningEffort === null || reasoningEffort === void 0 ? sanitizedPayload : {
|
|
1103
|
-
...sanitizedPayload,
|
|
1104
|
-
reasoning_effort: reasoningEffort
|
|
1105
|
-
};
|
|
1106
1105
|
}
|
|
1107
1106
|
return {
|
|
1108
1107
|
...payload,
|
|
@@ -1141,7 +1140,10 @@ const createChatCompletions = async (config, payload, options) => {
|
|
|
1141
1140
|
vision: enableVision,
|
|
1142
1141
|
initiator
|
|
1143
1142
|
});
|
|
1144
|
-
|
|
1143
|
+
await logUpstreamError("Failed to create chat completions", response, {
|
|
1144
|
+
model: payload.model,
|
|
1145
|
+
route: "/chat/completions"
|
|
1146
|
+
});
|
|
1145
1147
|
throw new HTTPError("Failed to create chat completions", response);
|
|
1146
1148
|
}
|
|
1147
1149
|
if (payload.stream) return events(response);
|
|
@@ -1161,12 +1163,25 @@ async function createResponses(provider, payload, claudeSettingsEnv, client, opt
|
|
|
1161
1163
|
initiator: options.initiator
|
|
1162
1164
|
});
|
|
1163
1165
|
if (!response.ok) {
|
|
1164
|
-
|
|
1166
|
+
await logUpstreamError("Failed to create responses", response, {
|
|
1167
|
+
model: payload.model,
|
|
1168
|
+
route: "/responses"
|
|
1169
|
+
});
|
|
1165
1170
|
throw new HTTPError("Failed to create responses", response);
|
|
1166
1171
|
}
|
|
1167
1172
|
if (payload.stream) return translateResponsesStreamToChatCompletionStream(events(response));
|
|
1168
1173
|
return translateResponsesToChatCompletion(await response.json());
|
|
1169
1174
|
}
|
|
1175
|
+
async function logUpstreamError(message, response, context) {
|
|
1176
|
+
const errorBody = await response.clone().text().catch(() => "");
|
|
1177
|
+
consola.error(message, {
|
|
1178
|
+
route: context.route,
|
|
1179
|
+
model: context.model,
|
|
1180
|
+
status: response.status,
|
|
1181
|
+
statusText: response.statusText,
|
|
1182
|
+
body: errorBody || void 0
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1170
1185
|
async function shouldRetryWithResponses(response) {
|
|
1171
1186
|
try {
|
|
1172
1187
|
return (await response.clone().json()).error?.code === "unsupported_api_for_model";
|
|
@@ -1247,14 +1262,22 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
1247
1262
|
const normalizeModelId = (modelId) => modelId.trim().toLowerCase().replaceAll(/[\s._-]+/g, "");
|
|
1248
1263
|
const stripSnapshotSuffix = (modelId) => {
|
|
1249
1264
|
const id = modelId.trim().toLowerCase().replace(/\[1m\]$/, "");
|
|
1250
|
-
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1265
|
+
const claudeSnapshotMatch = id.match(/^claude-(opus|sonnet|haiku)-(\d)-(\d)(-1m)?(?:-\d{8})?$/);
|
|
1266
|
+
if (claudeSnapshotMatch) {
|
|
1267
|
+
const [, family, major, minor, contextWindow = ""] = claudeSnapshotMatch;
|
|
1268
|
+
return `claude-${family}-${major}.${minor}${contextWindow}`;
|
|
1269
|
+
}
|
|
1253
1270
|
return id;
|
|
1254
1271
|
};
|
|
1255
1272
|
const getAliasCandidates = (modelId) => {
|
|
1256
1273
|
const canonicalModelId = stripSnapshotSuffix(modelId);
|
|
1257
1274
|
const aliases = new Set([canonicalModelId]);
|
|
1275
|
+
const canonicalClaudeMatch = canonicalModelId.match(/^claude-(opus|sonnet|haiku)-(\d+)\.(\d+)(-1m)?$/);
|
|
1276
|
+
if (canonicalClaudeMatch) {
|
|
1277
|
+
const [, family, major, minor, contextWindow = ""] = canonicalClaudeMatch;
|
|
1278
|
+
aliases.add(`claude-${family}-${major}-${minor}${contextWindow}`);
|
|
1279
|
+
return [...aliases];
|
|
1280
|
+
}
|
|
1258
1281
|
const familyMatch = canonicalModelId.match(/^[a-z]+(?:-[a-z]+)*-\d+(?:\.\d+)?/);
|
|
1259
1282
|
if (familyMatch) {
|
|
1260
1283
|
aliases.add(familyMatch[0]);
|
|
@@ -1318,13 +1341,36 @@ function mapOpenAIStopReasonToAnthropic(finishReason) {
|
|
|
1318
1341
|
//#endregion
|
|
1319
1342
|
//#region src/bridges/claude/non-stream-translation.ts
|
|
1320
1343
|
const normalizeClaudeModelAlias = (model) => {
|
|
1321
|
-
const
|
|
1344
|
+
const trimmed = model.trim().toLowerCase();
|
|
1345
|
+
if (trimmed === "opus[1m]") return "claude-opus-4.6-1m";
|
|
1346
|
+
const normalized = trimmed.replace(/\[1m\]$/, "");
|
|
1347
|
+
if (normalized === "opus") return "claude-opus";
|
|
1348
|
+
if (normalized === "sonnet") return "claude-sonnet";
|
|
1349
|
+
if (normalized === "haiku") return "claude-haiku";
|
|
1322
1350
|
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");
|
|
1323
1351
|
if (/^claude-opus-4-7-\d{8}$/.test(normalized)) return "claude-opus-4.7";
|
|
1324
1352
|
return normalized;
|
|
1325
1353
|
};
|
|
1326
|
-
|
|
1327
|
-
|
|
1354
|
+
const getConfiguredClaudeDefaultModel = (settings) => {
|
|
1355
|
+
if (!settings) return;
|
|
1356
|
+
const env = settings.env ?? {};
|
|
1357
|
+
const topLevelModel = settings.model?.trim();
|
|
1358
|
+
const anthropicModel = env.ANTHROPIC_MODEL;
|
|
1359
|
+
if (!topLevelModel) return anthropicModel;
|
|
1360
|
+
switch (topLevelModel.toLowerCase()) {
|
|
1361
|
+
case "opus[1m]": return env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? topLevelModel;
|
|
1362
|
+
case "opus": return env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? topLevelModel;
|
|
1363
|
+
case "sonnet": return env.ANTHROPIC_DEFAULT_SONNET_MODEL ?? topLevelModel;
|
|
1364
|
+
case "haiku": return env.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? env.ANTHROPIC_SMALL_FAST_MODEL ?? topLevelModel;
|
|
1365
|
+
default: return topLevelModel;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
const resolveClaudeRequestedModel = (model, settings) => {
|
|
1369
|
+
if (model.trim().toLowerCase() !== "definitely-not-a-real-model") return model;
|
|
1370
|
+
return getConfiguredClaudeDefaultModel(settings) ?? model;
|
|
1371
|
+
};
|
|
1372
|
+
function translateModelName(model, settings) {
|
|
1373
|
+
return resolveUpstreamModelId(normalizeClaudeModelAlias(resolveClaudeRequestedModel(model, settings)));
|
|
1328
1374
|
}
|
|
1329
1375
|
function isClaudeModel(modelId) {
|
|
1330
1376
|
return modelId.startsWith("claude-");
|
|
@@ -1353,13 +1399,13 @@ function getClaudeOpus47Effort(payload) {
|
|
|
1353
1399
|
if (budgetTokens <= 24576) return "high";
|
|
1354
1400
|
return "xhigh";
|
|
1355
1401
|
}
|
|
1356
|
-
function translateThinking(payload) {
|
|
1357
|
-
if (!isClaudeOpus47Model(translateModelName(payload.model))) return;
|
|
1402
|
+
function translateThinking(payload, settings) {
|
|
1403
|
+
if (!isClaudeOpus47Model(translateModelName(payload.model, settings))) return;
|
|
1358
1404
|
if (payload.thinking?.type === "adaptive") return { type: "adaptive" };
|
|
1359
1405
|
return payload.thinking?.type === "enabled" ? { type: "adaptive" } : void 0;
|
|
1360
1406
|
}
|
|
1361
|
-
function translateOutputConfig(payload) {
|
|
1362
|
-
if (!isClaudeOpus47Model(translateModelName(payload.model))) return;
|
|
1407
|
+
function translateOutputConfig(payload, settings) {
|
|
1408
|
+
if (!isClaudeOpus47Model(translateModelName(payload.model, settings))) return;
|
|
1363
1409
|
const effort = getClaudeOpus47Effort(payload);
|
|
1364
1410
|
return effort ? { effort } : void 0;
|
|
1365
1411
|
}
|
|
@@ -1374,8 +1420,8 @@ function normalizeReasoningEffort(value) {
|
|
|
1374
1420
|
default: return;
|
|
1375
1421
|
}
|
|
1376
1422
|
}
|
|
1377
|
-
function translateReasoningEffort(payload) {
|
|
1378
|
-
const modelId = translateModelName(payload.model);
|
|
1423
|
+
function translateReasoningEffort(payload, settings) {
|
|
1424
|
+
const modelId = translateModelName(payload.model, settings);
|
|
1379
1425
|
if (isClaudeModel(modelId)) return;
|
|
1380
1426
|
if (payload.reasoning_effort) return sanitizeReasoningEffortForModel(modelId, normalizeReasoningEffort(payload.reasoning_effort));
|
|
1381
1427
|
if (payload.thinking?.type !== "enabled") return;
|
|
@@ -1386,18 +1432,18 @@ function translateReasoningEffort(payload) {
|
|
|
1386
1432
|
if (budgetTokens <= 24576) return sanitizeReasoningEffortForModel(modelId, "high");
|
|
1387
1433
|
return sanitizeReasoningEffortForModel(modelId, "xhigh");
|
|
1388
1434
|
}
|
|
1389
|
-
function translateToOpenAI(payload) {
|
|
1435
|
+
function translateToOpenAI(payload, settings) {
|
|
1390
1436
|
return {
|
|
1391
|
-
model: translateModelName(payload.model),
|
|
1437
|
+
model: translateModelName(payload.model, settings),
|
|
1392
1438
|
messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system),
|
|
1393
1439
|
max_tokens: payload.max_tokens,
|
|
1394
1440
|
stop: payload.stop_sequences,
|
|
1395
1441
|
stream: payload.stream,
|
|
1396
1442
|
temperature: payload.temperature,
|
|
1397
1443
|
top_p: payload.top_p,
|
|
1398
|
-
thinking: translateThinking(payload),
|
|
1399
|
-
output_config: translateOutputConfig(payload),
|
|
1400
|
-
reasoning_effort: translateReasoningEffort(payload),
|
|
1444
|
+
thinking: translateThinking(payload, settings),
|
|
1445
|
+
output_config: translateOutputConfig(payload, settings),
|
|
1446
|
+
reasoning_effort: translateReasoningEffort(payload, settings),
|
|
1401
1447
|
user: payload.metadata?.user_id,
|
|
1402
1448
|
tools: translateAnthropicToolsToOpenAI(payload.tools),
|
|
1403
1449
|
tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice)
|
|
@@ -1870,7 +1916,7 @@ messageRoutes.post("/", async (c) => {
|
|
|
1870
1916
|
if (error instanceof RateLimitError) return c.json({ error: { message: error.message } }, 429);
|
|
1871
1917
|
throw error;
|
|
1872
1918
|
}
|
|
1873
|
-
const openAIPayload = translateToOpenAI(await c.req.json());
|
|
1919
|
+
const openAIPayload = translateToOpenAI(await c.req.json(), await getClaudeSettings());
|
|
1874
1920
|
try {
|
|
1875
1921
|
const response = await createChatCompletions(config, openAIPayload, { client: "claude" });
|
|
1876
1922
|
if (isNonStreamingResponse(response)) return c.json(translateToAnthropic(response));
|
|
@@ -1925,7 +1971,7 @@ messageRoutes.post("/count_tokens", async (c) => {
|
|
|
1925
1971
|
try {
|
|
1926
1972
|
const anthropicBeta = c.req.header("anthropic-beta");
|
|
1927
1973
|
const anthropicPayload = await c.req.json();
|
|
1928
|
-
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
1974
|
+
const openAIPayload = translateToOpenAI(anthropicPayload, await getClaudeSettings());
|
|
1929
1975
|
const selectedModel = resolveModel(openAIPayload.model);
|
|
1930
1976
|
if (!selectedModel) {
|
|
1931
1977
|
consola.warn(`Model ${openAIPayload.model} not found in registry, returning fallback token count`);
|
|
@@ -2309,6 +2355,15 @@ const normalizeCodexResponsesRequest = (payload) => {
|
|
|
2309
2355
|
...incoming ?? {},
|
|
2310
2356
|
effort: clamped.effort
|
|
2311
2357
|
};
|
|
2358
|
+
const text = isPlainObject(next.text) ? next.text : void 0;
|
|
2359
|
+
if (text && capability.textVerbosity && "verbosity" in text) {
|
|
2360
|
+
const { supported, default: defaultVerbosity } = capability.textVerbosity;
|
|
2361
|
+
const verbosity = text.verbosity;
|
|
2362
|
+
next.text = {
|
|
2363
|
+
...text,
|
|
2364
|
+
verbosity: isTextVerbosity(verbosity) && supported.includes(verbosity) ? verbosity : defaultVerbosity
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2312
2367
|
return next;
|
|
2313
2368
|
};
|
|
2314
2369
|
|
|
@@ -2497,24 +2552,29 @@ const start = defineCommand({
|
|
|
2497
2552
|
default: false,
|
|
2498
2553
|
description: "Print GitHub and Copilot tokens during startup."
|
|
2499
2554
|
},
|
|
2500
|
-
"
|
|
2555
|
+
"codex-setup": {
|
|
2501
2556
|
type: "boolean",
|
|
2502
|
-
default:
|
|
2557
|
+
default: true,
|
|
2503
2558
|
description: "Skip writing the bridge provider into ~/.codex/config.toml."
|
|
2504
2559
|
},
|
|
2505
|
-
"
|
|
2560
|
+
"claude-setup": {
|
|
2506
2561
|
type: "boolean",
|
|
2507
|
-
default:
|
|
2562
|
+
default: true,
|
|
2508
2563
|
description: "Skip writing ANTHROPIC_BASE_URL into ~/.claude/settings.json."
|
|
2509
2564
|
},
|
|
2565
|
+
"claude-code": {
|
|
2566
|
+
type: "boolean",
|
|
2567
|
+
default: false,
|
|
2568
|
+
description: "Print a one-shot `export ... && claude` command and do not modify user config files."
|
|
2569
|
+
},
|
|
2510
2570
|
"select-model": {
|
|
2511
2571
|
type: "boolean",
|
|
2512
2572
|
default: false,
|
|
2513
2573
|
description: "Force the model picker even when ~/.codex/config.toml already has a model."
|
|
2514
2574
|
},
|
|
2515
|
-
|
|
2575
|
+
prompt: {
|
|
2516
2576
|
type: "boolean",
|
|
2517
|
-
default:
|
|
2577
|
+
default: true,
|
|
2518
2578
|
description: "Never prompt; use the existing model from ~/.codex/config.toml as-is."
|
|
2519
2579
|
},
|
|
2520
2580
|
"rate-limit": {
|
|
@@ -2553,7 +2613,9 @@ const start = defineCommand({
|
|
|
2553
2613
|
consola.info(`copilot base url: ${config.copilotBaseUrl}`);
|
|
2554
2614
|
const baseUrl = `http://${config.host}:${config.port}`;
|
|
2555
2615
|
const codexConfigPath = CODEX_DEFAULTS.configPath;
|
|
2556
|
-
|
|
2616
|
+
const isClaudeCodeMode = Boolean(args["claude-code"]);
|
|
2617
|
+
if (isClaudeCodeMode) consola.info("--claude-code mode: not modifying ~/.claude/settings.json or ~/.codex/config.toml");
|
|
2618
|
+
if (!isClaudeCodeMode && args["claude-setup"]) try {
|
|
2557
2619
|
const claudeResult = await applyClaudeConfig({
|
|
2558
2620
|
baseUrl,
|
|
2559
2621
|
configPath: claudeConfigPath
|
|
@@ -2569,7 +2631,7 @@ const start = defineCommand({
|
|
|
2569
2631
|
let chosenModel = codexUserConfig.model;
|
|
2570
2632
|
let chosenEffort = codexUserConfig.modelReasoningEffort;
|
|
2571
2633
|
if (chosenModel && !chosenEffort) consola.info(`codex model_reasoning_effort not set; using ${chosenModel}'s default reasoning effort`);
|
|
2572
|
-
if (!args["
|
|
2634
|
+
if (!isClaudeCodeMode && args["codex-setup"]) try {
|
|
2573
2635
|
const result = await applyCodexConfig({
|
|
2574
2636
|
baseUrl: `${baseUrl}/v1`,
|
|
2575
2637
|
configPath: codexConfigPath,
|
|
@@ -2596,7 +2658,7 @@ const start = defineCommand({
|
|
|
2596
2658
|
``,
|
|
2597
2659
|
` https://betahi.github.io/copilot-bridge?endpoint=${baseUrl}/usage`
|
|
2598
2660
|
].join("\n"));
|
|
2599
|
-
if (!args
|
|
2661
|
+
if (!isClaudeCodeMode && args.prompt && finalPickable.length > 0 && (args["select-model"] || !chosenModel)) {
|
|
2600
2662
|
const defaultId = chosenModel && finalPickable.includes(chosenModel) ? chosenModel : finalPickable.includes("gpt-5.3-codex") ? "gpt-5.3-codex" : finalPickable[0];
|
|
2601
2663
|
const selected = await consola.prompt(`Select a model for codex (writes to ${codexConfigPath})`, {
|
|
2602
2664
|
type: "select",
|
|
@@ -2607,7 +2669,7 @@ const start = defineCommand({
|
|
|
2607
2669
|
chosenModel = selected;
|
|
2608
2670
|
const supported = getModelCapability(selected)?.reasoning?.supported;
|
|
2609
2671
|
if (chosenEffort && supported && !supported.includes(chosenEffort)) chosenEffort = void 0;
|
|
2610
|
-
if (
|
|
2672
|
+
if (args["codex-setup"]) try {
|
|
2611
2673
|
const result = await applyCodexConfig({
|
|
2612
2674
|
baseUrl: `${baseUrl}/v1`,
|
|
2613
2675
|
configPath: codexConfigPath,
|
|
@@ -2621,6 +2683,26 @@ const start = defineCommand({
|
|
|
2621
2683
|
}
|
|
2622
2684
|
}
|
|
2623
2685
|
}
|
|
2686
|
+
if (isClaudeCodeMode) {
|
|
2687
|
+
const defaults = pickClaudeLaunchDefaults(finalPickable, chosenModel);
|
|
2688
|
+
let selectedModel = defaults.model;
|
|
2689
|
+
if (args.prompt && finalPickable.length > 0) selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
2690
|
+
type: "select",
|
|
2691
|
+
options: finalPickable,
|
|
2692
|
+
initial: defaults.model
|
|
2693
|
+
});
|
|
2694
|
+
const command = buildClaudeLaunchCommand({
|
|
2695
|
+
baseUrl,
|
|
2696
|
+
model: selectedModel
|
|
2697
|
+
});
|
|
2698
|
+
consola.box([
|
|
2699
|
+
"Claude one-shot command",
|
|
2700
|
+
"",
|
|
2701
|
+
` ${command}`,
|
|
2702
|
+
"",
|
|
2703
|
+
" The bridge verified this path with explicit --model; current Claude CLI did not reliably honor ANTHROPIC_MODEL env defaults."
|
|
2704
|
+
].join("\n"));
|
|
2705
|
+
}
|
|
2624
2706
|
await new Promise((resolve, reject) => {
|
|
2625
2707
|
server.on("close", resolve);
|
|
2626
2708
|
server.on("error", reject);
|