github-router 0.3.10 → 0.3.12
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/LICENSE +21 -21
- package/README.md +206 -206
- package/dist/main.js +197 -29
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
import process$1 from "node:process";
|
|
9
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
9
10
|
import { serve } from "srvx";
|
|
10
11
|
import { getProxyForUrl } from "proxy-from-env";
|
|
11
12
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
@@ -76,7 +77,7 @@ const copilotHeaders = (state$1, vision = false, integrationId = "vscode-chat")
|
|
|
76
77
|
if (vision) headers["copilot-vision-request"] = "true";
|
|
77
78
|
return headers;
|
|
78
79
|
};
|
|
79
|
-
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
80
|
+
const GITHUB_API_BASE_URL = process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
80
81
|
const githubHeaders = (state$1) => ({
|
|
81
82
|
...standardHeaders(),
|
|
82
83
|
authorization: `token ${state$1.githubToken}`,
|
|
@@ -222,19 +223,68 @@ function filterBetaHeader(value) {
|
|
|
222
223
|
return value.split(",").map((v) => v.trim()).filter((v) => v && ALLOWED_BETA_PREFIXES.some((prefix) => v.startsWith(prefix))).join(",") || void 0;
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
226
|
+
* Normalize a model ID for fuzzy comparison: lowercase, replace dots with
|
|
227
|
+
* dashes, insert dash at letter→digit boundaries, and collapse repeated
|
|
228
|
+
* dashes. E.g. "gpt5.3-codex" → "gpt-5-3-codex", "GPT-5.3-Codex" → "gpt-5-3-codex".
|
|
229
|
+
*/
|
|
230
|
+
function normalizeModelId(id) {
|
|
231
|
+
return id.toLowerCase().replace(/\./g, "-").replace(/([a-z])(\d)/g, "$1-$2").replace(/-{2,}/g, "-");
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
225
234
|
* Resolve a model name to the best available variant in the Copilot model list.
|
|
226
|
-
*
|
|
235
|
+
*
|
|
236
|
+
* Resolution cascade:
|
|
237
|
+
* 1. Exact match
|
|
238
|
+
* 2. Case-insensitive match
|
|
239
|
+
* 3. Family preference (opus→1m, codex→highest version)
|
|
240
|
+
* 4. Normalized match (dots→dashes, letter-digit boundaries)
|
|
241
|
+
* 5. Return as-is with a warning
|
|
227
242
|
*/
|
|
228
243
|
function resolveModel(modelId) {
|
|
229
244
|
const models = state.models?.data;
|
|
230
245
|
if (!models) return modelId;
|
|
231
246
|
if (models.some((m) => m.id === modelId)) return modelId;
|
|
232
|
-
|
|
247
|
+
const lower = modelId.toLowerCase();
|
|
248
|
+
const ciMatch = models.find((m) => m.id.toLowerCase() === lower);
|
|
249
|
+
if (ciMatch) return ciMatch.id;
|
|
250
|
+
if (lower.includes("opus")) {
|
|
233
251
|
const oneM = models.find((m) => m.id.includes("opus") && m.id.endsWith("-1m"));
|
|
234
252
|
if (oneM) return oneM.id;
|
|
235
253
|
}
|
|
254
|
+
if (lower.includes("codex")) {
|
|
255
|
+
const codexModels = models.filter((m) => m.id.includes("codex") && !m.id.includes("mini"));
|
|
256
|
+
if (codexModels.length > 0) {
|
|
257
|
+
codexModels.sort((a, b) => b.id.localeCompare(a.id));
|
|
258
|
+
return codexModels[0].id;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const normalized = normalizeModelId(modelId);
|
|
262
|
+
const normMatch = models.find((m) => normalizeModelId(m.id) === normalized);
|
|
263
|
+
if (normMatch) return normMatch.id;
|
|
264
|
+
consola.warn(`Model "${modelId}" not found in Copilot model list. Available: ${models.map((m) => m.id).join(", ")}`);
|
|
236
265
|
return modelId;
|
|
237
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Resolve a codex model ID, falling back to the best available codex model.
|
|
269
|
+
* Used by the codex subcommand for model selection.
|
|
270
|
+
*/
|
|
271
|
+
function resolveCodexModel(modelId) {
|
|
272
|
+
const resolved = resolveModel(modelId);
|
|
273
|
+
const models = state.models?.data;
|
|
274
|
+
if (!models) return resolved;
|
|
275
|
+
if (models.some((m) => m.id === resolved)) return resolved;
|
|
276
|
+
const codexModels = models.filter((m) => {
|
|
277
|
+
const endpoints = m.supported_endpoints ?? [];
|
|
278
|
+
return m.id.includes("codex") && !m.id.includes("mini") && (endpoints.length === 0 || endpoints.includes("/responses"));
|
|
279
|
+
});
|
|
280
|
+
if (codexModels.length > 0) {
|
|
281
|
+
codexModels.sort((a, b) => b.id.localeCompare(a.id));
|
|
282
|
+
const best = codexModels[0].id;
|
|
283
|
+
consola.warn(`Model "${modelId}" not available, using "${best}" instead`);
|
|
284
|
+
return best;
|
|
285
|
+
}
|
|
286
|
+
return resolved;
|
|
287
|
+
}
|
|
238
288
|
async function cacheModels() {
|
|
239
289
|
state.models = await getModels();
|
|
240
290
|
}
|
|
@@ -415,7 +465,7 @@ const checkUsage = defineCommand({
|
|
|
415
465
|
//#endregion
|
|
416
466
|
//#region src/lib/port.ts
|
|
417
467
|
const DEFAULT_PORT = 8787;
|
|
418
|
-
const DEFAULT_CODEX_MODEL = "
|
|
468
|
+
const DEFAULT_CODEX_MODEL = "gpt-5.3-codex";
|
|
419
469
|
const PORT_RANGE_MIN = 11e3;
|
|
420
470
|
const PORT_RANGE_MAX = 65535;
|
|
421
471
|
/** Generate a random port number in the range [11000, 65535]. */
|
|
@@ -425,6 +475,14 @@ function generateRandomPort() {
|
|
|
425
475
|
|
|
426
476
|
//#endregion
|
|
427
477
|
//#region src/lib/launch.ts
|
|
478
|
+
function commandExists(name) {
|
|
479
|
+
try {
|
|
480
|
+
execFileSync(process$1.platform === "win32" ? "where.exe" : "which", [name], { stdio: "ignore" });
|
|
481
|
+
return true;
|
|
482
|
+
} catch {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
428
486
|
function buildLaunchCommand(target) {
|
|
429
487
|
return {
|
|
430
488
|
cmd: target.kind === "claude-code" ? [
|
|
@@ -433,6 +491,7 @@ function buildLaunchCommand(target) {
|
|
|
433
491
|
...target.extraArgs
|
|
434
492
|
] : [
|
|
435
493
|
"codex",
|
|
494
|
+
"--full-auto",
|
|
436
495
|
"-m",
|
|
437
496
|
target.model ?? DEFAULT_CODEX_MODEL,
|
|
438
497
|
...target.extraArgs
|
|
@@ -446,18 +505,20 @@ function buildLaunchCommand(target) {
|
|
|
446
505
|
function launchChild(target, server$1) {
|
|
447
506
|
const { cmd, env } = buildLaunchCommand(target);
|
|
448
507
|
const executable = cmd[0];
|
|
449
|
-
if (!
|
|
508
|
+
if (!commandExists(executable)) {
|
|
450
509
|
consola.error(`"${executable}" not found on PATH. Install it first, then try again.`);
|
|
451
510
|
process$1.exit(1);
|
|
452
511
|
}
|
|
453
512
|
let child;
|
|
454
513
|
try {
|
|
455
|
-
child =
|
|
456
|
-
|
|
514
|
+
if (process$1.platform === "win32") child = spawn(cmd.map((a) => a.includes(" ") ? `"${a}"` : a).join(" "), [], {
|
|
515
|
+
env,
|
|
516
|
+
stdio: "inherit",
|
|
517
|
+
shell: true
|
|
518
|
+
});
|
|
519
|
+
else child = spawn(cmd[0], cmd.slice(1), {
|
|
457
520
|
env,
|
|
458
|
-
|
|
459
|
-
stdout: "inherit",
|
|
460
|
-
stderr: "inherit"
|
|
521
|
+
stdio: "inherit"
|
|
461
522
|
});
|
|
462
523
|
} catch (error) {
|
|
463
524
|
consola.error(`Failed to launch ${executable}:`, error instanceof Error ? error.message : String(error));
|
|
@@ -488,10 +549,58 @@ function launchChild(target, server$1) {
|
|
|
488
549
|
};
|
|
489
550
|
process$1.on("SIGINT", onSignal);
|
|
490
551
|
process$1.on("SIGTERM", onSignal);
|
|
491
|
-
child.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
552
|
+
child.on("exit", (exitCode) => {
|
|
553
|
+
cleanup().then(() => exit(exitCode ?? 0)).catch(() => exit(1));
|
|
554
|
+
});
|
|
555
|
+
child.on("error", () => exit(1));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/lib/model-validation.ts
|
|
560
|
+
const ENDPOINT_ALIASES = {
|
|
561
|
+
"/chat/completions": "/chat/completions",
|
|
562
|
+
"/v1/chat/completions": "/chat/completions",
|
|
563
|
+
"/responses": "/responses",
|
|
564
|
+
"/v1/responses": "/responses",
|
|
565
|
+
"/v1/messages": "/v1/messages"
|
|
566
|
+
};
|
|
567
|
+
/**
|
|
568
|
+
* Check whether a model supports the given endpoint, based on cached
|
|
569
|
+
* `supported_endpoints` metadata from the Copilot `/models` response.
|
|
570
|
+
*
|
|
571
|
+
* Returns `true` (allow) when:
|
|
572
|
+
* - the model is not found in the cache (don't block unknown models)
|
|
573
|
+
* - the model has no `supported_endpoints` field (backward-compat)
|
|
574
|
+
* - the endpoint is listed in `supported_endpoints`
|
|
575
|
+
*/
|
|
576
|
+
function modelSupportsEndpoint(modelId, path$1) {
|
|
577
|
+
const endpoint = ENDPOINT_ALIASES[path$1] ?? path$1;
|
|
578
|
+
const model = state.models?.data.find((m) => m.id === modelId);
|
|
579
|
+
if (!model) return true;
|
|
580
|
+
const supported = model.supported_endpoints;
|
|
581
|
+
if (!supported || supported.length === 0) return true;
|
|
582
|
+
return supported.includes(endpoint);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Log an error when a model is used on an endpoint it doesn't support.
|
|
586
|
+
* Returns `true` if a mismatch was detected (for testing).
|
|
587
|
+
*/
|
|
588
|
+
function logEndpointMismatch(modelId, path$1) {
|
|
589
|
+
if (modelSupportsEndpoint(modelId, path$1)) return false;
|
|
590
|
+
const supported = (state.models?.data.find((m) => m.id === modelId))?.supported_endpoints ?? [];
|
|
591
|
+
consola.error(`Model "${modelId}" does not support ${path$1}. Supported endpoints: ${supported.join(", ")}`);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Return model IDs that support the given endpoint.
|
|
596
|
+
*/
|
|
597
|
+
function listModelsForEndpoint(path$1) {
|
|
598
|
+
const endpoint = ENDPOINT_ALIASES[path$1] ?? path$1;
|
|
599
|
+
return (state.models?.data ?? []).filter((m) => {
|
|
600
|
+
const supported = m.supported_endpoints;
|
|
601
|
+
if (!supported || supported.length === 0) return true;
|
|
602
|
+
return supported.includes(endpoint);
|
|
603
|
+
}).map((m) => m.id);
|
|
495
604
|
}
|
|
496
605
|
|
|
497
606
|
//#endregion
|
|
@@ -588,7 +697,7 @@ function formatTokens(n) {
|
|
|
588
697
|
function formatTokenInfo(inputTokens, outputTokens, model) {
|
|
589
698
|
if (inputTokens === void 0) return void 0;
|
|
590
699
|
const parts = [];
|
|
591
|
-
const maxPrompt = model?.capabilities
|
|
700
|
+
const maxPrompt = model?.capabilities?.limits?.max_prompt_tokens;
|
|
592
701
|
if (maxPrompt) {
|
|
593
702
|
const pct = (inputTokens / maxPrompt * 100).toFixed(1);
|
|
594
703
|
parts.push(`in:${formatTokens(inputTokens)}/${formatTokens(maxPrompt)} (${pct}%)`);
|
|
@@ -708,7 +817,7 @@ const getEncodeChatFunction = async (encoding) => {
|
|
|
708
817
|
* Get tokenizer type from model information
|
|
709
818
|
*/
|
|
710
819
|
const getTokenizerFromModel = (model) => {
|
|
711
|
-
return model.capabilities
|
|
820
|
+
return model.capabilities?.tokenizer || "o200k_base";
|
|
712
821
|
};
|
|
713
822
|
/**
|
|
714
823
|
* Get model-specific constants for token calculation
|
|
@@ -942,6 +1051,7 @@ async function handleCompletion$1(c) {
|
|
|
942
1051
|
const resolvedModel = resolveModel(payload.model);
|
|
943
1052
|
if (resolvedModel !== payload.model) payload.model = resolvedModel;
|
|
944
1053
|
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
1054
|
+
logEndpointMismatch(payload.model, "/chat/completions");
|
|
945
1055
|
let inputTokens;
|
|
946
1056
|
try {
|
|
947
1057
|
if (selectedModel) inputTokens = (await getTokenCount(payload, selectedModel)).input;
|
|
@@ -949,7 +1059,7 @@ async function handleCompletion$1(c) {
|
|
|
949
1059
|
if (isNullish(payload.max_tokens)) {
|
|
950
1060
|
payload = {
|
|
951
1061
|
...payload,
|
|
952
|
-
max_tokens: selectedModel?.capabilities
|
|
1062
|
+
max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens
|
|
953
1063
|
};
|
|
954
1064
|
if (debugEnabled) consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
955
1065
|
}
|
|
@@ -1360,7 +1470,9 @@ async function handleCompletion(c) {
|
|
|
1360
1470
|
if (state.manualApprove) await awaitApproval();
|
|
1361
1471
|
const betaHeaders = extractBetaHeaders(c);
|
|
1362
1472
|
const { body: resolvedBody, originalModel, resolvedModel } = resolveModelInBody(await processWebSearch(rawBody));
|
|
1363
|
-
const
|
|
1473
|
+
const modelId = resolvedModel ?? originalModel;
|
|
1474
|
+
const selectedModel = state.models?.data.find((m) => m.id === modelId);
|
|
1475
|
+
if (modelId) logEndpointMismatch(modelId, "/v1/messages");
|
|
1364
1476
|
const effectiveBetas = applyDefaultBetas(betaHeaders, resolvedModel ?? originalModel);
|
|
1365
1477
|
let response;
|
|
1366
1478
|
try {
|
|
@@ -1484,11 +1596,17 @@ modelRoutes.get("/", async (c) => {
|
|
|
1484
1596
|
const models = state.models?.data.map((model) => ({
|
|
1485
1597
|
id: model.id,
|
|
1486
1598
|
object: "model",
|
|
1487
|
-
type: "model",
|
|
1599
|
+
type: model.capabilities?.type ?? "model",
|
|
1488
1600
|
created: 0,
|
|
1489
1601
|
created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1490
1602
|
owned_by: model.vendor,
|
|
1491
|
-
display_name: model.name
|
|
1603
|
+
display_name: model.name,
|
|
1604
|
+
capabilities: model.capabilities,
|
|
1605
|
+
supported_endpoints: model.supported_endpoints,
|
|
1606
|
+
preview: model.preview,
|
|
1607
|
+
version: model.version,
|
|
1608
|
+
model_picker_enabled: model.model_picker_enabled,
|
|
1609
|
+
policy: model.policy
|
|
1492
1610
|
}));
|
|
1493
1611
|
return c.json({
|
|
1494
1612
|
object: "list",
|
|
@@ -1579,12 +1697,9 @@ async function handleResponses(c) {
|
|
|
1579
1697
|
const resolvedModel = resolveModel(payload.model);
|
|
1580
1698
|
if (resolvedModel !== payload.model) payload.model = resolvedModel;
|
|
1581
1699
|
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
1700
|
+
logEndpointMismatch(payload.model, "/responses");
|
|
1582
1701
|
if (state.manualApprove) await awaitApproval();
|
|
1583
1702
|
await injectWebSearchIfNeeded(payload);
|
|
1584
|
-
if (isNullish(payload.max_output_tokens)) {
|
|
1585
|
-
payload.max_output_tokens = selectedModel?.capabilities.limits.max_output_tokens;
|
|
1586
|
-
if (debugEnabled) consola.debug("Set max_output_tokens to:", JSON.stringify(payload.max_output_tokens));
|
|
1587
|
-
}
|
|
1588
1703
|
const response = await createResponses(payload, selectedModel?.requestHeaders).catch(async (error) => {
|
|
1589
1704
|
if (error instanceof HTTPError) {
|
|
1590
1705
|
const errorBody = await error.response.clone().text().catch(() => "");
|
|
@@ -1661,6 +1776,32 @@ function extractUserQuery(input) {
|
|
|
1661
1776
|
}
|
|
1662
1777
|
}
|
|
1663
1778
|
}
|
|
1779
|
+
async function handleResponsesCompact(c) {
|
|
1780
|
+
const startTime = Date.now();
|
|
1781
|
+
await checkRateLimit(state);
|
|
1782
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
1783
|
+
if (state.manualApprove) await awaitApproval();
|
|
1784
|
+
const body = await c.req.text();
|
|
1785
|
+
const response = await fetch(`${copilotBaseUrl(state)}/responses/compact`, {
|
|
1786
|
+
method: "POST",
|
|
1787
|
+
headers: copilotHeaders(state),
|
|
1788
|
+
body
|
|
1789
|
+
});
|
|
1790
|
+
if (!response.ok) {
|
|
1791
|
+
logRequest({
|
|
1792
|
+
method: "POST",
|
|
1793
|
+
path: c.req.path,
|
|
1794
|
+
status: response.status
|
|
1795
|
+
}, void 0, startTime);
|
|
1796
|
+
throw new HTTPError("Copilot responses/compact request failed", response);
|
|
1797
|
+
}
|
|
1798
|
+
logRequest({
|
|
1799
|
+
method: "POST",
|
|
1800
|
+
path: c.req.path,
|
|
1801
|
+
status: 200
|
|
1802
|
+
}, void 0, startTime);
|
|
1803
|
+
return c.json(await response.json());
|
|
1804
|
+
}
|
|
1664
1805
|
|
|
1665
1806
|
//#endregion
|
|
1666
1807
|
//#region src/routes/responses/route.ts
|
|
@@ -1672,6 +1813,13 @@ responsesRoutes.post("/", async (c) => {
|
|
|
1672
1813
|
return await forwardError(c, error);
|
|
1673
1814
|
}
|
|
1674
1815
|
});
|
|
1816
|
+
responsesRoutes.post("/compact", async (c) => {
|
|
1817
|
+
try {
|
|
1818
|
+
return await handleResponsesCompact(c);
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
return await forwardError(c, error);
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1675
1823
|
|
|
1676
1824
|
//#endregion
|
|
1677
1825
|
//#region src/routes/search/route.ts
|
|
@@ -1745,6 +1893,7 @@ async function setupAndServe(options) {
|
|
|
1745
1893
|
state.rateLimitSeconds = options.rateLimit;
|
|
1746
1894
|
state.rateLimitWait = options.rateLimitWait;
|
|
1747
1895
|
state.showToken = options.showToken;
|
|
1896
|
+
if (process.env.COPILOT_API_URL) state.copilotApiUrl = process.env.COPILOT_API_URL;
|
|
1748
1897
|
await ensurePaths();
|
|
1749
1898
|
await cacheVSCodeVersion();
|
|
1750
1899
|
if (options.githubToken) {
|
|
@@ -1753,7 +1902,7 @@ async function setupAndServe(options) {
|
|
|
1753
1902
|
} else await setupGitHubToken();
|
|
1754
1903
|
await setupCopilotToken();
|
|
1755
1904
|
await cacheModels();
|
|
1756
|
-
consola.
|
|
1905
|
+
consola.debug(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
|
|
1757
1906
|
const serveOptions = {
|
|
1758
1907
|
fetch: server.fetch,
|
|
1759
1908
|
hostname: "127.0.0.1",
|
|
@@ -1932,13 +2081,22 @@ const claude = defineCommand({
|
|
|
1932
2081
|
consola.error("Failed to start server:", error instanceof Error ? error.message : error);
|
|
1933
2082
|
process$1.exit(1);
|
|
1934
2083
|
}
|
|
2084
|
+
let resolvedModel;
|
|
2085
|
+
if (args.model) {
|
|
2086
|
+
resolvedModel = resolveModel(args.model);
|
|
2087
|
+
if (resolvedModel !== args.model) consola.info(`Model "${args.model}" resolved to "${resolvedModel}"`);
|
|
2088
|
+
if (!state.models?.data.find((m) => m.id === resolvedModel)) {
|
|
2089
|
+
const available = listModelsForEndpoint("/v1/messages");
|
|
2090
|
+
consola.warn(`Model "${resolvedModel}" not found. Available claude models: ${available.join(", ")}`);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
1935
2093
|
consola.success(`Server ready on ${serverUrl}, launching Claude Code...`);
|
|
1936
2094
|
consola.level = 1;
|
|
1937
2095
|
launchChild({
|
|
1938
2096
|
kind: "claude-code",
|
|
1939
|
-
envVars: getClaudeCodeEnvVars(serverUrl, args.model),
|
|
2097
|
+
envVars: getClaudeCodeEnvVars(serverUrl, resolvedModel ?? args.model),
|
|
1940
2098
|
extraArgs: args._ ?? [],
|
|
1941
|
-
model: args.model
|
|
2099
|
+
model: resolvedModel ?? args.model
|
|
1942
2100
|
}, server$1);
|
|
1943
2101
|
}
|
|
1944
2102
|
});
|
|
@@ -1978,14 +2136,24 @@ const codex = defineCommand({
|
|
|
1978
2136
|
consola.error("Failed to start server:", error instanceof Error ? error.message : error);
|
|
1979
2137
|
process$1.exit(1);
|
|
1980
2138
|
}
|
|
1981
|
-
const
|
|
2139
|
+
const requestedModel = args.model ?? DEFAULT_CODEX_MODEL;
|
|
2140
|
+
const codexModel = resolveCodexModel(requestedModel);
|
|
2141
|
+
if (codexModel !== requestedModel) consola.info(`Model "${requestedModel}" resolved to "${codexModel}"`);
|
|
2142
|
+
const modelEntry = state.models?.data.find((m) => m.id === codexModel);
|
|
2143
|
+
if (!modelEntry) {
|
|
2144
|
+
const available = listModelsForEndpoint("/responses");
|
|
2145
|
+
consola.warn(`Model "${codexModel}" not found. Available codex models: ${available.join(", ")}`);
|
|
2146
|
+
} else {
|
|
2147
|
+
const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens;
|
|
2148
|
+
if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`);
|
|
2149
|
+
}
|
|
1982
2150
|
consola.success(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...`);
|
|
1983
2151
|
consola.level = 1;
|
|
1984
2152
|
launchChild({
|
|
1985
2153
|
kind: "codex",
|
|
1986
2154
|
envVars: getCodexEnvVars(serverUrl),
|
|
1987
2155
|
extraArgs: args._ ?? [],
|
|
1988
|
-
model:
|
|
2156
|
+
model: codexModel
|
|
1989
2157
|
}, server$1);
|
|
1990
2158
|
}
|
|
1991
2159
|
});
|