@bman654/clodex 0.1.0 → 1.0.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 +52 -8
- package/dist/{chunk-C7N2JI7Z.js → chunk-3XM6UZWP.js} +138 -28
- package/dist/chunk-3XM6UZWP.js.map +1 -0
- package/dist/claude-wrapper.js +10 -4
- package/dist/claude-wrapper.js.map +1 -1
- package/dist/cli.js +265 -193
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +41 -7
- package/docs/model-picker.png +0 -0
- package/package.json +1 -1
- package/dist/chunk-C7N2JI7Z.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
getServerFavoritesOnly,
|
|
12
12
|
getServerListenMode,
|
|
13
13
|
getServerMaskGatewayIds,
|
|
14
|
+
isDiscoveryDisabled,
|
|
14
15
|
launchClaude,
|
|
15
16
|
loadPreferences,
|
|
16
17
|
recordLaunchSelection,
|
|
17
|
-
|
|
18
|
+
registerServerRuntimeState,
|
|
18
19
|
resolveBridgeMode,
|
|
19
20
|
savePreferences,
|
|
20
21
|
setSavedServerPassword,
|
|
@@ -22,8 +23,8 @@ import {
|
|
|
22
23
|
setServerFavoritesOnly,
|
|
23
24
|
setServerListenMode,
|
|
24
25
|
setServerMaskGatewayIds,
|
|
25
|
-
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
unregisterServerRuntimeState
|
|
27
|
+
} from "./chunk-3XM6UZWP.js";
|
|
27
28
|
|
|
28
29
|
// src/cli.ts
|
|
29
30
|
import pc13 from "picocolors";
|
|
@@ -201,7 +202,7 @@ import { join } from "path";
|
|
|
201
202
|
// package.json
|
|
202
203
|
var package_default = {
|
|
203
204
|
name: "@bman654/clodex",
|
|
204
|
-
version: "
|
|
205
|
+
version: "1.0.0",
|
|
205
206
|
publishConfig: {
|
|
206
207
|
access: "public"
|
|
207
208
|
},
|
|
@@ -6259,6 +6260,146 @@ function sendJson(res, status, body) {
|
|
|
6259
6260
|
res.end(json);
|
|
6260
6261
|
}
|
|
6261
6262
|
|
|
6263
|
+
// src/catalog.ts
|
|
6264
|
+
function localModelToRoute(lp, model) {
|
|
6265
|
+
if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
|
|
6266
|
+
if (model.modelFormat === "openai" && !isSdkMigratedNpm(model.npm) && !model.completionsUrl) return null;
|
|
6267
|
+
const upstreamUrl = model.modelFormat === "anthropic" ? model.baseUrl : model.completionsUrl;
|
|
6268
|
+
return {
|
|
6269
|
+
aliasId: claudeCodeClientModelId(aliasModelId(model.id, lp.id), model.contextWindow),
|
|
6270
|
+
realModelId: model.upstreamModelId,
|
|
6271
|
+
displayName: `${model.name || model.id} (${lp.name})`,
|
|
6272
|
+
upstreamUrl: upstreamUrl ?? "",
|
|
6273
|
+
apiKey: lp.apiKey,
|
|
6274
|
+
modelFormat: model.modelFormat,
|
|
6275
|
+
contextWindow: model.contextWindow,
|
|
6276
|
+
npm: model.npm,
|
|
6277
|
+
baseURL: model.apiBaseUrl,
|
|
6278
|
+
providerId: lp.id,
|
|
6279
|
+
authType: lp.authType,
|
|
6280
|
+
oauthAccountId: lp.oauthAccountId,
|
|
6281
|
+
providerData: lp.providerData,
|
|
6282
|
+
headers: lp.headers,
|
|
6283
|
+
supportedParameters: model.supportedParameters,
|
|
6284
|
+
reasoning: model.reasoning,
|
|
6285
|
+
interleavedReasoningField: model.interleavedReasoningField,
|
|
6286
|
+
useResponsesLite: model.useResponsesLite,
|
|
6287
|
+
preferWebSockets: model.preferWebSockets
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
function makeRouteResolver(localProviders) {
|
|
6291
|
+
return (providerId, modelId) => {
|
|
6292
|
+
const provider = localProviders?.find((lp) => lp.id === providerId);
|
|
6293
|
+
const model = provider?.models.find((m) => m.id === modelId);
|
|
6294
|
+
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6297
|
+
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
6298
|
+
const droppedFavorites = [];
|
|
6299
|
+
const tail = favorites.map((fav) => {
|
|
6300
|
+
const route = resolveRoute(fav.providerId, fav.modelId);
|
|
6301
|
+
if (!route) droppedFavorites.push(fav);
|
|
6302
|
+
return route;
|
|
6303
|
+
}).filter((route) => route !== void 0);
|
|
6304
|
+
const routes = [
|
|
6305
|
+
startingRoute,
|
|
6306
|
+
...tail.filter((route) => route.aliasId !== startingRoute.aliasId)
|
|
6307
|
+
].slice(0, max);
|
|
6308
|
+
return { routes, droppedFavorites };
|
|
6309
|
+
}
|
|
6310
|
+
|
|
6311
|
+
// src/model-aliases.ts
|
|
6312
|
+
var MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
6313
|
+
function isValidModelAlias(name) {
|
|
6314
|
+
return MODEL_ALIAS_PATTERN.test(name);
|
|
6315
|
+
}
|
|
6316
|
+
function parseModelAliasAssignment(value) {
|
|
6317
|
+
const separator = value.indexOf("=");
|
|
6318
|
+
if (separator < 1 || separator === value.length - 1) {
|
|
6319
|
+
return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
|
|
6320
|
+
}
|
|
6321
|
+
const name = value.slice(0, separator).trim();
|
|
6322
|
+
if (!isValidModelAlias(name)) {
|
|
6323
|
+
return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
|
|
6324
|
+
}
|
|
6325
|
+
const rawTarget = value.slice(separator + 1).trim();
|
|
6326
|
+
const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
|
|
6327
|
+
const targetSeparator = target.indexOf(":");
|
|
6328
|
+
if (targetSeparator < 1 || targetSeparator === target.length - 1) {
|
|
6329
|
+
return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
|
|
6330
|
+
}
|
|
6331
|
+
return {
|
|
6332
|
+
name,
|
|
6333
|
+
providerId: target.slice(0, targetSeparator),
|
|
6334
|
+
// `models --list` prints Claude's synthetic context suffix. It is a client
|
|
6335
|
+
// routing hint, not part of the provider catalog id stored in favorites.
|
|
6336
|
+
modelId: stripOneMContextSuffix(target.slice(targetSeparator + 1))
|
|
6337
|
+
};
|
|
6338
|
+
}
|
|
6339
|
+
function modelAliasTarget(alias) {
|
|
6340
|
+
return `clodex:${alias.providerId}:${alias.modelId}`;
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
// src/http-proxy/routes.ts
|
|
6344
|
+
var HTTP_PROXY_MODEL_PREFIX = "clodex:";
|
|
6345
|
+
function httpProxyModelId(providerId, modelId) {
|
|
6346
|
+
return `${HTTP_PROXY_MODEL_PREFIX}${providerId}:${modelId}`;
|
|
6347
|
+
}
|
|
6348
|
+
function httpProxyDisplayName(model, providerName) {
|
|
6349
|
+
return `${formatModelLabel(model)} (${providerName})`;
|
|
6350
|
+
}
|
|
6351
|
+
function buildHttpProxyRoutes(providers, favorites, modelAliases = [], max = MAX_MODEL_CATALOG) {
|
|
6352
|
+
const routes = [];
|
|
6353
|
+
const unavailable = [];
|
|
6354
|
+
const unsupported = [];
|
|
6355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6356
|
+
const routesByFavorite = /* @__PURE__ */ new Map();
|
|
6357
|
+
for (const favorite of favorites) {
|
|
6358
|
+
if (routes.length >= max) break;
|
|
6359
|
+
const provider = providers.find((item) => item.id === favorite.providerId);
|
|
6360
|
+
const model = provider?.models.find((item) => item.id === favorite.modelId);
|
|
6361
|
+
if (!provider || !model) {
|
|
6362
|
+
unavailable.push(favorite);
|
|
6363
|
+
continue;
|
|
6364
|
+
}
|
|
6365
|
+
if (model.modelFormat !== "openai" || !isSdkMigratedNpm(model.npm)) {
|
|
6366
|
+
unsupported.push(favorite);
|
|
6367
|
+
continue;
|
|
6368
|
+
}
|
|
6369
|
+
const route = localModelToRoute(provider, model);
|
|
6370
|
+
if (!route || !route.apiKey.trim()) {
|
|
6371
|
+
unavailable.push(favorite);
|
|
6372
|
+
continue;
|
|
6373
|
+
}
|
|
6374
|
+
const aliasId = claudeCodeClientModelId(
|
|
6375
|
+
httpProxyModelId(provider.id, model.id),
|
|
6376
|
+
model.contextWindow
|
|
6377
|
+
);
|
|
6378
|
+
if (seen.has(aliasId)) continue;
|
|
6379
|
+
seen.add(aliasId);
|
|
6380
|
+
const proxyRoute = {
|
|
6381
|
+
...route,
|
|
6382
|
+
aliasId,
|
|
6383
|
+
displayName: httpProxyDisplayName(model, provider.name)
|
|
6384
|
+
};
|
|
6385
|
+
routes.push(proxyRoute);
|
|
6386
|
+
routesByFavorite.set(`${favorite.providerId}:${favorite.modelId}`, proxyRoute);
|
|
6387
|
+
}
|
|
6388
|
+
const aliases = [];
|
|
6389
|
+
const unavailableAliases = [];
|
|
6390
|
+
const seenAliases = /* @__PURE__ */ new Set();
|
|
6391
|
+
for (const alias of modelAliases) {
|
|
6392
|
+
const route = routesByFavorite.get(`${alias.providerId}:${alias.modelId}`);
|
|
6393
|
+
if (!isValidModelAlias(alias.name) || seenAliases.has(alias.name) || !route) {
|
|
6394
|
+
unavailableAliases.push(alias);
|
|
6395
|
+
continue;
|
|
6396
|
+
}
|
|
6397
|
+
seenAliases.add(alias.name);
|
|
6398
|
+
aliases.push({ name: alias.name, routeId: route.aliasId, displayName: route.displayName });
|
|
6399
|
+
}
|
|
6400
|
+
return { routes, unavailable, unsupported, aliases, unavailableAliases };
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6262
6403
|
// src/server/vendor-mask.ts
|
|
6263
6404
|
function reverseSegment(value) {
|
|
6264
6405
|
return [...value].reverse().join("");
|
|
@@ -6320,7 +6461,7 @@ function formatGatewayAnthropicModels(models, opts) {
|
|
|
6320
6461
|
}))
|
|
6321
6462
|
);
|
|
6322
6463
|
}
|
|
6323
|
-
function createGatewayModelCatalog(models, opts) {
|
|
6464
|
+
function createGatewayModelCatalog(models, opts, modelAliases) {
|
|
6324
6465
|
const byId = /* @__PURE__ */ new Map();
|
|
6325
6466
|
for (const model of models) {
|
|
6326
6467
|
byId.set(model.id, model);
|
|
@@ -6331,6 +6472,17 @@ function createGatewayModelCatalog(models, opts) {
|
|
|
6331
6472
|
if (rawAlias !== alias) byId.set(rawAlias, model);
|
|
6332
6473
|
}
|
|
6333
6474
|
}
|
|
6475
|
+
for (const model of models) {
|
|
6476
|
+
const canonicalId = httpProxyModelId(gatewayProviderId(model), model.id);
|
|
6477
|
+
if (!byId.has(canonicalId)) byId.set(canonicalId, model);
|
|
6478
|
+
}
|
|
6479
|
+
for (const alias of modelAliases ?? []) {
|
|
6480
|
+
if (byId.has(alias.name)) continue;
|
|
6481
|
+
const target = models.find(
|
|
6482
|
+
(model) => gatewayProviderId(model) === alias.providerId && model.id === alias.modelId
|
|
6483
|
+
);
|
|
6484
|
+
if (target) byId.set(alias.name, target);
|
|
6485
|
+
}
|
|
6334
6486
|
return {
|
|
6335
6487
|
get: (id) => byId.get(id),
|
|
6336
6488
|
list: () => [...models]
|
|
@@ -7898,54 +8050,6 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
7898
8050
|
}], clientModelId, debug);
|
|
7899
8051
|
}
|
|
7900
8052
|
|
|
7901
|
-
// src/catalog.ts
|
|
7902
|
-
function localModelToRoute(lp, model) {
|
|
7903
|
-
if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
|
|
7904
|
-
if (model.modelFormat === "openai" && !isSdkMigratedNpm(model.npm) && !model.completionsUrl) return null;
|
|
7905
|
-
const upstreamUrl = model.modelFormat === "anthropic" ? model.baseUrl : model.completionsUrl;
|
|
7906
|
-
return {
|
|
7907
|
-
aliasId: claudeCodeClientModelId(aliasModelId(model.id, lp.id), model.contextWindow),
|
|
7908
|
-
realModelId: model.upstreamModelId,
|
|
7909
|
-
displayName: `${model.name || model.id} (${lp.name})`,
|
|
7910
|
-
upstreamUrl: upstreamUrl ?? "",
|
|
7911
|
-
apiKey: lp.apiKey,
|
|
7912
|
-
modelFormat: model.modelFormat,
|
|
7913
|
-
contextWindow: model.contextWindow,
|
|
7914
|
-
npm: model.npm,
|
|
7915
|
-
baseURL: model.apiBaseUrl,
|
|
7916
|
-
providerId: lp.id,
|
|
7917
|
-
authType: lp.authType,
|
|
7918
|
-
oauthAccountId: lp.oauthAccountId,
|
|
7919
|
-
providerData: lp.providerData,
|
|
7920
|
-
headers: lp.headers,
|
|
7921
|
-
supportedParameters: model.supportedParameters,
|
|
7922
|
-
reasoning: model.reasoning,
|
|
7923
|
-
interleavedReasoningField: model.interleavedReasoningField,
|
|
7924
|
-
useResponsesLite: model.useResponsesLite,
|
|
7925
|
-
preferWebSockets: model.preferWebSockets
|
|
7926
|
-
};
|
|
7927
|
-
}
|
|
7928
|
-
function makeRouteResolver(localProviders) {
|
|
7929
|
-
return (providerId, modelId) => {
|
|
7930
|
-
const provider = localProviders?.find((lp) => lp.id === providerId);
|
|
7931
|
-
const model = provider?.models.find((m) => m.id === modelId);
|
|
7932
|
-
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
7933
|
-
};
|
|
7934
|
-
}
|
|
7935
|
-
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
7936
|
-
const droppedFavorites = [];
|
|
7937
|
-
const tail = favorites.map((fav) => {
|
|
7938
|
-
const route = resolveRoute(fav.providerId, fav.modelId);
|
|
7939
|
-
if (!route) droppedFavorites.push(fav);
|
|
7940
|
-
return route;
|
|
7941
|
-
}).filter((route) => route !== void 0);
|
|
7942
|
-
const routes = [
|
|
7943
|
-
startingRoute,
|
|
7944
|
-
...tail.filter((route) => route.aliasId !== startingRoute.aliasId)
|
|
7945
|
-
].slice(0, max);
|
|
7946
|
-
return { routes, droppedFavorites };
|
|
7947
|
-
}
|
|
7948
|
-
|
|
7949
8053
|
// src/server/index.ts
|
|
7950
8054
|
import pc10 from "picocolors";
|
|
7951
8055
|
import { networkInterfaces } from "os";
|
|
@@ -8587,6 +8691,7 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
8587
8691
|
}
|
|
8588
8692
|
const completionsUrl = model.completionsUrl;
|
|
8589
8693
|
const apiKey2 = model.apiKey ?? options.apiKey;
|
|
8694
|
+
const forwardBody = body.model === upstreamModelId(model) ? body : { ...body, model: upstreamModelId(model) };
|
|
8590
8695
|
auditInference(options, {
|
|
8591
8696
|
modelId: body.model,
|
|
8592
8697
|
effort: openAiEffort(body),
|
|
@@ -8594,7 +8699,7 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
8594
8699
|
route: "passthrough",
|
|
8595
8700
|
requestPreview: getLatestMessagePreview(body.messages, body.system)
|
|
8596
8701
|
});
|
|
8597
|
-
await relayAnthropicMessages(res, completionsUrl,
|
|
8702
|
+
await relayAnthropicMessages(res, completionsUrl, forwardBody, apiKey2, Boolean(body.stream), {
|
|
8598
8703
|
onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
8599
8704
|
modelId: body.model,
|
|
8600
8705
|
provider: inferenceProvider(model),
|
|
@@ -8698,6 +8803,7 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, w
|
|
|
8698
8803
|
return languageModel;
|
|
8699
8804
|
}
|
|
8700
8805
|
function getResponseModelId(bodyModel, model, options) {
|
|
8806
|
+
if (typeof bodyModel === "string" && options.aliasNames?.has(bodyModel)) return bodyModel;
|
|
8701
8807
|
return options.gateway?.maskGatewayIds ? gatewayDisplayName(model, options.gateway) : typeof bodyModel === "string" ? bodyModel : model.id;
|
|
8702
8808
|
}
|
|
8703
8809
|
async function readJson(req) {
|
|
@@ -8833,95 +8939,6 @@ async function selectServerProviders(available, initial) {
|
|
|
8833
8939
|
import pc9 from "picocolors";
|
|
8834
8940
|
import * as p8 from "@clack/prompts";
|
|
8835
8941
|
|
|
8836
|
-
// src/model-aliases.ts
|
|
8837
|
-
var MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
8838
|
-
function isValidModelAlias(name) {
|
|
8839
|
-
return MODEL_ALIAS_PATTERN.test(name);
|
|
8840
|
-
}
|
|
8841
|
-
function parseModelAliasAssignment(value) {
|
|
8842
|
-
const separator = value.indexOf("=");
|
|
8843
|
-
if (separator < 1 || separator === value.length - 1) {
|
|
8844
|
-
return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
|
|
8845
|
-
}
|
|
8846
|
-
const name = value.slice(0, separator).trim();
|
|
8847
|
-
if (!isValidModelAlias(name)) {
|
|
8848
|
-
return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
|
|
8849
|
-
}
|
|
8850
|
-
const rawTarget = value.slice(separator + 1).trim();
|
|
8851
|
-
const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
|
|
8852
|
-
const targetSeparator = target.indexOf(":");
|
|
8853
|
-
if (targetSeparator < 1 || targetSeparator === target.length - 1) {
|
|
8854
|
-
return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
|
|
8855
|
-
}
|
|
8856
|
-
return {
|
|
8857
|
-
name,
|
|
8858
|
-
providerId: target.slice(0, targetSeparator),
|
|
8859
|
-
// `models --list` prints Claude's synthetic context suffix. It is a client
|
|
8860
|
-
// routing hint, not part of the provider catalog id stored in favorites.
|
|
8861
|
-
modelId: stripOneMContextSuffix(target.slice(targetSeparator + 1))
|
|
8862
|
-
};
|
|
8863
|
-
}
|
|
8864
|
-
function modelAliasTarget(alias) {
|
|
8865
|
-
return `clodex:${alias.providerId}:${alias.modelId}`;
|
|
8866
|
-
}
|
|
8867
|
-
|
|
8868
|
-
// src/http-proxy/routes.ts
|
|
8869
|
-
var HTTP_PROXY_MODEL_PREFIX = "clodex:";
|
|
8870
|
-
function httpProxyModelId(providerId, modelId) {
|
|
8871
|
-
return `${HTTP_PROXY_MODEL_PREFIX}${providerId}:${modelId}`;
|
|
8872
|
-
}
|
|
8873
|
-
function buildHttpProxyRoutes(providers, favorites, modelAliases = [], max = MAX_MODEL_CATALOG) {
|
|
8874
|
-
const routes = [];
|
|
8875
|
-
const unavailable = [];
|
|
8876
|
-
const unsupported = [];
|
|
8877
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8878
|
-
const routesByFavorite = /* @__PURE__ */ new Map();
|
|
8879
|
-
for (const favorite of favorites) {
|
|
8880
|
-
if (routes.length >= max) break;
|
|
8881
|
-
const provider = providers.find((item) => item.id === favorite.providerId);
|
|
8882
|
-
const model = provider?.models.find((item) => item.id === favorite.modelId);
|
|
8883
|
-
if (!provider || !model) {
|
|
8884
|
-
unavailable.push(favorite);
|
|
8885
|
-
continue;
|
|
8886
|
-
}
|
|
8887
|
-
if (model.modelFormat !== "openai" || !isSdkMigratedNpm(model.npm)) {
|
|
8888
|
-
unsupported.push(favorite);
|
|
8889
|
-
continue;
|
|
8890
|
-
}
|
|
8891
|
-
const route = localModelToRoute(provider, model);
|
|
8892
|
-
if (!route || !route.apiKey.trim()) {
|
|
8893
|
-
unavailable.push(favorite);
|
|
8894
|
-
continue;
|
|
8895
|
-
}
|
|
8896
|
-
const aliasId = claudeCodeClientModelId(
|
|
8897
|
-
httpProxyModelId(provider.id, model.id),
|
|
8898
|
-
model.contextWindow
|
|
8899
|
-
);
|
|
8900
|
-
if (seen.has(aliasId)) continue;
|
|
8901
|
-
seen.add(aliasId);
|
|
8902
|
-
const proxyRoute = {
|
|
8903
|
-
...route,
|
|
8904
|
-
aliasId,
|
|
8905
|
-
displayName: `${model.name || model.id} (${provider.name})`
|
|
8906
|
-
};
|
|
8907
|
-
routes.push(proxyRoute);
|
|
8908
|
-
routesByFavorite.set(`${favorite.providerId}:${favorite.modelId}`, proxyRoute);
|
|
8909
|
-
}
|
|
8910
|
-
const aliases = [];
|
|
8911
|
-
const unavailableAliases = [];
|
|
8912
|
-
const seenAliases = /* @__PURE__ */ new Set();
|
|
8913
|
-
for (const alias of modelAliases) {
|
|
8914
|
-
const route = routesByFavorite.get(`${alias.providerId}:${alias.modelId}`);
|
|
8915
|
-
if (!isValidModelAlias(alias.name) || seenAliases.has(alias.name) || !route) {
|
|
8916
|
-
unavailableAliases.push(alias);
|
|
8917
|
-
continue;
|
|
8918
|
-
}
|
|
8919
|
-
seenAliases.add(alias.name);
|
|
8920
|
-
aliases.push({ name: alias.name, routeId: route.aliasId, displayName: route.displayName });
|
|
8921
|
-
}
|
|
8922
|
-
return { routes, unavailable, unsupported, aliases, unavailableAliases };
|
|
8923
|
-
}
|
|
8924
|
-
|
|
8925
8942
|
// src/http-proxy/server.ts
|
|
8926
8943
|
import * as http from "http";
|
|
8927
8944
|
import * as https from "https";
|
|
@@ -9842,7 +9859,7 @@ function waitForShutdown() {
|
|
|
9842
9859
|
process.once("SIGTERM", done);
|
|
9843
9860
|
});
|
|
9844
9861
|
}
|
|
9845
|
-
async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = false, port) {
|
|
9862
|
+
async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = false, port, noDiscovery = false) {
|
|
9846
9863
|
const webSocketDiagnosticsLogPath = webSocketDiagnostics ? getSessionLogPath("server-websocket-diagnostics", "jsonl") : void 0;
|
|
9847
9864
|
let started;
|
|
9848
9865
|
try {
|
|
@@ -9875,15 +9892,17 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
|
|
|
9875
9892
|
console.log(pc9.dim("Anthropic requests keep Claude Code auth and pass through unchanged."));
|
|
9876
9893
|
console.log(pc9.dim("Use `/model <listed-name>` for a favorite or saved alias."));
|
|
9877
9894
|
console.log(pc9.dim("Press Ctrl+C to stop."));
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9895
|
+
if (!noDiscovery) {
|
|
9896
|
+
registerServerRuntimeState({
|
|
9897
|
+
mode: "proxy",
|
|
9898
|
+
port: handle.port,
|
|
9899
|
+
pid: process.pid,
|
|
9900
|
+
caPath: handle.caCertPath,
|
|
9901
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9902
|
+
});
|
|
9903
|
+
}
|
|
9885
9904
|
await waitForShutdown();
|
|
9886
|
-
|
|
9905
|
+
if (!noDiscovery) unregisterServerRuntimeState();
|
|
9887
9906
|
await handle.close();
|
|
9888
9907
|
return 0;
|
|
9889
9908
|
}
|
|
@@ -10113,13 +10132,14 @@ async function resolveServerUpstreamApiKey() {
|
|
|
10113
10132
|
return null;
|
|
10114
10133
|
}
|
|
10115
10134
|
async function runServerCommand(options = {}) {
|
|
10135
|
+
const noDiscovery = isDiscoveryDisabled(options.noDiscovery);
|
|
10116
10136
|
if (options.httpProxy) {
|
|
10117
10137
|
const hasGatewayOptions = options.quick || options.listenMode !== void 0 || options.providersMode !== void 0 || options.maskGatewayIds !== void 0 || options.password !== void 0;
|
|
10118
10138
|
if (hasGatewayOptions) {
|
|
10119
10139
|
p9.log.error("--proxy is a local-only server mode and cannot be combined with endpoint-mode server options.");
|
|
10120
10140
|
return 1;
|
|
10121
10141
|
}
|
|
10122
|
-
return runHttpProxyServerCommand(false, options.wsDiagnostics, options.port);
|
|
10142
|
+
return runHttpProxyServerCommand(false, options.wsDiagnostics, options.port, noDiscovery);
|
|
10123
10143
|
}
|
|
10124
10144
|
const apiKey = await resolveServerUpstreamApiKey();
|
|
10125
10145
|
if (!apiKey) {
|
|
@@ -10184,6 +10204,7 @@ async function runServerCommand(options = {}) {
|
|
|
10184
10204
|
return 1;
|
|
10185
10205
|
}
|
|
10186
10206
|
const gateway = runConfig.maskGatewayIds ? { maskGatewayIds: true } : void 0;
|
|
10207
|
+
const modelAliases = loadPreferences().modelAliases ?? [];
|
|
10187
10208
|
const inferenceLogPath = getInferenceRequestLogPath();
|
|
10188
10209
|
const webSocketDiagnosticsLogPath = options.wsDiagnostics ? getSessionLogPath("server-websocket-diagnostics", "jsonl") : void 0;
|
|
10189
10210
|
const server = await startServer({
|
|
@@ -10191,8 +10212,9 @@ async function runServerCommand(options = {}) {
|
|
|
10191
10212
|
port: options.port ?? DEFAULT_SERVER_PORT,
|
|
10192
10213
|
apiKey,
|
|
10193
10214
|
serverPassword,
|
|
10194
|
-
catalog: createGatewayModelCatalog(models, gateway),
|
|
10215
|
+
catalog: createGatewayModelCatalog(models, gateway, modelAliases),
|
|
10195
10216
|
gateway,
|
|
10217
|
+
aliasNames: new Set(modelAliases.map((alias) => alias.name)),
|
|
10196
10218
|
inferenceLogPath,
|
|
10197
10219
|
webSocketDiagnosticsLogPath
|
|
10198
10220
|
});
|
|
@@ -10231,14 +10253,16 @@ async function runServerCommand(options = {}) {
|
|
|
10231
10253
|
console.log("");
|
|
10232
10254
|
printModelCatalog(models, gateway);
|
|
10233
10255
|
console.log(pc10.dim("Press Ctrl+C to stop."));
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10256
|
+
if (!noDiscovery) {
|
|
10257
|
+
registerServerRuntimeState({
|
|
10258
|
+
mode: "endpoint",
|
|
10259
|
+
port: server.port,
|
|
10260
|
+
pid: process.pid,
|
|
10261
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10262
|
+
});
|
|
10263
|
+
}
|
|
10240
10264
|
await waitForShutdown2();
|
|
10241
|
-
|
|
10265
|
+
if (!noDiscovery) unregisterServerRuntimeState();
|
|
10242
10266
|
await server.close();
|
|
10243
10267
|
return 0;
|
|
10244
10268
|
}
|
|
@@ -10527,6 +10551,14 @@ const MODEL_CONFIG = {};
|
|
|
10527
10551
|
// ---- derive helpers --------------------------------------------------------
|
|
10528
10552
|
// alias -> model id (only for entries that define an alias)
|
|
10529
10553
|
const ALIAS_TO_ID = {};
|
|
10554
|
+
// The name Claude Code knows a model by: its alias when it has one, else its
|
|
10555
|
+
// canonical id. This single value is used for the Agent-tool enum, the
|
|
10556
|
+
// known-alias validator, the /model picker value, and the context-window table,
|
|
10557
|
+
// so the name the binary validates == the name it sends upstream == the name
|
|
10558
|
+
// the proxy echoes back == the key its context window is stored under.
|
|
10559
|
+
const IDENTITIES = [];
|
|
10560
|
+
// identity -> human label for the /model picker (falls back at use site)
|
|
10561
|
+
const DISPLAY_BY_IDENTITY = {};
|
|
10530
10562
|
// lowercased alias AND id -> context-window tokens (only for models that set it)
|
|
10531
10563
|
const CONTEXT_BY_KEY = {};
|
|
10532
10564
|
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
@@ -10537,6 +10569,11 @@ for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
|
10537
10569
|
throw new Error('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
10538
10570
|
}
|
|
10539
10571
|
ALIAS_TO_ID[a] = String(id);
|
|
10572
|
+
IDENTITIES.push(a);
|
|
10573
|
+
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
10574
|
+
} else {
|
|
10575
|
+
IDENTITIES.push(String(id));
|
|
10576
|
+
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
10540
10577
|
}
|
|
10541
10578
|
|
|
10542
10579
|
if (spec.context !== undefined) {
|
|
@@ -10560,6 +10597,11 @@ const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
|
10560
10597
|
const MODELS = Object.keys(MODEL_CONFIG);
|
|
10561
10598
|
if (MODELS.length === 0) throw new Error("clodex patch: MODEL_CONFIG is empty");
|
|
10562
10599
|
|
|
10600
|
+
/** Picker/description label for an identity; falls back to the old wording. */
|
|
10601
|
+
function displayFor(identity, fallbackId) {
|
|
10602
|
+
return DISPLAY_BY_IDENTITY[identity] || "Custom model (" + fallbackId + ")";
|
|
10603
|
+
}
|
|
10604
|
+
|
|
10563
10605
|
const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
|
|
10564
10606
|
const q = (s) => JSON.stringify(s); // safe JS string literal
|
|
10565
10607
|
|
|
@@ -10608,22 +10650,24 @@ function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
|
|
|
10608
10650
|
log("OK", name);
|
|
10609
10651
|
}
|
|
10610
10652
|
|
|
10611
|
-
/** Insert missing
|
|
10653
|
+
/** Insert missing identities just before the closing bracket of a JS array literal string. */
|
|
10612
10654
|
function extendAliasArray(arrLiteral) {
|
|
10613
|
-
const toAdd =
|
|
10655
|
+
const toAdd = IDENTITIES.filter((a) => !new RegExp('"' + reEsc(a) + '"').test(arrLiteral));
|
|
10614
10656
|
if (toAdd.length === 0) return arrLiteral; // idempotent
|
|
10615
10657
|
return arrLiteral.replace(/\]\s*$/, "," + toAdd.map(q).join(",") + "]");
|
|
10616
10658
|
}
|
|
10617
10659
|
|
|
10618
|
-
console.error("clodex patch: injecting
|
|
10619
|
-
+ (ALIASES.length ? " aliases [" + ALIASES.join(", ") + "]" : ""));
|
|
10660
|
+
console.error("clodex patch: injecting model names [" + IDENTITIES.join(", ") + "]");
|
|
10620
10661
|
|
|
10621
10662
|
// ---------------------------------------------------------------------------
|
|
10622
10663
|
// PATCH 1 — Agent/subagent tool 'model' zod enum.
|
|
10623
10664
|
// Anchor: .enum([ "sonnet",...,"fable" ]).optional().describe( — the array
|
|
10624
10665
|
// begins with the built-in aliases and is immediately followed by
|
|
10625
|
-
// .optional().describe(. We append our
|
|
10626
|
-
//
|
|
10666
|
+
// .optional().describe(. We append our identities (alias when defined, else
|
|
10667
|
+
// the canonical id) inside the enum so the tool accepts them — this is the same
|
|
10668
|
+
// enum subagent/skill 'model:' frontmatter is validated against, which is why
|
|
10669
|
+
// the short alias has to be the value that lands here.
|
|
10670
|
+
// (This same .describe( is patched by PATCH 4 below.)
|
|
10627
10671
|
// ---------------------------------------------------------------------------
|
|
10628
10672
|
applyOnce(
|
|
10629
10673
|
"PATCH 1: Agent tool model enum",
|
|
@@ -10636,7 +10680,7 @@ applyOnce(
|
|
|
10636
10680
|
// PATCH 3 — known-alias validator list (drives "is this a known alias?").
|
|
10637
10681
|
// Anchor: the master list literal, matched loosely as
|
|
10638
10682
|
// ["sonnet","opus","haiku","fable", ...anything... ,"opusplan"] so it
|
|
10639
|
-
// tolerates new built-ins being added in the middle. Appending our
|
|
10683
|
+
// tolerates new built-ins being added in the middle. Appending our identities
|
|
10640
10684
|
// makes them recognized as first-class aliases everywhere the gate runs.
|
|
10641
10685
|
// ---------------------------------------------------------------------------
|
|
10642
10686
|
applyOnce(
|
|
@@ -10647,16 +10691,26 @@ applyOnce(
|
|
|
10647
10691
|
);
|
|
10648
10692
|
|
|
10649
10693
|
// ---------------------------------------------------------------------------
|
|
10650
|
-
// PATCH 6 — alias
|
|
10694
|
+
// PATCH 6 — alias resolver switch (IDENTITY mapping).
|
|
10651
10695
|
// Anchor: case"best":{ ... } (the case"best":{ is unique). We inject
|
|
10652
|
-
// case"<alias>":return"<
|
|
10653
|
-
// default:return null)
|
|
10696
|
+
// case"<alias>":return"<alias>"; right after it (before the switch's
|
|
10697
|
+
// default:return null).
|
|
10698
|
+
//
|
|
10699
|
+
// The mapping is deliberately an identity, NOT alias -> canonical id: the alias
|
|
10700
|
+
// IS the model's identity everywhere else in the patched binary (enum,
|
|
10701
|
+
// validator, picker, context table), and the MITM proxy resolves short alias
|
|
10702
|
+
// names as request model ids and echoes request bodies unrewritten. Resolving
|
|
10703
|
+
// to the canonical id here would make Claude Code send one name and look its
|
|
10704
|
+
// context window up under another — the exact mismatch that stopped auto-compact
|
|
10705
|
+
// from firing and killed agents with "Prompt is too long". The case still has to
|
|
10706
|
+
// EXIST (rather than be skipped) so the resolver returns the name instead of
|
|
10707
|
+
// falling through to default:return null.
|
|
10654
10708
|
// Only aliases not already present are inserted, so a rerun (or a config
|
|
10655
10709
|
// edit) tops up cleanly rather than duplicating cases.
|
|
10656
10710
|
// ---------------------------------------------------------------------------
|
|
10657
10711
|
{
|
|
10658
10712
|
const missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
|
|
10659
|
-
const cases = missing.map((a) => "case" + q(a) + ":return " + q(
|
|
10713
|
+
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
10660
10714
|
if (ALIASES.length === 0) {
|
|
10661
10715
|
log("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
10662
10716
|
} else {
|
|
@@ -10683,7 +10737,9 @@ applyOnce(
|
|
|
10683
10737
|
.map(
|
|
10684
10738
|
// ASCII only: tweakcc's script->JSON->repack path double-encodes any
|
|
10685
10739
|
// non-ASCII byte.
|
|
10686
|
-
|
|
10740
|
+
// value = the alias (the name the user types and the binary sends);
|
|
10741
|
+
// description = the real model label, e.g. "GPT-5.6 Sol (OpenAI (ChatGPT))".
|
|
10742
|
+
(a) => "{value:" + q(a) + ",label:" + q(a.charAt(0).toUpperCase() + a.slice(1)) + ",description:" + q(displayFor(a, ALIAS_TO_ID[a])) + "}"
|
|
10687
10743
|
)
|
|
10688
10744
|
.join(",");
|
|
10689
10745
|
const inject = missing.length
|
|
@@ -10703,18 +10759,24 @@ applyOnce(
|
|
|
10703
10759
|
|
|
10704
10760
|
// ---------------------------------------------------------------------------
|
|
10705
10761
|
// PATCH 4 — Agent tool 'model' parameter description text.
|
|
10706
|
-
// Append the available model names
|
|
10707
|
-
//
|
|
10762
|
+
// Append the available model names (with their real labels) before the closing
|
|
10763
|
+
// backtick so the model knows which extra names it may request and what they
|
|
10764
|
+
// actually are. Best-effort (cosmetic). The text is spliced into a backtick
|
|
10765
|
+
// template literal, so backticks and interpolation openers are stripped.
|
|
10708
10766
|
// ---------------------------------------------------------------------------
|
|
10709
10767
|
{
|
|
10710
|
-
const
|
|
10768
|
+
const safe = (s) => String(s).replace(/\`/g, "'").replace(/\$\{/g, "(");
|
|
10769
|
+
const listing = IDENTITIES.map(function (i) {
|
|
10770
|
+
const d = DISPLAY_BY_IDENTITY[i];
|
|
10771
|
+
return d ? safe(i) + " = " + safe(d) : safe(i);
|
|
10772
|
+
}).join("; ");
|
|
10711
10773
|
applyOnce(
|
|
10712
10774
|
"PATCH 4: Agent tool model description",
|
|
10713
10775
|
/(describe\(\`Optional model override for this agent[^\`]*?)(\`\))/,
|
|
10714
10776
|
(_m, body, close) =>
|
|
10715
|
-
body.includes("custom
|
|
10777
|
+
body.includes("Additional custom models")
|
|
10716
10778
|
? body + close
|
|
10717
|
-
: body + " Additional custom
|
|
10779
|
+
: body + " Additional custom models: " + listing + "." + close,
|
|
10718
10780
|
{ required: false, noopIsSkip: true }
|
|
10719
10781
|
);
|
|
10720
10782
|
}
|
|
@@ -10800,28 +10862,30 @@ function writePatchManifest(manifest, path = getPatchManifestPath()) {
|
|
|
10800
10862
|
writeFileSync5(path, `${JSON.stringify(manifest, null, 2)}
|
|
10801
10863
|
`, { encoding: "utf8", mode: 384 });
|
|
10802
10864
|
}
|
|
10803
|
-
function buildPatchModelConfig(favorites, aliases,
|
|
10865
|
+
function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
10804
10866
|
const config = {};
|
|
10805
10867
|
const unknownWindows = [];
|
|
10806
10868
|
const aliasByFavorite = new Map(aliases.map((a) => [`${a.providerId}:${a.modelId}`, a.name]));
|
|
10807
10869
|
for (const favorite of favorites) {
|
|
10808
10870
|
const id = stripOneMContextSuffix(httpProxyModelId(favorite.providerId, favorite.modelId));
|
|
10809
10871
|
if (config[id]) continue;
|
|
10810
|
-
const
|
|
10872
|
+
const meta = modelMetaFor(favorite.providerId, favorite.modelId);
|
|
10873
|
+
const context = meta?.contextWindow;
|
|
10811
10874
|
const alias = aliasByFavorite.get(`${favorite.providerId}:${favorite.modelId}`);
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10875
|
+
const entry = {};
|
|
10876
|
+
if (alias) entry.alias = alias;
|
|
10877
|
+
if (context === void 0 || context <= 0) unknownWindows.push(id);
|
|
10878
|
+
else if (context !== 2e5) entry.context = context;
|
|
10879
|
+
const display = meta?.displayName?.trim();
|
|
10880
|
+
if (display) entry.display = display;
|
|
10881
|
+
config[id] = entry;
|
|
10818
10882
|
}
|
|
10819
10883
|
return { config, unknownWindows };
|
|
10820
10884
|
}
|
|
10821
10885
|
function computePatchConfigHash(config) {
|
|
10822
10886
|
const canonical = Object.keys(config).sort().map((key) => {
|
|
10823
10887
|
const entry = config[key];
|
|
10824
|
-
return [key, entry.alias ?? null, entry.context ?? null];
|
|
10888
|
+
return [key, entry.alias ?? null, entry.context ?? null, entry.display ?? null];
|
|
10825
10889
|
});
|
|
10826
10890
|
return createHash5("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
10827
10891
|
}
|
|
@@ -10830,18 +10894,20 @@ function buildDesiredPatchConfig() {
|
|
|
10830
10894
|
const favorites = prefs.favoriteModels ?? [];
|
|
10831
10895
|
const aliases = prefs.modelAliases ?? [];
|
|
10832
10896
|
const registry = loadRegistry();
|
|
10833
|
-
const
|
|
10897
|
+
const meta = /* @__PURE__ */ new Map();
|
|
10834
10898
|
for (const provider of registry.providers) {
|
|
10835
10899
|
for (const model of provider.modelsCache?.models ?? []) {
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10900
|
+
meta.set(`${provider.id}:${model.id}`, {
|
|
10901
|
+
contextWindow: model.contextWindow && model.contextWindow > 0 ? model.contextWindow : void 0,
|
|
10902
|
+
// Same label `clodex server` prints at startup and `models --list` shows.
|
|
10903
|
+
displayName: httpProxyDisplayName(model, provider.name)
|
|
10904
|
+
});
|
|
10839
10905
|
}
|
|
10840
10906
|
}
|
|
10841
10907
|
return buildPatchModelConfig(
|
|
10842
10908
|
favorites,
|
|
10843
10909
|
aliases,
|
|
10844
|
-
(providerId, modelId) =>
|
|
10910
|
+
(providerId, modelId) => meta.get(`${providerId}:${modelId}`)
|
|
10845
10911
|
);
|
|
10846
10912
|
}
|
|
10847
10913
|
function evaluatePatchState(manifest, current) {
|
|
@@ -11206,6 +11272,7 @@ function parseArgs(args) {
|
|
|
11206
11272
|
else if (consumeBridgeModeFlag(arg, parsed2)) continue;
|
|
11207
11273
|
else if (arg === "--save-mode") parsed2.saveBridgeMode = true;
|
|
11208
11274
|
else if (arg === "--ws-diagnostics") parsed2.serverWsDiagnostics = true;
|
|
11275
|
+
else if (arg === "--no-discovery") parsed2.serverNoDiscovery = true;
|
|
11209
11276
|
else if (arg === "--quick" || arg === "--saved") parsed2.serverQuick = true;
|
|
11210
11277
|
else if (arg === "--mask-gateway-ids") parsed2.serverMaskGatewayIds = true;
|
|
11211
11278
|
else if (arg === "--no-mask-gateway-ids") parsed2.serverMaskGatewayIds = false;
|
|
@@ -11438,6 +11505,10 @@ ${pc13.bold("Common options (both modes):")}
|
|
|
11438
11505
|
--save-mode With --endpoint/--proxy: save that mode as the
|
|
11439
11506
|
server default
|
|
11440
11507
|
--port <1-65535> Listen port (default 17645)
|
|
11508
|
+
--no-discovery Do not advertise this server in
|
|
11509
|
+
~/.clodex/server-runtime.json, so the
|
|
11510
|
+
clodex-claude wrapper never bridges to it
|
|
11511
|
+
(CLODEX_NO_DISCOVERY=1 works too)
|
|
11441
11512
|
--ws-diagnostics Log sanitized request envelopes and WebSocket
|
|
11442
11513
|
head decisions
|
|
11443
11514
|
--help, --version Help / version
|
|
@@ -12272,7 +12343,8 @@ Error: ${parsed.error}
|
|
|
12272
12343
|
maskGatewayIds: parsed.serverMaskGatewayIds,
|
|
12273
12344
|
password: parsed.serverPassword,
|
|
12274
12345
|
wsDiagnostics: parsed.serverWsDiagnostics,
|
|
12275
|
-
port: parsed.serverPort
|
|
12346
|
+
port: parsed.serverPort,
|
|
12347
|
+
noDiscovery: parsed.serverNoDiscovery
|
|
12276
12348
|
});
|
|
12277
12349
|
}
|
|
12278
12350
|
if (parsed.command === "models") {
|