copilot-relay 0.2.0 → 0.2.2
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 +16 -2
- package/dist/main.js +67 -16
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,17 @@ Routing:
|
|
|
37
37
|
| Requested model | Upstream model |
|
|
38
38
|
| --- | --- |
|
|
39
39
|
| contains `opus` | `claude-opus-4.8` |
|
|
40
|
-
|
|
|
40
|
+
| `gpt-5.6-sol[1m]`, plain `gpt-5.6-sol`, or another non-Opus alias | `gpt-5.6-sol` |
|
|
41
|
+
|
|
42
|
+
The relay advertises `gpt-5.6-sol[1m]` to Claude Code. The `[1m]` selector
|
|
43
|
+
changes Claude Code's client-side context budgeting only; every Copilot request
|
|
44
|
+
still uses `gpt-5.6-sol`, and the relay cannot enlarge GitHub Copilot's upstream
|
|
45
|
+
capacity. An explicit CLI or API model override that bypasses the managed Claude
|
|
46
|
+
settings is outside this guarantee.
|
|
47
|
+
|
|
48
|
+
Claude Code can still show its built-in Haiku and Sonnet picker entries. The
|
|
49
|
+
managed `model` field sets the startup default but does not restrict
|
|
50
|
+
`availableModels`; selecting any non-Opus alias still follows the GPT route.
|
|
41
51
|
|
|
42
52
|
## Install & run
|
|
43
53
|
|
|
@@ -48,7 +58,11 @@ npx copilot-relay@latest restart
|
|
|
48
58
|
npx copilot-relay@latest stop
|
|
49
59
|
```
|
|
50
60
|
|
|
51
|
-
`start`
|
|
61
|
+
With `claudeSetup: true`, `start` manages `ANTHROPIC_BASE_URL`, a dummy
|
|
62
|
+
`ANTHROPIC_AUTH_TOKEN` when auth is absent, and the configured GPT default via
|
|
63
|
+
the top-level `model` field in `~/.claude/settings.json`. Exact
|
|
64
|
+
`gpt-5.6-sol` model overrides are normalized to the Claude-facing
|
|
65
|
+
`gpt-5.6-sol[1m]` identity; unrelated model choices are preserved.
|
|
52
66
|
|
|
53
67
|
## Config
|
|
54
68
|
|
package/dist/main.js
CHANGED
|
@@ -349,8 +349,24 @@ const defaultModelRouting = {
|
|
|
349
349
|
opusModel: "claude-opus-4.8"
|
|
350
350
|
};
|
|
351
351
|
const isReasoningEffort = (value) => value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
|
|
352
|
-
const
|
|
352
|
+
const exactGpt56SolPattern = /^gpt-5\.6-sol(?:\[1m\])*$/i;
|
|
353
|
+
const claudeContextModelId = "gpt-5.6-sol[1m]";
|
|
354
|
+
const copilotModelId = "gpt-5.6-sol";
|
|
355
|
+
const normalizeClaudeModelId = (model) => exactGpt56SolPattern.test(model) ? claudeContextModelId : model;
|
|
356
|
+
const normalizeCopilotModelId = (model) => exactGpt56SolPattern.test(model) ? copilotModelId : model;
|
|
357
|
+
const getConfiguredModelRouting = () => runtimeState.modelRouting ?? defaultModelRouting;
|
|
358
|
+
const getModelRouting = () => {
|
|
359
|
+
const routing = getConfiguredModelRouting();
|
|
360
|
+
return {
|
|
361
|
+
gptModel: normalizeCopilotModelId(routing.gptModel),
|
|
362
|
+
opusModel: normalizeCopilotModelId(routing.opusModel)
|
|
363
|
+
};
|
|
364
|
+
};
|
|
353
365
|
const getExposedModelIds = () => {
|
|
366
|
+
const routing = getConfiguredModelRouting();
|
|
367
|
+
return [normalizeClaudeModelId(routing.gptModel), routing.opusModel];
|
|
368
|
+
};
|
|
369
|
+
const getUpstreamModelIds = () => {
|
|
354
370
|
const routing = getModelRouting();
|
|
355
371
|
return [routing.gptModel, routing.opusModel];
|
|
356
372
|
};
|
|
@@ -806,13 +822,30 @@ const stopExistingRelay = async (config) => {
|
|
|
806
822
|
|
|
807
823
|
//#endregion
|
|
808
824
|
//#region src/lib/claude-settings.ts
|
|
825
|
+
const knownModelEnvKeys = [
|
|
826
|
+
"ANTHROPIC_MODEL",
|
|
827
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
828
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
829
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
830
|
+
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
|
831
|
+
"ANTHROPIC_SMALL_FAST_MODEL",
|
|
832
|
+
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
833
|
+
];
|
|
834
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
835
|
+
const hasMalformedModelOverride = (parsed) => {
|
|
836
|
+
if (parsed.model !== void 0 && typeof parsed.model !== "string") return true;
|
|
837
|
+
const env = parsed.env;
|
|
838
|
+
if (!isRecord$1(env)) return false;
|
|
839
|
+
return knownModelEnvKeys.some((key) => env[key] !== void 0 && typeof env[key] !== "string");
|
|
840
|
+
};
|
|
841
|
+
const hasPrimaryModelOverride = (parsed, env) => typeof parsed.model === "string" || typeof env.ANTHROPIC_MODEL === "string";
|
|
809
842
|
/**
|
|
810
843
|
* Update `~/.claude/settings.json` so its env block points Claude Code at the
|
|
811
|
-
* running proxy. Preserves
|
|
812
|
-
*
|
|
844
|
+
* running proxy. Preserves unrelated keys and model choices while normalizing
|
|
845
|
+
* managed gpt-5.6-sol overrides. Sets a dummy auth token only if none is present.
|
|
813
846
|
*/
|
|
814
847
|
async function applyClaudeConfig(input) {
|
|
815
|
-
const { configPath: configPath$1, baseUrl } = input;
|
|
848
|
+
const { configPath: configPath$1, baseUrl, gptModel } = input;
|
|
816
849
|
let raw = "";
|
|
817
850
|
let created = false;
|
|
818
851
|
try {
|
|
@@ -822,9 +855,19 @@ async function applyClaudeConfig(input) {
|
|
|
822
855
|
else throw error;
|
|
823
856
|
}
|
|
824
857
|
let parsed = {};
|
|
858
|
+
if (!created && raw.trim().length === 0) return {
|
|
859
|
+
configPath: configPath$1,
|
|
860
|
+
changed: false,
|
|
861
|
+
created: false
|
|
862
|
+
};
|
|
825
863
|
if (raw.trim().length > 0) try {
|
|
826
|
-
|
|
827
|
-
if (
|
|
864
|
+
const value = JSON.parse(raw);
|
|
865
|
+
if (!isRecord$1(value) || value.env !== void 0 && !isRecord$1(value.env) || hasMalformedModelOverride(value)) return {
|
|
866
|
+
configPath: configPath$1,
|
|
867
|
+
changed: false,
|
|
868
|
+
created: false
|
|
869
|
+
};
|
|
870
|
+
parsed = value;
|
|
828
871
|
} catch {
|
|
829
872
|
return {
|
|
830
873
|
configPath: configPath$1,
|
|
@@ -832,12 +875,19 @@ async function applyClaudeConfig(input) {
|
|
|
832
875
|
created: false
|
|
833
876
|
};
|
|
834
877
|
}
|
|
835
|
-
const env =
|
|
878
|
+
const env = isRecord$1(parsed.env) ? { ...parsed.env } : {};
|
|
836
879
|
const previousBaseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : void 0;
|
|
880
|
+
const hadPrimaryModelOverride = hasPrimaryModelOverride(parsed, env);
|
|
881
|
+
const claudeGptModel = normalizeClaudeModelId(gptModel);
|
|
882
|
+
const shouldSeedTopLevelModel = !hadPrimaryModelOverride;
|
|
883
|
+
const nextModel = typeof parsed.model === "string" ? normalizeClaudeModelId(parsed.model) : void 0;
|
|
884
|
+
for (const key of knownModelEnvKeys) if (typeof env[key] === "string") env[key] = normalizeClaudeModelId(env[key]);
|
|
837
885
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
838
886
|
if (typeof env.ANTHROPIC_AUTH_TOKEN !== "string" || !env.ANTHROPIC_AUTH_TOKEN) env.ANTHROPIC_AUTH_TOKEN = "dummy";
|
|
839
887
|
const next = {
|
|
840
888
|
...parsed,
|
|
889
|
+
...nextModel !== void 0 && { model: nextModel },
|
|
890
|
+
...shouldSeedTopLevelModel && { model: claudeGptModel },
|
|
841
891
|
env
|
|
842
892
|
};
|
|
843
893
|
const serialized = `${JSON.stringify(next, null, 2)}\n`;
|
|
@@ -1539,7 +1589,7 @@ async function shouldRetryWithResponses(response, signal, timeoutMs) {
|
|
|
1539
1589
|
|
|
1540
1590
|
//#endregion
|
|
1541
1591
|
//#region src/lib/preflight.ts
|
|
1542
|
-
const
|
|
1592
|
+
const fetchUpstreamModelIds = async (config) => {
|
|
1543
1593
|
const provider = getCopilotProviderContext(config);
|
|
1544
1594
|
const signal = createCopilotRequestSignal(void 0, config.upstreamTimeoutMs);
|
|
1545
1595
|
const response = await fetchCopilot(provider, "/models", {
|
|
@@ -1554,8 +1604,8 @@ const getUpstreamModelIds = async (config) => {
|
|
|
1554
1604
|
return new Set((payload.data ?? []).map((model) => model.id).filter((id) => typeof id === "string" && id.length > 0));
|
|
1555
1605
|
};
|
|
1556
1606
|
const ensureRequiredModels = async (config) => {
|
|
1557
|
-
const upstreamModels = await
|
|
1558
|
-
const requiredModels =
|
|
1607
|
+
const upstreamModels = await fetchUpstreamModelIds(config);
|
|
1608
|
+
const requiredModels = getUpstreamModelIds();
|
|
1559
1609
|
const missingModels = requiredModels.filter((model) => !upstreamModels.has(model));
|
|
1560
1610
|
if (missingModels.length > 0) throw new Error(`Required Copilot model(s) unavailable upstream: ${missingModels.join(", ")}`);
|
|
1561
1611
|
log.info(`Upstream models available: ${requiredModels.join(", ")}`);
|
|
@@ -1589,7 +1639,7 @@ const validateModelRequest = async (config, model, thinkEffort) => {
|
|
|
1589
1639
|
const validateUpstream = async (config, thinkEffort) => {
|
|
1590
1640
|
log.info("Running upstream preflight");
|
|
1591
1641
|
await ensureRequiredModels(config);
|
|
1592
|
-
for (const model of
|
|
1642
|
+
for (const model of getUpstreamModelIds()) await validateModelRequest(config, model, thinkEffort);
|
|
1593
1643
|
};
|
|
1594
1644
|
|
|
1595
1645
|
//#endregion
|
|
@@ -1865,7 +1915,7 @@ function translateToClaude(response, toolNameMapper = createClaudeToolNameMapper
|
|
|
1865
1915
|
id: response.id,
|
|
1866
1916
|
type: "message",
|
|
1867
1917
|
role: "assistant",
|
|
1868
|
-
model: response.model,
|
|
1918
|
+
model: normalizeClaudeModelId(response.model),
|
|
1869
1919
|
content: [
|
|
1870
1920
|
...allThinkingBlocks,
|
|
1871
1921
|
...allTextBlocks,
|
|
@@ -1945,7 +1995,7 @@ function translateChunkToClaudeEvents(chunk, state, toolNameMapper = createClaud
|
|
|
1945
1995
|
type: "message",
|
|
1946
1996
|
role: "assistant",
|
|
1947
1997
|
content: [],
|
|
1948
|
-
model: chunk.model,
|
|
1998
|
+
model: normalizeClaudeModelId(chunk.model),
|
|
1949
1999
|
stop_reason: null,
|
|
1950
2000
|
stop_sequence: null,
|
|
1951
2001
|
usage: {
|
|
@@ -2083,7 +2133,7 @@ const isClaudeCodeWebSearchTool = (tool) => tool.name === claudeCodeWebSearchToo
|
|
|
2083
2133
|
const isClaudeWebSearchTool = (tool) => isAnthropicNativeWebSearchTool(tool) || isClaudeCodeWebSearchTool(tool);
|
|
2084
2134
|
const hasClaudeWebSearch = (payload) => payload.tools?.some(isClaudeWebSearchTool) ?? false;
|
|
2085
2135
|
const isClaudeWebSearchToolName = (name) => name === "web_search" || name === claudeCodeWebSearchToolName;
|
|
2086
|
-
const getWebSearchBackendModel = (config) => config.webSearchBackend?.trim() || getModelRouting().gptModel;
|
|
2136
|
+
const getWebSearchBackendModel = (config) => normalizeCopilotModelId(config.webSearchBackend?.trim() || getModelRouting().gptModel);
|
|
2087
2137
|
const prepareClaudeWebSearchDecisionPayload = (payload) => {
|
|
2088
2138
|
if (!hasClaudeWebSearch(payload)) return payload;
|
|
2089
2139
|
return {
|
|
@@ -2257,7 +2307,7 @@ const createClaudeWebSearchResponse = (search) => {
|
|
|
2257
2307
|
type: "message",
|
|
2258
2308
|
role: "assistant",
|
|
2259
2309
|
content,
|
|
2260
|
-
model: search.model,
|
|
2310
|
+
model: normalizeClaudeModelId(search.model),
|
|
2261
2311
|
stop_reason: "end_turn",
|
|
2262
2312
|
stop_sequence: null,
|
|
2263
2313
|
usage: {
|
|
@@ -2845,7 +2895,8 @@ async function startRelay(appConfig) {
|
|
|
2845
2895
|
if (appConfig.claudeSetup) try {
|
|
2846
2896
|
const claudeResult = await applyClaudeConfig({
|
|
2847
2897
|
baseUrl,
|
|
2848
|
-
configPath: claudeConfigPath$1
|
|
2898
|
+
configPath: claudeConfigPath$1,
|
|
2899
|
+
gptModel: appConfig.gptModel
|
|
2849
2900
|
});
|
|
2850
2901
|
if (claudeResult.changed) {
|
|
2851
2902
|
log.info(`claude settings ${claudeResult.created ? "created" : "updated"}: ${claudeResult.configPath}`);
|