@bman654/clodex 2.1.3 → 2.1.5
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/dist/{chunk-OVO6OUZG.js → chunk-VQOX3AXE.js} +20 -10
- package/dist/chunk-VQOX3AXE.js.map +1 -0
- package/dist/claude-wrapper.js +1 -1
- package/dist/cli.js +859 -135
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-OVO6OUZG.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ensureSecureAppHome,
|
|
5
5
|
findClaudeBinary,
|
|
6
6
|
getAppHome,
|
|
7
|
+
getClaudeVersionForBinary,
|
|
7
8
|
getCredentialCleanupPath,
|
|
8
9
|
getCredentialMutationLockPath,
|
|
9
10
|
getCredentialStateRoot,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
withProviderMutationLock,
|
|
39
40
|
withRegistryWriteLock,
|
|
40
41
|
withRegistryWriteLockSync
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-VQOX3AXE.js";
|
|
42
43
|
|
|
43
44
|
// src/cli.ts
|
|
44
45
|
import pc13 from "picocolors";
|
|
@@ -216,7 +217,7 @@ import { join } from "path";
|
|
|
216
217
|
// package.json
|
|
217
218
|
var package_default = {
|
|
218
219
|
name: "@bman654/clodex",
|
|
219
|
-
version: "2.1.
|
|
220
|
+
version: "2.1.5",
|
|
220
221
|
publishConfig: {
|
|
221
222
|
access: "public"
|
|
222
223
|
},
|
|
@@ -5538,6 +5539,7 @@ async function createLanguageModel(spec) {
|
|
|
5538
5539
|
}
|
|
5539
5540
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
5540
5541
|
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
|
|
5542
|
+
var GPT_56_EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
5541
5543
|
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
5542
5544
|
var MISTRAL_EFFORT_LEVELS = ["high", "off"];
|
|
5543
5545
|
var XAI_EFFORT_LEVELS = ["none", "low", "medium", "high"];
|
|
@@ -5701,7 +5703,13 @@ function mapCodexEffortToAnthropic(effort) {
|
|
|
5701
5703
|
return void 0;
|
|
5702
5704
|
}
|
|
5703
5705
|
}
|
|
5704
|
-
function
|
|
5706
|
+
function isGpt56Model(modelId) {
|
|
5707
|
+
return /^gpt-5\.6(?:-|$)/i.test(modelId);
|
|
5708
|
+
}
|
|
5709
|
+
function mapCodexEffortToOpenAI(effort, modelId) {
|
|
5710
|
+
if (modelId && isGpt56Model(modelId) && GPT_56_EFFORT_LEVELS.includes(effort)) {
|
|
5711
|
+
return effort;
|
|
5712
|
+
}
|
|
5705
5713
|
if (effort === "xhigh") return "high";
|
|
5706
5714
|
const allowed = ["low", "medium", "high"];
|
|
5707
5715
|
return allowed.includes(effort) ? effort : void 0;
|
|
@@ -5782,7 +5790,7 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
5782
5790
|
const prefersResponses = modelPrefersResponsesApi(modelId);
|
|
5783
5791
|
if (prefersResponses || metadata?.reasoning) {
|
|
5784
5792
|
return {
|
|
5785
|
-
levels: [...OPENAI_EFFORT_LEVELS],
|
|
5793
|
+
levels: isGpt56Model(modelId) ? [...GPT_56_EFFORT_LEVELS] : [...OPENAI_EFFORT_LEVELS],
|
|
5786
5794
|
defaultLevel: "medium",
|
|
5787
5795
|
supportsSummaries: true,
|
|
5788
5796
|
mode: "controllable",
|
|
@@ -5904,6 +5912,24 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
5904
5912
|
}
|
|
5905
5913
|
return EMPTY_REASONING;
|
|
5906
5914
|
}
|
|
5915
|
+
function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
5916
|
+
if (metadata?.reasoning === false && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
|
|
5917
|
+
return EMPTY_REASONING;
|
|
5918
|
+
}
|
|
5919
|
+
const capabilities = getReasoningCapabilities(npm, modelId, metadata);
|
|
5920
|
+
const seenProviderOptions = /* @__PURE__ */ new Set();
|
|
5921
|
+
return {
|
|
5922
|
+
...capabilities,
|
|
5923
|
+
levels: capabilities.levels.filter((level) => {
|
|
5924
|
+
const providerOptions = effortProviderOptions(npm, level, modelId, metadata);
|
|
5925
|
+
if (!providerOptions) return false;
|
|
5926
|
+
const fingerprint = JSON.stringify(providerOptions);
|
|
5927
|
+
if (seenProviderOptions.has(fingerprint)) return false;
|
|
5928
|
+
seenProviderOptions.add(fingerprint);
|
|
5929
|
+
return true;
|
|
5930
|
+
})
|
|
5931
|
+
};
|
|
5932
|
+
}
|
|
5907
5933
|
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
5908
5934
|
if (!effort) return void 0;
|
|
5909
5935
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
@@ -5915,7 +5941,7 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
5915
5941
|
}
|
|
5916
5942
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
5917
5943
|
if (!modelId || !modelPrefersResponsesApi(modelId)) return void 0;
|
|
5918
|
-
const reasoningEffort = mapCodexEffortToOpenAI(effort);
|
|
5944
|
+
const reasoningEffort = mapCodexEffortToOpenAI(effort, modelId);
|
|
5919
5945
|
return reasoningEffort ? { openai: { reasoningEffort } } : void 0;
|
|
5920
5946
|
}
|
|
5921
5947
|
if (npm === "@ai-sdk/xai") {
|
|
@@ -8817,31 +8843,151 @@ function sendJson(res, status, body) {
|
|
|
8817
8843
|
}
|
|
8818
8844
|
|
|
8819
8845
|
// src/model-aliases.ts
|
|
8820
|
-
var MODEL_ALIAS_PATTERN = /^[
|
|
8821
|
-
|
|
8822
|
-
|
|
8846
|
+
var MODEL_ALIAS_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8847
|
+
var RESERVED_MODEL_ALIASES = /* @__PURE__ */ new Set([
|
|
8848
|
+
"sonnet",
|
|
8849
|
+
"opus",
|
|
8850
|
+
"haiku",
|
|
8851
|
+
"fable",
|
|
8852
|
+
"best",
|
|
8853
|
+
"default",
|
|
8854
|
+
"opusplan",
|
|
8855
|
+
"inherit"
|
|
8856
|
+
]);
|
|
8857
|
+
function canonicalModelAliasName(name) {
|
|
8858
|
+
return name.trim().toLowerCase();
|
|
8859
|
+
}
|
|
8860
|
+
function isReservedModelAlias(name) {
|
|
8861
|
+
return RESERVED_MODEL_ALIASES.has(
|
|
8862
|
+
stripOneMContextSuffix(canonicalModelAliasName(name))
|
|
8863
|
+
);
|
|
8864
|
+
}
|
|
8865
|
+
function isModelAliasNameSyntax(name) {
|
|
8866
|
+
return MODEL_ALIAS_PATTERN.test(canonicalModelAliasName(name));
|
|
8867
|
+
}
|
|
8868
|
+
function modelAliasMatchesName(value, name) {
|
|
8869
|
+
if (!value || typeof value !== "object" || !("name" in value)) return false;
|
|
8870
|
+
const candidate = value.name;
|
|
8871
|
+
return typeof candidate === "string" && canonicalModelAliasName(candidate) === canonicalModelAliasName(name);
|
|
8872
|
+
}
|
|
8873
|
+
function modelAliasMatchesStoredName(value, name) {
|
|
8874
|
+
if (!value || typeof value !== "object" || !("name" in value)) return false;
|
|
8875
|
+
const candidate = value.name;
|
|
8876
|
+
if (typeof candidate !== "string") return false;
|
|
8877
|
+
const requested = name.trim();
|
|
8878
|
+
return isModelAliasNameSyntax(requested) ? canonicalModelAliasName(candidate) === canonicalModelAliasName(requested) : candidate.trim() === requested;
|
|
8879
|
+
}
|
|
8880
|
+
function describeModelAliasRejection(reason) {
|
|
8881
|
+
switch (reason) {
|
|
8882
|
+
case "invalid-name":
|
|
8883
|
+
return "invalid name";
|
|
8884
|
+
case "reserved-name":
|
|
8885
|
+
return "reserved client name";
|
|
8886
|
+
case "invalid-target":
|
|
8887
|
+
return "invalid target";
|
|
8888
|
+
case "conflicting-targets":
|
|
8889
|
+
return "conflicting targets";
|
|
8890
|
+
case "target-not-favorite":
|
|
8891
|
+
return "target is not a saved favorite";
|
|
8892
|
+
}
|
|
8893
|
+
}
|
|
8894
|
+
function normalizeModelAliases(value) {
|
|
8895
|
+
if (value === void 0) {
|
|
8896
|
+
return { aliases: [], accepted: [], rejected: [], rejections: [] };
|
|
8897
|
+
}
|
|
8898
|
+
if (!Array.isArray(value)) {
|
|
8899
|
+
throw new TypeError('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
8900
|
+
}
|
|
8901
|
+
const candidates = [];
|
|
8902
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8903
|
+
for (const [index, item] of value.entries()) {
|
|
8904
|
+
if (!item || typeof item !== "object" || typeof item.name !== "string") {
|
|
8905
|
+
throw new TypeError(
|
|
8906
|
+
`Saved model aliases are malformed: "modelAliases[${index}]" must be an object with a string "name".`
|
|
8907
|
+
);
|
|
8908
|
+
}
|
|
8909
|
+
const source = item;
|
|
8910
|
+
const candidate = { source };
|
|
8911
|
+
candidates.push(candidate);
|
|
8912
|
+
if (typeof source.providerId !== "string" || typeof source.modelId !== "string") {
|
|
8913
|
+
candidate.rejectionReason = "invalid-target";
|
|
8914
|
+
continue;
|
|
8915
|
+
}
|
|
8916
|
+
const normalized = {
|
|
8917
|
+
name: canonicalModelAliasName(source.name),
|
|
8918
|
+
providerId: source.providerId,
|
|
8919
|
+
modelId: source.modelId
|
|
8920
|
+
};
|
|
8921
|
+
if (!isModelAliasNameSyntax(normalized.name)) {
|
|
8922
|
+
candidate.rejectionReason = "invalid-name";
|
|
8923
|
+
continue;
|
|
8924
|
+
}
|
|
8925
|
+
if (isReservedModelAlias(normalized.name)) {
|
|
8926
|
+
candidate.rejectionReason = "reserved-name";
|
|
8927
|
+
continue;
|
|
8928
|
+
}
|
|
8929
|
+
if (!normalized.providerId.trim() || !normalized.modelId.trim()) {
|
|
8930
|
+
candidate.rejectionReason = "invalid-target";
|
|
8931
|
+
continue;
|
|
8932
|
+
}
|
|
8933
|
+
candidate.normalized = normalized;
|
|
8934
|
+
const group = groups.get(normalized.name) ?? [];
|
|
8935
|
+
group.push(candidate);
|
|
8936
|
+
groups.set(normalized.name, group);
|
|
8937
|
+
}
|
|
8938
|
+
for (const group of groups.values()) {
|
|
8939
|
+
const targets = new Set(
|
|
8940
|
+
group.map((candidate) => `${candidate.normalized.providerId}\0${candidate.normalized.modelId}`)
|
|
8941
|
+
);
|
|
8942
|
+
if (targets.size === 1) {
|
|
8943
|
+
group[0].accepted = true;
|
|
8944
|
+
group[0].sources = group.map((candidate) => candidate.source);
|
|
8945
|
+
} else {
|
|
8946
|
+
for (const candidate of group) candidate.rejectionReason = "conflicting-targets";
|
|
8947
|
+
}
|
|
8948
|
+
}
|
|
8949
|
+
const rejections = candidates.filter((candidate) => candidate.rejectionReason !== void 0).map((candidate) => ({
|
|
8950
|
+
alias: candidate.source,
|
|
8951
|
+
reason: candidate.rejectionReason
|
|
8952
|
+
}));
|
|
8953
|
+
const accepted = candidates.filter((candidate) => candidate.accepted === true && candidate.normalized !== void 0).map((candidate) => ({
|
|
8954
|
+
alias: candidate.normalized,
|
|
8955
|
+
source: candidate.source,
|
|
8956
|
+
sources: candidate.sources ?? [candidate.source]
|
|
8957
|
+
}));
|
|
8958
|
+
return {
|
|
8959
|
+
aliases: accepted.map((entry) => entry.alias),
|
|
8960
|
+
accepted,
|
|
8961
|
+
rejected: rejections.map((rejection) => rejection.alias),
|
|
8962
|
+
rejections
|
|
8963
|
+
};
|
|
8823
8964
|
}
|
|
8824
8965
|
function parseModelAliasAssignment(value) {
|
|
8825
8966
|
const separator = value.indexOf("=");
|
|
8826
8967
|
if (separator < 1 || separator === value.length - 1) {
|
|
8827
8968
|
return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
|
|
8828
8969
|
}
|
|
8829
|
-
const name = value.slice(0, separator)
|
|
8830
|
-
if (!
|
|
8970
|
+
const name = canonicalModelAliasName(value.slice(0, separator));
|
|
8971
|
+
if (!MODEL_ALIAS_PATTERN.test(name)) {
|
|
8831
8972
|
return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
|
|
8832
8973
|
}
|
|
8974
|
+
if (isReservedModelAlias(name)) {
|
|
8975
|
+
return { error: "That alias name is reserved by the client." };
|
|
8976
|
+
}
|
|
8833
8977
|
const rawTarget = value.slice(separator + 1).trim();
|
|
8834
8978
|
const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
|
|
8835
8979
|
const targetSeparator = target.indexOf(":");
|
|
8836
|
-
|
|
8980
|
+
const providerId = target.slice(0, targetSeparator).trim();
|
|
8981
|
+
const modelId = stripOneMContextSuffix(target.slice(targetSeparator + 1).trim());
|
|
8982
|
+
if (targetSeparator < 1 || targetSeparator === target.length - 1 || !providerId || !modelId) {
|
|
8837
8983
|
return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
|
|
8838
8984
|
}
|
|
8839
8985
|
return {
|
|
8840
8986
|
name,
|
|
8841
|
-
providerId
|
|
8987
|
+
providerId,
|
|
8842
8988
|
// `models --list` prints Claude's synthetic context suffix. It is a client
|
|
8843
8989
|
// routing hint, not part of the provider catalog id stored in favorites.
|
|
8844
|
-
modelId
|
|
8990
|
+
modelId
|
|
8845
8991
|
};
|
|
8846
8992
|
}
|
|
8847
8993
|
function modelAliasTarget(alias) {
|
|
@@ -8888,11 +9034,32 @@ function makeRouteResolver(localProviders) {
|
|
|
8888
9034
|
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
8889
9035
|
};
|
|
8890
9036
|
}
|
|
8891
|
-
function resolveCatalogModelAliases(modelAliases, resolveRoute) {
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
9037
|
+
function resolveCatalogModelAliases(modelAliases, resolveRoute, catalogRoutes = []) {
|
|
9038
|
+
const normalized = normalizeModelAliases(modelAliases);
|
|
9039
|
+
const catalogRouteIds = new Set(
|
|
9040
|
+
catalogRoutes.map((route) => normalizeRouteLookupId(route.aliasId))
|
|
9041
|
+
);
|
|
9042
|
+
return [
|
|
9043
|
+
...normalized.accepted.map(({ alias, source, sources }) => {
|
|
9044
|
+
const route = resolveRoute(alias.providerId, alias.modelId);
|
|
9045
|
+
const collidesWithCatalog = catalogRouteIds.has(
|
|
9046
|
+
normalizeRouteLookupId(alias.name)
|
|
9047
|
+
);
|
|
9048
|
+
const sourceNames = [...new Set(sources.map((entry) => entry.name))];
|
|
9049
|
+
return {
|
|
9050
|
+
name: alias.name,
|
|
9051
|
+
...source.name === alias.name ? {} : { savedName: source.name },
|
|
9052
|
+
...sourceNames.length === 1 && sourceNames[0] === alias.name ? {} : { sourceNames },
|
|
9053
|
+
routeId: route?.aliasId ?? modelAliasTarget(alias),
|
|
9054
|
+
...collidesWithCatalog ? { unavailableReason: "conflicts with a catalog model id" } : route ? {} : { unavailableReason: "target unavailable" }
|
|
9055
|
+
};
|
|
9056
|
+
}),
|
|
9057
|
+
...normalized.rejections.map((rejection) => ({
|
|
9058
|
+
name: canonicalModelAliasName(rejection.alias.name),
|
|
9059
|
+
...rejection.alias.name === canonicalModelAliasName(rejection.alias.name) ? {} : { savedName: rejection.alias.name },
|
|
9060
|
+
unavailableReason: describeModelAliasRejection(rejection.reason)
|
|
9061
|
+
}))
|
|
9062
|
+
];
|
|
8896
9063
|
}
|
|
8897
9064
|
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
8898
9065
|
const droppedFavorites = [];
|
|
@@ -8916,7 +9083,7 @@ function httpProxyModelId(providerId, modelId) {
|
|
|
8916
9083
|
function httpProxyDisplayName(model, providerName) {
|
|
8917
9084
|
return `${formatModelLabel(model)} (${providerName})`;
|
|
8918
9085
|
}
|
|
8919
|
-
function buildHttpProxyRoutes(providers, favorites, modelAliases =
|
|
9086
|
+
function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max = MAX_MODEL_CATALOG) {
|
|
8920
9087
|
const routes = [];
|
|
8921
9088
|
const unavailable = [];
|
|
8922
9089
|
const unsupported = [];
|
|
@@ -8954,16 +9121,21 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = [], max = MAX
|
|
|
8954
9121
|
routesByFavorite.set(`${favorite.providerId}:${favorite.modelId}`, proxyRoute);
|
|
8955
9122
|
}
|
|
8956
9123
|
const aliases = [];
|
|
8957
|
-
const
|
|
8958
|
-
const
|
|
8959
|
-
for (const alias of
|
|
9124
|
+
const normalizedAliases = normalizeModelAliases(modelAliases);
|
|
9125
|
+
const unavailableAliases = [...normalizedAliases.rejected];
|
|
9126
|
+
for (const { alias, sources } of normalizedAliases.accepted) {
|
|
8960
9127
|
const route = routesByFavorite.get(`${alias.providerId}:${alias.modelId}`);
|
|
8961
|
-
if (!
|
|
8962
|
-
unavailableAliases.push(
|
|
9128
|
+
if (!route) {
|
|
9129
|
+
unavailableAliases.push(...sources);
|
|
8963
9130
|
continue;
|
|
8964
9131
|
}
|
|
8965
|
-
|
|
8966
|
-
aliases.push({
|
|
9132
|
+
const sourceNames = [...new Set(sources.map((source) => source.name))];
|
|
9133
|
+
aliases.push({
|
|
9134
|
+
name: alias.name,
|
|
9135
|
+
routeId: route.aliasId,
|
|
9136
|
+
displayName: route.displayName,
|
|
9137
|
+
...sourceNames.length === 1 && sourceNames[0] === alias.name ? {} : { sourceNames }
|
|
9138
|
+
});
|
|
8967
9139
|
}
|
|
8968
9140
|
return { routes, unavailable, unsupported, aliases, unavailableAliases };
|
|
8969
9141
|
}
|
|
@@ -9044,7 +9216,7 @@ function createGatewayModelCatalog(models, opts, modelAliases) {
|
|
|
9044
9216
|
const canonicalId = httpProxyModelId(gatewayProviderId(model), model.id);
|
|
9045
9217
|
if (!byId.has(canonicalId)) byId.set(canonicalId, model);
|
|
9046
9218
|
}
|
|
9047
|
-
for (const alias of modelAliases
|
|
9219
|
+
for (const alias of normalizeModelAliases(modelAliases).aliases) {
|
|
9048
9220
|
if (byId.has(alias.name)) continue;
|
|
9049
9221
|
const target = models.find(
|
|
9050
9222
|
(model) => gatewayProviderId(model) === alias.providerId && model.id === alias.modelId
|
|
@@ -9092,8 +9264,9 @@ function formatOpenAIModels(models) {
|
|
|
9092
9264
|
}
|
|
9093
9265
|
|
|
9094
9266
|
// src/route-unavailable.ts
|
|
9095
|
-
function routeUnavailableMessage(modelId) {
|
|
9096
|
-
|
|
9267
|
+
function routeUnavailableMessage(modelId, reason) {
|
|
9268
|
+
const detail = reason ? `: ${reason}` : "";
|
|
9269
|
+
return `Clodex model route '${modelId}' is unavailable${detail}. Run \`clodex models --list\` to inspect saved routes and aliases.`;
|
|
9097
9270
|
}
|
|
9098
9271
|
|
|
9099
9272
|
// src/upstream-forward.ts
|
|
@@ -9739,7 +9912,7 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
|
|
|
9739
9912
|
let openToolId = null;
|
|
9740
9913
|
let finishReason = "end_turn";
|
|
9741
9914
|
let usage = {
|
|
9742
|
-
input_tokens: 0,
|
|
9915
|
+
input_tokens: observer?.initialInputTokens ?? 0,
|
|
9743
9916
|
output_tokens: 0,
|
|
9744
9917
|
cache_creation_input_tokens: 0,
|
|
9745
9918
|
cache_read_input_tokens: 0
|
|
@@ -9758,7 +9931,7 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
|
|
|
9758
9931
|
stop_reason: null,
|
|
9759
9932
|
stop_sequence: null,
|
|
9760
9933
|
usage: {
|
|
9761
|
-
input_tokens:
|
|
9934
|
+
input_tokens: 0,
|
|
9762
9935
|
output_tokens: 0,
|
|
9763
9936
|
cache_creation_input_tokens: 0,
|
|
9764
9937
|
cache_read_input_tokens: 0
|
|
@@ -9895,7 +10068,9 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
|
|
|
9895
10068
|
}
|
|
9896
10069
|
case "finish":
|
|
9897
10070
|
if (part.totalUsage) {
|
|
9898
|
-
|
|
10071
|
+
const finalUsage = toAnthropicUsage(part.totalUsage);
|
|
10072
|
+
const hasFinalInputUsage = finalUsage.input_tokens + finalUsage.cache_creation_input_tokens + finalUsage.cache_read_input_tokens > 0;
|
|
10073
|
+
usage = hasFinalInputUsage ? finalUsage : { ...usage, output_tokens: finalUsage.output_tokens };
|
|
9899
10074
|
}
|
|
9900
10075
|
if (part.finishReason === "tool-calls") finishReason = "tool_use";
|
|
9901
10076
|
else if (part.finishReason === "length") finishReason = "max_tokens";
|
|
@@ -10125,6 +10300,7 @@ function anthropicPromptTooLongMessage(body, contextWindow) {
|
|
|
10125
10300
|
// src/proxy.ts
|
|
10126
10301
|
var STREAM_KEEPALIVE_INTERVAL_MS = 2e4;
|
|
10127
10302
|
var STREAM_KEEPALIVE_PING = 'event: ping\ndata: {"type":"ping"}\n\n';
|
|
10303
|
+
var INTERNAL_ADAPTER_KEEPALIVE_TIMEOUT_MS = 6e4;
|
|
10128
10304
|
function createTranslationLifecycle(logPath, requestId, claudeSessionId, modelId, provider) {
|
|
10129
10305
|
if (!logPath || !requestId) return void 0;
|
|
10130
10306
|
const startedAt = Date.now();
|
|
@@ -10253,6 +10429,17 @@ function aliasModelId(realId, providerId) {
|
|
|
10253
10429
|
function lookupRoute(byAlias, id) {
|
|
10254
10430
|
return byAlias.get(normalizeRouteLookupId(id));
|
|
10255
10431
|
}
|
|
10432
|
+
function configuredAliasLookupNames(alias) {
|
|
10433
|
+
const sourceNames = [
|
|
10434
|
+
alias.name,
|
|
10435
|
+
...alias.savedName === void 0 ? [] : [alias.savedName],
|
|
10436
|
+
...alias.sourceNames ?? []
|
|
10437
|
+
];
|
|
10438
|
+
return [...new Set(sourceNames.flatMap((name) => {
|
|
10439
|
+
const trimmed = name.trim();
|
|
10440
|
+
return trimmed === name ? [name] : [name, trimmed];
|
|
10441
|
+
}))].map(normalizeRouteLookupId);
|
|
10442
|
+
}
|
|
10256
10443
|
async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
|
|
10257
10444
|
const proxyToken = randomUUID4();
|
|
10258
10445
|
silenceSdkWarnings();
|
|
@@ -10261,9 +10448,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10261
10448
|
}
|
|
10262
10449
|
const byAlias = new Map(routes.map((r) => [normalizeRouteLookupId(r.aliasId), r]));
|
|
10263
10450
|
const configuredAliasNames = new Set(
|
|
10264
|
-
(modelAliases ?? []).
|
|
10451
|
+
(modelAliases ?? []).flatMap(configuredAliasLookupNames)
|
|
10452
|
+
);
|
|
10453
|
+
const unavailableAliasReasons = new Map(
|
|
10454
|
+
(modelAliases ?? []).filter((alias) => alias.unavailableReason !== void 0).flatMap((alias) => configuredAliasLookupNames(alias).map((name) => [name, alias.unavailableReason]))
|
|
10265
10455
|
);
|
|
10266
10456
|
for (const alias of modelAliases ?? []) {
|
|
10457
|
+
if (alias.routeId === void 0 || alias.unavailableReason !== void 0) continue;
|
|
10267
10458
|
const route = lookupRoute(byAlias, alias.routeId);
|
|
10268
10459
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
10269
10460
|
if (route && !byAlias.has(aliasId)) byAlias.set(aliasId, route);
|
|
@@ -10338,7 +10529,14 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10338
10529
|
const resolvedRoute = typeof originalModel === "string" ? lookupRoute(byAlias, originalModel) : void 0;
|
|
10339
10530
|
const configuredModelUnavailable = typeof originalModel === "string" && (normalizeRouteLookupId(originalModel).startsWith("clodex:") || configuredAliasNames.has(normalizeRouteLookupId(originalModel)));
|
|
10340
10531
|
if (!resolvedRoute && configuredModelUnavailable) {
|
|
10341
|
-
anthropicError(
|
|
10532
|
+
anthropicError(
|
|
10533
|
+
res,
|
|
10534
|
+
400,
|
|
10535
|
+
routeUnavailableMessage(
|
|
10536
|
+
originalModel,
|
|
10537
|
+
unavailableAliasReasons.get(normalizeRouteLookupId(originalModel))
|
|
10538
|
+
)
|
|
10539
|
+
);
|
|
10342
10540
|
return;
|
|
10343
10541
|
}
|
|
10344
10542
|
const route = resolvedRoute ?? defaultRoute;
|
|
@@ -10656,6 +10854,7 @@ data: ${JSON.stringify({
|
|
|
10656
10854
|
}
|
|
10657
10855
|
anthropicError(res, 404, `Unknown endpoint: ${req.method} ${req.url}`);
|
|
10658
10856
|
});
|
|
10857
|
+
server.keepAliveTimeout = INTERNAL_ADAPTER_KEEPALIVE_TIMEOUT_MS;
|
|
10659
10858
|
let address;
|
|
10660
10859
|
try {
|
|
10661
10860
|
address = await listenTcpServer(server, 0, "127.0.0.1");
|
|
@@ -12152,7 +12351,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
12152
12351
|
upstream.end(rawBody);
|
|
12153
12352
|
});
|
|
12154
12353
|
}
|
|
12155
|
-
function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.request, lifecycle, isLocalShutdown = () => false) {
|
|
12354
|
+
function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.request, adapterAgent, lifecycle, isLocalShutdown = () => false) {
|
|
12156
12355
|
return new Promise((resolve3) => {
|
|
12157
12356
|
const startedAt = Date.now();
|
|
12158
12357
|
let lastActivityAt = startedAt;
|
|
@@ -12260,6 +12459,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.requ
|
|
|
12260
12459
|
port: adapter.port,
|
|
12261
12460
|
method: "POST",
|
|
12262
12461
|
path: req.url,
|
|
12462
|
+
agent: adapterAgent,
|
|
12263
12463
|
headers: {
|
|
12264
12464
|
"Content-Type": "application/json",
|
|
12265
12465
|
"Content-Length": String(rawBody.length),
|
|
@@ -12377,6 +12577,10 @@ async function startHttpProxy(options) {
|
|
|
12377
12577
|
for (const alias of options.modelAliases ?? []) {
|
|
12378
12578
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
12379
12579
|
reservedModelIds.add(aliasId);
|
|
12580
|
+
for (const sourceName of alias.sourceNames ?? []) {
|
|
12581
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName));
|
|
12582
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName.trim()));
|
|
12583
|
+
}
|
|
12380
12584
|
const route = routesById.get(normalizeRouteLookupId(alias.routeId));
|
|
12381
12585
|
if (!route) continue;
|
|
12382
12586
|
routesById.set(aliasId, route);
|
|
@@ -12397,6 +12601,7 @@ async function startHttpProxy(options) {
|
|
|
12397
12601
|
options.modelAliases
|
|
12398
12602
|
);
|
|
12399
12603
|
}
|
|
12604
|
+
const adapterAgent = adapter ? new http.Agent({ keepAlive: true }) : void 0;
|
|
12400
12605
|
let shuttingDown = false;
|
|
12401
12606
|
const mitmServer = https.createServer({
|
|
12402
12607
|
key: certificates.serverKey,
|
|
@@ -12490,6 +12695,7 @@ async function startHttpProxy(options) {
|
|
|
12490
12695
|
adapterBody,
|
|
12491
12696
|
adapter,
|
|
12492
12697
|
options.adapterRequest,
|
|
12698
|
+
adapterAgent,
|
|
12493
12699
|
messagesEndpoint === "messages" && options.inferenceLogPath ? {
|
|
12494
12700
|
logPath: options.inferenceLogPath,
|
|
12495
12701
|
requestId,
|
|
@@ -12597,6 +12803,7 @@ async function startHttpProxy(options) {
|
|
|
12597
12803
|
options.host ?? "127.0.0.1"
|
|
12598
12804
|
);
|
|
12599
12805
|
} catch (err) {
|
|
12806
|
+
adapterAgent?.destroy();
|
|
12600
12807
|
adapter?.close();
|
|
12601
12808
|
throw err;
|
|
12602
12809
|
}
|
|
@@ -12624,13 +12831,17 @@ async function startHttpProxy(options) {
|
|
|
12624
12831
|
async function loadHttpProxyRoutes() {
|
|
12625
12832
|
const prefs = loadPreferences();
|
|
12626
12833
|
const favorites = prefs.favoriteModels ?? [];
|
|
12834
|
+
const normalizedAliases = normalizeModelAliases(prefs.modelAliases);
|
|
12627
12835
|
if (favorites.length === 0) {
|
|
12628
12836
|
return {
|
|
12629
12837
|
routes: [],
|
|
12630
12838
|
unavailable: [],
|
|
12631
12839
|
unsupported: [],
|
|
12632
12840
|
aliases: [],
|
|
12633
|
-
unavailableAliases:
|
|
12841
|
+
unavailableAliases: [
|
|
12842
|
+
...normalizedAliases.rejected,
|
|
12843
|
+
...normalizedAliases.accepted.flatMap(({ sources }) => sources)
|
|
12844
|
+
],
|
|
12634
12845
|
favoriteCount: 0
|
|
12635
12846
|
};
|
|
12636
12847
|
}
|
|
@@ -12640,7 +12851,7 @@ async function loadHttpProxyRoutes() {
|
|
|
12640
12851
|
apiKey: await resolveLocalProviderApiKey(provider) ?? ""
|
|
12641
12852
|
})));
|
|
12642
12853
|
return {
|
|
12643
|
-
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases
|
|
12854
|
+
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases),
|
|
12644
12855
|
favoriteCount: favorites.length
|
|
12645
12856
|
};
|
|
12646
12857
|
}
|
|
@@ -12674,8 +12885,16 @@ function reportSkippedHttpProxyFavorites(loaded) {
|
|
|
12674
12885
|
);
|
|
12675
12886
|
}
|
|
12676
12887
|
if (loaded.unavailableAliases.length > 0) {
|
|
12888
|
+
const normalizedAliases = normalizeModelAliases(loaded.unavailableAliases);
|
|
12889
|
+
const reasonByAlias = new Map(
|
|
12890
|
+
normalizedAliases.rejections.map((rejection) => [
|
|
12891
|
+
rejection.alias,
|
|
12892
|
+
describeModelAliasRejection(rejection.reason)
|
|
12893
|
+
])
|
|
12894
|
+
);
|
|
12677
12895
|
p8.log.warn(
|
|
12678
|
-
`${loaded.unavailableAliases.length} model alias${loaded.unavailableAliases.length === 1 ? "" : "es"} skipped
|
|
12896
|
+
`${loaded.unavailableAliases.length} model alias${loaded.unavailableAliases.length === 1 ? "" : "es"} skipped. Saved entries were preserved.
|
|
12897
|
+
` + loaded.unavailableAliases.map((alias) => ` ${JSON.stringify(alias.name)} \u2014 ${reasonByAlias.get(alias) ?? "target unavailable"}`).join("\n")
|
|
12679
12898
|
);
|
|
12680
12899
|
}
|
|
12681
12900
|
}
|
|
@@ -12685,7 +12904,14 @@ function buildConfiguredHttpProxyOptions(loaded, port, debug = false, inferenceL
|
|
|
12685
12904
|
port,
|
|
12686
12905
|
routes: loaded.routes,
|
|
12687
12906
|
modelAliases: loaded.aliases,
|
|
12688
|
-
reservedModelIds:
|
|
12907
|
+
reservedModelIds: [...new Set([
|
|
12908
|
+
...loaded.aliases.flatMap((alias) => alias.sourceNames ?? []),
|
|
12909
|
+
...loaded.unavailableAliases.map((alias) => alias.name)
|
|
12910
|
+
].flatMap((name) => {
|
|
12911
|
+
const trimmedName = name.trim();
|
|
12912
|
+
const canonicalName = canonicalModelAliasName(name);
|
|
12913
|
+
return [name, trimmedName, canonicalName].filter(Boolean);
|
|
12914
|
+
}))],
|
|
12689
12915
|
debug,
|
|
12690
12916
|
debugLogPath,
|
|
12691
12917
|
inferenceLogPath,
|
|
@@ -13096,7 +13322,28 @@ async function runServerCommand(options = {}) {
|
|
|
13096
13322
|
return 1;
|
|
13097
13323
|
}
|
|
13098
13324
|
const gateway = runConfig.maskGatewayIds ? { maskGatewayIds: true } : void 0;
|
|
13099
|
-
const
|
|
13325
|
+
const normalizedAliases = normalizeModelAliases(loadPreferences().modelAliases);
|
|
13326
|
+
const baseCatalog = createGatewayModelCatalog(models, gateway);
|
|
13327
|
+
const unavailableAliases = normalizedAliases.accepted.filter(({ alias }) => !models.some((model) => gatewayProviderId(model) === alias.providerId && model.id === alias.modelId));
|
|
13328
|
+
const collidingAliases = normalizedAliases.accepted.filter(({ alias }) => baseCatalog.get(alias.name) !== void 0);
|
|
13329
|
+
const collidingAliasNames = new Set(collidingAliases.map(({ alias }) => alias.name));
|
|
13330
|
+
const targetUnavailableAliases = unavailableAliases.filter(({ alias }) => !collidingAliasNames.has(alias.name));
|
|
13331
|
+
const inactiveAliasNames = /* @__PURE__ */ new Set([
|
|
13332
|
+
...targetUnavailableAliases.map(({ alias }) => alias.name),
|
|
13333
|
+
...collidingAliasNames
|
|
13334
|
+
]);
|
|
13335
|
+
const modelAliases = normalizedAliases.aliases.filter((alias) => !inactiveAliasNames.has(alias.name));
|
|
13336
|
+
const aliasWarnings = [
|
|
13337
|
+
...normalizedAliases.rejections.map((rejection) => `${JSON.stringify(rejection.alias.name)} \u2014 ${describeModelAliasRejection(rejection.reason)}`),
|
|
13338
|
+
...targetUnavailableAliases.flatMap(({ sources }) => sources.map((source) => `${JSON.stringify(source.name)} \u2014 target unavailable`)),
|
|
13339
|
+
...collidingAliases.flatMap(({ sources }) => sources.map((source) => `${JSON.stringify(source.name)} \u2014 conflicts with a catalog model id`))
|
|
13340
|
+
];
|
|
13341
|
+
if (aliasWarnings.length > 0) {
|
|
13342
|
+
p9.log.warn(
|
|
13343
|
+
`${aliasWarnings.length} saved model alias${aliasWarnings.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
|
|
13344
|
+
${aliasWarnings.join("\n ")}`
|
|
13345
|
+
);
|
|
13346
|
+
}
|
|
13100
13347
|
const inferenceLogPath = getInferenceRequestLogPath();
|
|
13101
13348
|
const webSocketDiagnosticsLogPath = options.wsDiagnostics ? getSessionLogPath("server-websocket-diagnostics", "jsonl") : void 0;
|
|
13102
13349
|
const server = await startServer({
|
|
@@ -13408,25 +13655,192 @@ function planLaunchWizard(opts) {
|
|
|
13408
13655
|
}
|
|
13409
13656
|
|
|
13410
13657
|
// src/patcher.ts
|
|
13411
|
-
import { createHash as
|
|
13658
|
+
import { createHash as createHash8 } from "crypto";
|
|
13412
13659
|
import {
|
|
13413
13660
|
copyFileSync,
|
|
13414
|
-
existsSync as
|
|
13661
|
+
existsSync as existsSync7,
|
|
13662
|
+
mkdtempSync,
|
|
13415
13663
|
mkdirSync as mkdirSync7,
|
|
13416
|
-
readFileSync as
|
|
13417
|
-
|
|
13664
|
+
readFileSync as readFileSync9,
|
|
13665
|
+
renameSync as renameSync3,
|
|
13666
|
+
rmSync,
|
|
13667
|
+
statSync as statSync4,
|
|
13418
13668
|
unlinkSync as unlinkSync4,
|
|
13419
13669
|
writeFileSync as writeFileSync7,
|
|
13420
13670
|
openSync as openSync4,
|
|
13421
13671
|
closeSync as closeSync4,
|
|
13422
13672
|
realpathSync
|
|
13423
13673
|
} from "fs";
|
|
13424
|
-
import { homedir as
|
|
13425
|
-
import { basename, join as
|
|
13674
|
+
import { homedir as homedir3 } from "os";
|
|
13675
|
+
import { basename, dirname as dirname5, join as join8 } from "path";
|
|
13426
13676
|
import pc12 from "picocolors";
|
|
13427
13677
|
import * as p11 from "@clack/prompts";
|
|
13428
13678
|
|
|
13679
|
+
// src/patch-backup.ts
|
|
13680
|
+
import { createHash as createHash7 } from "crypto";
|
|
13681
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, readdirSync, statSync as statSync3 } from "fs";
|
|
13682
|
+
import { homedir as homedir2 } from "os";
|
|
13683
|
+
import { join as join7 } from "path";
|
|
13684
|
+
var BACKUP_SHA_PREFIX_LENGTH = 16;
|
|
13685
|
+
function backupDir() {
|
|
13686
|
+
return process.env["TWEAKCC_CONFIG_DIR"]?.trim() || join7(homedir2(), ".tweakcc");
|
|
13687
|
+
}
|
|
13688
|
+
function sha256File(path) {
|
|
13689
|
+
return createHash7("sha256").update(readFileSync8(path)).digest("hex");
|
|
13690
|
+
}
|
|
13691
|
+
function backupVersionTag(version) {
|
|
13692
|
+
const tag = version.trim().replace(/[^\w.-]+/g, "_");
|
|
13693
|
+
if (!tag) throw new Error("clodex patch: refusing to name a pristine backup for an empty claude version");
|
|
13694
|
+
return tag;
|
|
13695
|
+
}
|
|
13696
|
+
function contentAddressedBackupPath(version, sha256, dir = backupDir()) {
|
|
13697
|
+
return join7(dir, `claude-${backupVersionTag(version)}-${sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH)}.orig`);
|
|
13698
|
+
}
|
|
13699
|
+
function tweakccMirrorBackupPath(dir = backupDir()) {
|
|
13700
|
+
return join7(dir, "native-binary.backup");
|
|
13701
|
+
}
|
|
13702
|
+
function scanPristineBackups(version, dir = backupDir()) {
|
|
13703
|
+
const tag = backupVersionTag(version);
|
|
13704
|
+
const pattern = new RegExp(`^claude-${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:-([0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}))?\\.orig$`);
|
|
13705
|
+
let entries;
|
|
13706
|
+
try {
|
|
13707
|
+
entries = readdirSync(dir);
|
|
13708
|
+
} catch {
|
|
13709
|
+
return { valid: [], corrupt: [] };
|
|
13710
|
+
}
|
|
13711
|
+
const valid = [];
|
|
13712
|
+
const corrupt = [];
|
|
13713
|
+
for (const entry of entries.sort()) {
|
|
13714
|
+
const match = pattern.exec(entry);
|
|
13715
|
+
if (!match) continue;
|
|
13716
|
+
const path = join7(dir, entry);
|
|
13717
|
+
let sha256;
|
|
13718
|
+
try {
|
|
13719
|
+
if (!statSync3(path).isFile()) continue;
|
|
13720
|
+
sha256 = sha256File(path);
|
|
13721
|
+
} catch {
|
|
13722
|
+
corrupt.push(path);
|
|
13723
|
+
continue;
|
|
13724
|
+
}
|
|
13725
|
+
const embedded = match[1];
|
|
13726
|
+
if (embedded) {
|
|
13727
|
+
if (sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH) !== embedded) {
|
|
13728
|
+
corrupt.push(path);
|
|
13729
|
+
continue;
|
|
13730
|
+
}
|
|
13731
|
+
valid.push({ path, kind: "content-addressed", sha256 });
|
|
13732
|
+
} else {
|
|
13733
|
+
valid.push({ path, kind: "legacy", sha256 });
|
|
13734
|
+
}
|
|
13735
|
+
}
|
|
13736
|
+
return { valid, corrupt };
|
|
13737
|
+
}
|
|
13738
|
+
var CLODEX_PATCH_COMMENT_PREFIX = "/*ccpatch:";
|
|
13739
|
+
var LEGACY_CLODEX_PATCH_MARKERS = [
|
|
13740
|
+
"Additional custom models: ",
|
|
13741
|
+
// PATCH 4 — Agent tool model description
|
|
13742
|
+
"function(_i){return _i.value===_o.value}",
|
|
13743
|
+
// PATCH 5 — picker dedupe guard
|
|
13744
|
+
'"clodex:'
|
|
13745
|
+
// PATCH 1/3 — canonical ids as model identities
|
|
13746
|
+
];
|
|
13747
|
+
function isPatchedClaudeSource(source) {
|
|
13748
|
+
return source.includes(CLODEX_PATCH_COMMENT_PREFIX);
|
|
13749
|
+
}
|
|
13750
|
+
function looksLikeLegacyClodexPatch(source) {
|
|
13751
|
+
return !isPatchedClaudeSource(source) && LEGACY_CLODEX_PATCH_MARKERS.some((marker) => source.includes(marker));
|
|
13752
|
+
}
|
|
13753
|
+
function noBackupMessage(facts) {
|
|
13754
|
+
const corrupt = facts.corruptBackups?.length ? ` (${facts.corruptBackups.length} backup file(s) for this version failed integrity checks and were ignored)` : "";
|
|
13755
|
+
return `claude ${facts.version} is already patched and no trustworthy pristine backup for that version exists in ${backupDir()}${corrupt}. Reinstall Claude Code to get a pristine binary, then run \`clodex patch\`.`;
|
|
13756
|
+
}
|
|
13757
|
+
function selectRestoreSource(facts) {
|
|
13758
|
+
const notes = [];
|
|
13759
|
+
const manifest = facts.manifest && facts.manifest.binaryPath === facts.binaryPath ? facts.manifest : null;
|
|
13760
|
+
let chosen = manifest?.pristineSha256 ? facts.backups.find((backup) => backup.sha256 === manifest.pristineSha256) : void 0;
|
|
13761
|
+
if (!chosen && manifest?.backupPath) {
|
|
13762
|
+
chosen = facts.backups.find((backup) => backup.path === manifest.backupPath);
|
|
13763
|
+
if (!chosen) {
|
|
13764
|
+
notes.push(`Recorded pristine backup ${manifest.backupPath} is missing, corrupt, or belongs to another claude version \u2014 ignoring it.`);
|
|
13765
|
+
}
|
|
13766
|
+
}
|
|
13767
|
+
if (!chosen) {
|
|
13768
|
+
const distinct = [...new Set(facts.backups.map((backup) => backup.sha256))];
|
|
13769
|
+
if (distinct.length > 1) {
|
|
13770
|
+
return {
|
|
13771
|
+
action: "error",
|
|
13772
|
+
message: `Found conflicting pristine backups for claude ${facts.version}: ${facts.backups.map((backup) => backup.path).join(", ")}. They do not hold the same bytes, so clodex cannot tell which one is pristine. Remove the wrong one (or reinstall Claude Code), then run \`clodex patch\`.`
|
|
13773
|
+
};
|
|
13774
|
+
}
|
|
13775
|
+
chosen = facts.backups.find((backup) => backup.kind === "content-addressed") ?? facts.backups[0];
|
|
13776
|
+
}
|
|
13777
|
+
if (!chosen) return { action: "error", message: noBackupMessage(facts) };
|
|
13778
|
+
return {
|
|
13779
|
+
action: "restore",
|
|
13780
|
+
backupPath: chosen.path,
|
|
13781
|
+
pristineSha256: chosen.sha256,
|
|
13782
|
+
// A legacy name carries no hash, so its bytes could be anything — including
|
|
13783
|
+
// another version's binary, stored under a mislabeled name by an older
|
|
13784
|
+
// clodex. Executing it is the only evidence available; require it.
|
|
13785
|
+
probeVersion: chosen.kind === "legacy",
|
|
13786
|
+
notes
|
|
13787
|
+
};
|
|
13788
|
+
}
|
|
13789
|
+
function planPristineSource(facts) {
|
|
13790
|
+
const identical = facts.backups.find((backup) => backup.sha256 === facts.liveSha256 && backup.kind === "content-addressed") ?? facts.backups.find((backup) => backup.sha256 === facts.liveSha256);
|
|
13791
|
+
if (identical) {
|
|
13792
|
+
return { action: "reuse", backupPath: identical.path, pristineSha256: identical.sha256, notes: [] };
|
|
13793
|
+
}
|
|
13794
|
+
const manifest = facts.manifest;
|
|
13795
|
+
if (manifest && manifest.binaryPath === facts.binaryPath && manifest.patchedSha256 === facts.liveSha256) {
|
|
13796
|
+
return selectRestoreSource(facts);
|
|
13797
|
+
}
|
|
13798
|
+
return { action: "inspect" };
|
|
13799
|
+
}
|
|
13800
|
+
function planInspectedPristineSource(facts, inspection) {
|
|
13801
|
+
if (inspection.patched) return selectRestoreSource(facts);
|
|
13802
|
+
const notes = [];
|
|
13803
|
+
const conflicting = facts.backups.filter((backup) => backup.sha256 !== facts.liveSha256);
|
|
13804
|
+
if (conflicting.length) {
|
|
13805
|
+
notes.push(
|
|
13806
|
+
`Existing backup(s) for claude ${facts.version} hold different bytes (${conflicting.map((b) => b.path).join(", ")}); the binary being patched carries no clodex patch marker, so it is being stored under its own content address. Both files are kept.`
|
|
13807
|
+
);
|
|
13808
|
+
}
|
|
13809
|
+
return {
|
|
13810
|
+
action: "snapshot",
|
|
13811
|
+
backupPath: contentAddressedBackupPath(facts.version, facts.liveSha256),
|
|
13812
|
+
pristineSha256: facts.liveSha256,
|
|
13813
|
+
notes
|
|
13814
|
+
};
|
|
13815
|
+
}
|
|
13816
|
+
function planRestoreOnly(facts) {
|
|
13817
|
+
return selectRestoreSource(facts);
|
|
13818
|
+
}
|
|
13819
|
+
function collectPristineFacts(args) {
|
|
13820
|
+
const dir = args.dir ?? backupDir();
|
|
13821
|
+
const scan = scanPristineBackups(args.version, dir);
|
|
13822
|
+
return {
|
|
13823
|
+
version: args.version,
|
|
13824
|
+
binaryPath: args.binaryPath,
|
|
13825
|
+
liveSha256: existsSync6(args.binaryPath) ? sha256File(args.binaryPath) : "",
|
|
13826
|
+
manifest: args.manifest,
|
|
13827
|
+
backups: scan.valid,
|
|
13828
|
+
corruptBackups: scan.corrupt
|
|
13829
|
+
};
|
|
13830
|
+
}
|
|
13831
|
+
|
|
13429
13832
|
// src/patch-transforms.ts
|
|
13833
|
+
var PATCH_TRANSFORMS_VERSION = 2;
|
|
13834
|
+
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
13835
|
+
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
13836
|
+
function projectNativeEffort(effort) {
|
|
13837
|
+
if (!effort || !Array.isArray(effort.levels) || typeof effort.defaultLevel !== "string") return void 0;
|
|
13838
|
+
const declared = new Set(effort.levels);
|
|
13839
|
+
const levels = NATIVE_EFFORT_LEVELS.filter((level) => declared.has(level));
|
|
13840
|
+
if (!BASE_EFFORT_LEVELS.every((level) => declared.has(level))) return void 0;
|
|
13841
|
+
if (!levels.some((level) => level === effort.defaultLevel)) return void 0;
|
|
13842
|
+
return { levels, defaultLevel: "high" };
|
|
13843
|
+
}
|
|
13430
13844
|
var PatchApplyError = class extends Error {
|
|
13431
13845
|
results;
|
|
13432
13846
|
constructor(message, results) {
|
|
@@ -13441,21 +13855,37 @@ function formatPatchSiteLine(result) {
|
|
|
13441
13855
|
function applyClodexPatches(source, config) {
|
|
13442
13856
|
let js = source;
|
|
13443
13857
|
const MODEL_CONFIG = config;
|
|
13444
|
-
const ALIAS_TO_ID =
|
|
13858
|
+
const ALIAS_TO_ID = /* @__PURE__ */ Object.create(null);
|
|
13445
13859
|
const IDENTITIES = [];
|
|
13446
|
-
const DISPLAY_BY_IDENTITY =
|
|
13447
|
-
const CONTEXT_BY_KEY =
|
|
13860
|
+
const DISPLAY_BY_IDENTITY = /* @__PURE__ */ Object.create(null);
|
|
13861
|
+
const CONTEXT_BY_KEY = /* @__PURE__ */ Object.create(null);
|
|
13862
|
+
const CONFIGURED_CAPABILITY_KEYS = /* @__PURE__ */ new Set();
|
|
13863
|
+
const EFFORT_BY_KEY = /* @__PURE__ */ Object.create(null);
|
|
13448
13864
|
const report = [];
|
|
13449
13865
|
const fail = (message) => {
|
|
13450
13866
|
throw new PatchApplyError(message, report);
|
|
13451
13867
|
};
|
|
13868
|
+
const capabilityKeys = (value) => {
|
|
13869
|
+
const normalized = value.trim().toLowerCase();
|
|
13870
|
+
const bare = normalized.replace(/\[1m\]$/i, "");
|
|
13871
|
+
return [.../* @__PURE__ */ new Set([bare, `${bare}[1m]`])];
|
|
13872
|
+
};
|
|
13873
|
+
const registerCapabilityKeys = (value) => {
|
|
13874
|
+
for (const key of capabilityKeys(value)) {
|
|
13875
|
+
CONFIGURED_CAPABILITY_KEYS.add(key);
|
|
13876
|
+
}
|
|
13877
|
+
};
|
|
13452
13878
|
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
13453
13879
|
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
13454
13880
|
if (spec.alias !== void 0) {
|
|
13455
|
-
const
|
|
13881
|
+
const rawAlias = String(spec.alias).trim();
|
|
13882
|
+
const a = rawAlias.toLowerCase();
|
|
13456
13883
|
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
13457
13884
|
fail('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
13458
13885
|
}
|
|
13886
|
+
if (isReservedModelAlias(a)) {
|
|
13887
|
+
fail('clodex patch: reserved alias "' + a + '" cannot be reassigned');
|
|
13888
|
+
}
|
|
13459
13889
|
ALIAS_TO_ID[a] = String(id);
|
|
13460
13890
|
IDENTITIES.push(a);
|
|
13461
13891
|
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
@@ -13463,6 +13893,10 @@ function applyClodexPatches(source, config) {
|
|
|
13463
13893
|
IDENTITIES.push(String(id));
|
|
13464
13894
|
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
13465
13895
|
}
|
|
13896
|
+
if (spec.alias !== void 0) {
|
|
13897
|
+
registerCapabilityKeys(String(spec.alias));
|
|
13898
|
+
}
|
|
13899
|
+
registerCapabilityKeys(String(id));
|
|
13466
13900
|
if (spec.context !== void 0) {
|
|
13467
13901
|
const n = Number(spec.context);
|
|
13468
13902
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -13476,6 +13910,22 @@ function applyClodexPatches(source, config) {
|
|
|
13476
13910
|
if (spec.alias !== void 0) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
13477
13911
|
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
13478
13912
|
}
|
|
13913
|
+
if (spec.effort) {
|
|
13914
|
+
const effort = projectNativeEffort(spec.effort);
|
|
13915
|
+
if (!effort) {
|
|
13916
|
+
fail(
|
|
13917
|
+
`clodex patch: effort for "${id}" must include low, medium, and high with a native default`
|
|
13918
|
+
);
|
|
13919
|
+
}
|
|
13920
|
+
if (spec.alias !== void 0) {
|
|
13921
|
+
for (const key of capabilityKeys(String(spec.alias))) {
|
|
13922
|
+
EFFORT_BY_KEY[key] = effort;
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
for (const key of capabilityKeys(String(id))) {
|
|
13926
|
+
EFFORT_BY_KEY[key] = effort;
|
|
13927
|
+
}
|
|
13928
|
+
}
|
|
13479
13929
|
}
|
|
13480
13930
|
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
13481
13931
|
const MODELS = Object.keys(MODEL_CONFIG);
|
|
@@ -13603,19 +14053,91 @@ function applyClodexPatches(source, config) {
|
|
|
13603
14053
|
);
|
|
13604
14054
|
}
|
|
13605
14055
|
}
|
|
14056
|
+
function patchEffortCapability(capability, marker, name, anchor) {
|
|
14057
|
+
const verdicts = Object.fromEntries(
|
|
14058
|
+
[...CONFIGURED_CAPABILITY_KEYS].map((key) => {
|
|
14059
|
+
const effort = EFFORT_BY_KEY[key];
|
|
14060
|
+
return [
|
|
14061
|
+
key,
|
|
14062
|
+
effort !== void 0 && (capability === "effort" || effort.levels.includes(capability === "xhigh_effort" ? "xhigh" : "max"))
|
|
14063
|
+
];
|
|
14064
|
+
})
|
|
14065
|
+
);
|
|
14066
|
+
const hasMarker = js.includes(marker);
|
|
14067
|
+
if (Object.keys(verdicts).length === 0 && !hasMarker) return;
|
|
14068
|
+
const snippet = (arg) => marker + "var _ccv=Object.assign(Object.create(null)," + JSON.stringify(verdicts) + ")[String(" + arg + '||"").trim().toLowerCase()];if(_ccv!==void 0)return _ccv;';
|
|
14069
|
+
if (hasMarker) {
|
|
14070
|
+
const markerPattern = reEsc(marker);
|
|
14071
|
+
applyOnce(
|
|
14072
|
+
name + " (refresh)",
|
|
14073
|
+
new RegExp(
|
|
14074
|
+
markerPattern + 'var _ccv=Object\\.assign\\(Object\\.create\\(null\\),\\{[^{}]*\\}\\)\\[String\\(([\\w$]+)\\|\\|""\\)\\.trim\\(\\)\\.toLowerCase\\(\\)\\];if\\(_ccv!==void 0\\)return _ccv;'
|
|
14075
|
+
),
|
|
14076
|
+
(_m, arg) => snippet(arg),
|
|
14077
|
+
{ required: false, noopIsSkip: true }
|
|
14078
|
+
);
|
|
14079
|
+
return;
|
|
14080
|
+
}
|
|
14081
|
+
applyOnce(
|
|
14082
|
+
name,
|
|
14083
|
+
anchor,
|
|
14084
|
+
(_m, head, arg, body) => head + snippet(arg) + body,
|
|
14085
|
+
{ required: false }
|
|
14086
|
+
);
|
|
14087
|
+
}
|
|
14088
|
+
patchEffortCapability(
|
|
14089
|
+
"effort",
|
|
14090
|
+
"/*ccpatch:effort*/",
|
|
14091
|
+
"PATCH 8a: effort capability",
|
|
14092
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"effort"\);)/
|
|
14093
|
+
);
|
|
14094
|
+
patchEffortCapability(
|
|
14095
|
+
"xhigh_effort",
|
|
14096
|
+
"/*ccpatch:xhigh-effort*/",
|
|
14097
|
+
"PATCH 8b: xhigh effort capability",
|
|
14098
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"xhigh_effort"\);)/
|
|
14099
|
+
);
|
|
14100
|
+
patchEffortCapability(
|
|
14101
|
+
"max_effort",
|
|
14102
|
+
"/*ccpatch:max-effort*/",
|
|
14103
|
+
"PATCH 8c: max effort capability",
|
|
14104
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"max_effort"\);)/
|
|
14105
|
+
);
|
|
14106
|
+
const DEFAULT_EFFORT_MARKER = "/*ccpatch:default-effort*/";
|
|
14107
|
+
const defaults = Object.fromEntries(
|
|
14108
|
+
Object.entries(EFFORT_BY_KEY).map(([key, effort]) => [key, effort.defaultLevel])
|
|
14109
|
+
);
|
|
14110
|
+
if (Object.keys(defaults).length || js.includes(DEFAULT_EFFORT_MARKER)) {
|
|
14111
|
+
const snippet = (arg) => DEFAULT_EFFORT_MARKER + "var _cce=Object.assign(Object.create(null)," + JSON.stringify(defaults) + ")[String(" + arg + '||"").trim().toLowerCase()];if(_cce!==void 0)return _cce;';
|
|
14112
|
+
if (js.includes(DEFAULT_EFFORT_MARKER)) {
|
|
14113
|
+
applyOnce(
|
|
14114
|
+
"PATCH 9: default effort (refresh)",
|
|
14115
|
+
/\/\*ccpatch:default-effort\*\/var _cce=Object\.assign\(Object\.create\(null\),\{[^{}]*\}\)\[String\(([\w$]+)\|\|""\)\.trim\(\)\.toLowerCase\(\)\];if\(_cce!==void 0\)return _cce;/,
|
|
14116
|
+
(_m, arg) => snippet(arg),
|
|
14117
|
+
{ required: false, noopIsSkip: true }
|
|
14118
|
+
);
|
|
14119
|
+
} else {
|
|
14120
|
+
applyOnce(
|
|
14121
|
+
"PATCH 9: default effort",
|
|
14122
|
+
/(function [\w$]+\(([\w$]+)\)\{)(return [\w$]+\([\w$]+\(\2\)\)\?\.default_effort\?\?"high"\})/,
|
|
14123
|
+
(_m, head, arg, body) => head + snippet(arg) + body,
|
|
14124
|
+
{ required: false }
|
|
14125
|
+
);
|
|
14126
|
+
}
|
|
14127
|
+
}
|
|
13606
14128
|
return { content: js, results: report };
|
|
13607
14129
|
}
|
|
13608
14130
|
|
|
13609
14131
|
// src/patcher.ts
|
|
13610
14132
|
function getPatchManifestPath() {
|
|
13611
|
-
return
|
|
14133
|
+
return join8(getAppHome(), "patch-state.json");
|
|
13612
14134
|
}
|
|
13613
14135
|
function getPatchLockPath() {
|
|
13614
|
-
return
|
|
14136
|
+
return join8(getAppHome(), "patch.lock");
|
|
13615
14137
|
}
|
|
13616
14138
|
function readPatchManifest(path = getPatchManifestPath()) {
|
|
13617
14139
|
try {
|
|
13618
|
-
const parsed = JSON.parse(
|
|
14140
|
+
const parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
13619
14141
|
if (parsed && typeof parsed.binaryPath === "string" && typeof parsed.configHash === "string") {
|
|
13620
14142
|
return parsed;
|
|
13621
14143
|
}
|
|
@@ -13631,7 +14153,20 @@ function writePatchManifest(manifest, path = getPatchManifestPath()) {
|
|
|
13631
14153
|
function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
13632
14154
|
const config = {};
|
|
13633
14155
|
const unknownWindows = [];
|
|
13634
|
-
const
|
|
14156
|
+
const normalizedAliases = normalizeModelAliases(aliases);
|
|
14157
|
+
const favoriteTargets = new Set(
|
|
14158
|
+
favorites.map((favorite) => `${favorite.providerId}:${favorite.modelId}`)
|
|
14159
|
+
);
|
|
14160
|
+
const targetRejections = normalizedAliases.accepted.filter(({ alias }) => !favoriteTargets.has(`${alias.providerId}:${alias.modelId}`)).flatMap(({ sources }) => sources.map((source) => ({
|
|
14161
|
+
alias: source,
|
|
14162
|
+
reason: "target-not-favorite"
|
|
14163
|
+
})));
|
|
14164
|
+
const aliasByFavorite = new Map(
|
|
14165
|
+
normalizedAliases.aliases.filter((alias) => favoriteTargets.has(`${alias.providerId}:${alias.modelId}`)).map((alias) => [
|
|
14166
|
+
`${alias.providerId}:${alias.modelId}`,
|
|
14167
|
+
alias.name
|
|
14168
|
+
])
|
|
14169
|
+
);
|
|
13635
14170
|
for (const favorite of favorites) {
|
|
13636
14171
|
const id = stripOneMContextSuffix(httpProxyModelId(favorite.providerId, favorite.modelId));
|
|
13637
14172
|
if (config[id]) continue;
|
|
@@ -13644,29 +14179,68 @@ function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
|
13644
14179
|
else if (context !== 2e5) entry.context = context;
|
|
13645
14180
|
const display = meta?.displayName?.trim();
|
|
13646
14181
|
if (display) entry.display = display;
|
|
14182
|
+
const effort = projectNativeEffort(meta?.effort);
|
|
14183
|
+
if (effort) entry.effort = effort;
|
|
13647
14184
|
config[id] = entry;
|
|
13648
14185
|
}
|
|
13649
|
-
return {
|
|
14186
|
+
return {
|
|
14187
|
+
config,
|
|
14188
|
+
unknownWindows,
|
|
14189
|
+
rejectedAliases: [
|
|
14190
|
+
...normalizedAliases.rejected,
|
|
14191
|
+
...targetRejections.map((rejection) => rejection.alias)
|
|
14192
|
+
],
|
|
14193
|
+
rejectedAliasRejections: [
|
|
14194
|
+
...normalizedAliases.rejections,
|
|
14195
|
+
...targetRejections
|
|
14196
|
+
]
|
|
14197
|
+
};
|
|
13650
14198
|
}
|
|
13651
|
-
function
|
|
14199
|
+
function reportRejectedModelAliases(rejections) {
|
|
14200
|
+
for (const rejection of rejections) {
|
|
14201
|
+
p11.log.warn(
|
|
14202
|
+
`Saved model alias ${JSON.stringify(rejection.alias.name)} was not patched \u2014 ${describeModelAliasRejection(rejection.reason)}. The saved entry was preserved.`
|
|
14203
|
+
);
|
|
14204
|
+
}
|
|
14205
|
+
}
|
|
14206
|
+
function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VERSION) {
|
|
13652
14207
|
const canonical = Object.keys(config).sort().map((key) => {
|
|
13653
14208
|
const entry = config[key];
|
|
13654
|
-
return [
|
|
14209
|
+
return [
|
|
14210
|
+
key,
|
|
14211
|
+
entry.alias ?? null,
|
|
14212
|
+
entry.context ?? null,
|
|
14213
|
+
entry.display ?? null,
|
|
14214
|
+
entry.effort?.levels ?? null,
|
|
14215
|
+
entry.effort?.defaultLevel ?? null
|
|
14216
|
+
];
|
|
13655
14217
|
});
|
|
13656
|
-
return
|
|
14218
|
+
return createHash8("sha256").update(JSON.stringify([transformsVersion, canonical])).digest("hex");
|
|
13657
14219
|
}
|
|
13658
14220
|
function buildDesiredPatchConfig() {
|
|
13659
14221
|
const prefs = loadPreferences();
|
|
13660
14222
|
const favorites = prefs.favoriteModels ?? [];
|
|
13661
|
-
const aliases = prefs.modelAliases
|
|
14223
|
+
const aliases = prefs.modelAliases;
|
|
13662
14224
|
const registry = loadRegistry();
|
|
13663
14225
|
const meta = /* @__PURE__ */ new Map();
|
|
13664
14226
|
for (const provider of registry.providers) {
|
|
13665
14227
|
for (const model of provider.modelsCache?.models ?? []) {
|
|
14228
|
+
const npm = model.npm ?? provider.api.npm ?? "";
|
|
14229
|
+
const upstreamModelId2 = model.upstreamModelId ?? model.id;
|
|
14230
|
+
const modelsDev = findModelsDevModel(provider.id, model.id);
|
|
14231
|
+
const effort = getPatchReasoningCapabilities(npm, upstreamModelId2, {
|
|
14232
|
+
providerId: provider.id,
|
|
14233
|
+
apiBaseUrl: model.apiUrl ?? provider.api.url,
|
|
14234
|
+
supportedParameters: model.supportedParameters,
|
|
14235
|
+
reasoning: model.reasoning ?? modelsDev?.reasoning,
|
|
14236
|
+
interleavedReasoningField: model.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
14237
|
+
upstreamModelId: upstreamModelId2
|
|
14238
|
+
});
|
|
13666
14239
|
meta.set(`${provider.id}:${model.id}`, {
|
|
13667
14240
|
contextWindow: model.contextWindow && model.contextWindow > 0 ? model.contextWindow : void 0,
|
|
13668
14241
|
// Same label `clodex server` prints at startup and `models --list` shows.
|
|
13669
|
-
displayName: httpProxyDisplayName(model, provider.name)
|
|
14242
|
+
displayName: httpProxyDisplayName(model, provider.name),
|
|
14243
|
+
effort: effort.mode === "controllable" ? { levels: effort.levels, defaultLevel: effort.defaultLevel } : void 0
|
|
13670
14244
|
});
|
|
13671
14245
|
}
|
|
13672
14246
|
}
|
|
@@ -13696,7 +14270,7 @@ function pidIsAlive(pid) {
|
|
|
13696
14270
|
function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
13697
14271
|
const now = opts.now ?? Date.now();
|
|
13698
14272
|
const isAlive = opts.isAlive ?? pidIsAlive;
|
|
13699
|
-
mkdirSync7(
|
|
14273
|
+
mkdirSync7(join8(lockPath, ".."), { recursive: true, mode: 448 });
|
|
13700
14274
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13701
14275
|
try {
|
|
13702
14276
|
const fd = openSync4(lockPath, "wx");
|
|
@@ -13712,7 +14286,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13712
14286
|
} catch {
|
|
13713
14287
|
let stale = false;
|
|
13714
14288
|
try {
|
|
13715
|
-
const existing = JSON.parse(
|
|
14289
|
+
const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
13716
14290
|
stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
|
|
13717
14291
|
} catch {
|
|
13718
14292
|
stale = true;
|
|
@@ -13726,33 +14300,28 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13726
14300
|
}
|
|
13727
14301
|
return null;
|
|
13728
14302
|
}
|
|
13729
|
-
function sha256File(path) {
|
|
13730
|
-
return createHash7("sha256").update(readFileSync8(path)).digest("hex");
|
|
13731
|
-
}
|
|
13732
14303
|
function resolveClaudeBinaryForPatch() {
|
|
13733
14304
|
const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"];
|
|
13734
|
-
const nativeSymlink =
|
|
13735
|
-
const source = envOverride?.trim() || (
|
|
13736
|
-
if (!source) return
|
|
14305
|
+
const nativeSymlink = join8(homedir3(), ".local", "bin", "claude");
|
|
14306
|
+
const source = envOverride?.trim() || (existsSync7(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
|
|
14307
|
+
if (!source) return { ok: false, reason: "binary-not-found" };
|
|
13737
14308
|
let resolved;
|
|
13738
14309
|
try {
|
|
13739
14310
|
resolved = realpathSync(source);
|
|
13740
14311
|
} catch {
|
|
13741
|
-
return
|
|
14312
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13742
14313
|
}
|
|
13743
14314
|
try {
|
|
13744
|
-
if (!
|
|
14315
|
+
if (!statSync4(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
|
|
13745
14316
|
} catch {
|
|
13746
|
-
return
|
|
14317
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13747
14318
|
}
|
|
13748
|
-
|
|
14319
|
+
const version = getClaudeVersionForBinary(resolved);
|
|
14320
|
+
if (!version) return { ok: false, reason: "version-unknown", binaryPath: resolved };
|
|
14321
|
+
return { ok: true, binaryPath: resolved, version };
|
|
13749
14322
|
}
|
|
13750
|
-
function
|
|
13751
|
-
return
|
|
13752
|
-
}
|
|
13753
|
-
function pristineBackupPath(version, binaryPath) {
|
|
13754
|
-
const tag = version.replace(/[^\w.-]+/g, "_") || basename(binaryPath);
|
|
13755
|
-
return join7(backupDir(), `claude-${tag}.orig`);
|
|
14323
|
+
function describePatchTargetFailure(target) {
|
|
14324
|
+
return target.reason === "binary-not-found" ? "claude binary not found. Install Claude Code or set TWEAKCC_CC_INSTALLATION_PATH." : `Could not determine the version of ${target.binaryPath} (\`claude --version\` failed). clodex will not patch a binary whose version it cannot read, because the version selects the pristine backup it patches from. If a previous patch left the install broken, \`clodex patch --restore\` still works \u2014 it reads the version from the patch manifest.`;
|
|
13756
14325
|
}
|
|
13757
14326
|
function summarizePatchResults(results) {
|
|
13758
14327
|
const lines = results.map(formatPatchSiteLine);
|
|
@@ -13765,26 +14334,112 @@ function summarizePatchResults(results) {
|
|
|
13765
14334
|
}
|
|
13766
14335
|
return lines;
|
|
13767
14336
|
}
|
|
14337
|
+
function verifyPristineSource(plan, version) {
|
|
14338
|
+
if (!plan.probeVersion) return { ok: true };
|
|
14339
|
+
const backupVersion = getClaudeVersionForBinary(plan.backupPath);
|
|
14340
|
+
if (backupVersion === version) return { ok: true };
|
|
14341
|
+
return {
|
|
14342
|
+
ok: false,
|
|
14343
|
+
message: `Refusing to use ${plan.backupPath} as the pristine source for claude ${version}: it reports ${backupVersion ? `version ${backupVersion}` : "no version at all"}. That backup predates content-addressed backup names and does not hold this version's bytes. Remove it (or reinstall Claude Code), then run \`clodex patch\`.`
|
|
14344
|
+
};
|
|
14345
|
+
}
|
|
14346
|
+
function publishBackupFile(from, to) {
|
|
14347
|
+
const temp = `${to}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
14348
|
+
try {
|
|
14349
|
+
copyFileSync(from, temp);
|
|
14350
|
+
renameSync3(temp, to);
|
|
14351
|
+
} catch (err) {
|
|
14352
|
+
try {
|
|
14353
|
+
rmSync(temp, { force: true });
|
|
14354
|
+
} catch {
|
|
14355
|
+
}
|
|
14356
|
+
throw err;
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
function describePoisonedPristineSource(backupPath, version) {
|
|
14360
|
+
return `Refusing to patch from ${backupPath}: those bytes already carry a clodex patch, so they are not pristine and patching them would stack a patch on a patch. That backup was recorded as pristine for claude ${version} by an older clodex, which snapshotted whatever binary was live when no backup existed. Delete ${backupPath}, reinstall Claude Code, then run \`clodex patch\`.`;
|
|
14361
|
+
}
|
|
14362
|
+
function requiredEffortPatchFailures(results) {
|
|
14363
|
+
return results.filter(
|
|
14364
|
+
(result) => result.status === "FAIL" && (result.name.startsWith("PATCH 8a:") || result.name.startsWith("PATCH 8b:") || result.name.startsWith("PATCH 8c:") || result.name.startsWith("PATCH 9:"))
|
|
14365
|
+
);
|
|
14366
|
+
}
|
|
13768
14367
|
async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
13769
|
-
|
|
13770
|
-
mkdirSync7(backupDir(), { recursive: true });
|
|
13771
|
-
if (opts.restoreFirst) {
|
|
13772
|
-
if (!existsSync6(backup)) {
|
|
13773
|
-
return { ok: false, message: `Cannot re-patch: pristine backup missing at ${backup}. Reinstall claude, then run clodex patch.` };
|
|
13774
|
-
}
|
|
13775
|
-
copyFileSync(backup, binaryPath);
|
|
13776
|
-
} else if (!existsSync6(backup)) {
|
|
13777
|
-
copyFileSync(binaryPath, backup);
|
|
13778
|
-
}
|
|
13779
|
-
copyFileSync(backup, join7(backupDir(), "native-binary.backup"));
|
|
13780
|
-
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
14368
|
+
let candidateDir;
|
|
13781
14369
|
let results;
|
|
14370
|
+
let patchedSize;
|
|
14371
|
+
let patchedSha256;
|
|
14372
|
+
let backup;
|
|
14373
|
+
let pristineSha256;
|
|
13782
14374
|
try {
|
|
13783
|
-
|
|
13784
|
-
const
|
|
13785
|
-
|
|
14375
|
+
mkdirSync7(backupDir(), { recursive: true });
|
|
14376
|
+
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
14377
|
+
candidateDir = mkdtempSync(join8(dirname5(binaryPath), ".clodex-patch-"));
|
|
14378
|
+
const candidatePath = join8(candidateDir, basename(binaryPath));
|
|
14379
|
+
const seedCandidate = async (from) => {
|
|
14380
|
+
copyFileSync(from, candidatePath);
|
|
14381
|
+
const installation = await tryDetectInstallation({ path: candidatePath });
|
|
14382
|
+
return { installation, source: await readContent(installation) };
|
|
14383
|
+
};
|
|
14384
|
+
const facts = collectPristineFacts({
|
|
14385
|
+
version,
|
|
14386
|
+
binaryPath,
|
|
14387
|
+
manifest: opts.manifest
|
|
14388
|
+
});
|
|
14389
|
+
const initial = planPristineSource(facts);
|
|
14390
|
+
let loaded = null;
|
|
14391
|
+
let plan;
|
|
14392
|
+
if (initial.action === "inspect") {
|
|
14393
|
+
loaded = await seedCandidate(binaryPath);
|
|
14394
|
+
if (looksLikeLegacyClodexPatch(loaded.source)) {
|
|
14395
|
+
p11.log.warn(
|
|
14396
|
+
`${binaryPath} carries no clodex patch marker but does look like a patch from a clodex older than the effort patch sites. Treating it as pristine; if \`/model\` shows stale entries afterwards, reinstall Claude Code and run \`clodex patch\` again.`
|
|
14397
|
+
);
|
|
14398
|
+
}
|
|
14399
|
+
plan = planInspectedPristineSource(facts, { patched: isPatchedClaudeSource(loaded.source) });
|
|
14400
|
+
if (plan.action !== "snapshot") loaded = null;
|
|
14401
|
+
} else {
|
|
14402
|
+
plan = initial;
|
|
14403
|
+
}
|
|
14404
|
+
if (plan.action === "error") return { ok: false, message: plan.message };
|
|
14405
|
+
for (const note of plan.notes) p11.log.warn(note);
|
|
14406
|
+
if (plan.action === "restore") {
|
|
14407
|
+
const verified = verifyPristineSource(plan, version);
|
|
14408
|
+
if (!verified.ok) return { ok: false, message: verified.message };
|
|
14409
|
+
p11.log.info(`Binary differs from its pristine backup \u2014 building a fresh patch candidate from ${plan.backupPath}.`);
|
|
14410
|
+
}
|
|
14411
|
+
pristineSha256 = plan.pristineSha256;
|
|
14412
|
+
backup = plan.backupPath;
|
|
14413
|
+
if (!loaded) {
|
|
14414
|
+
loaded = await seedCandidate(backup);
|
|
14415
|
+
if (isPatchedClaudeSource(loaded.source)) {
|
|
14416
|
+
return { ok: false, message: describePoisonedPristineSource(backup, version) };
|
|
14417
|
+
}
|
|
14418
|
+
} else if (plan.action === "snapshot") {
|
|
14419
|
+
publishBackupFile(candidatePath, plan.backupPath);
|
|
14420
|
+
}
|
|
14421
|
+
const canonical = contentAddressedBackupPath(version, pristineSha256);
|
|
14422
|
+
if (canonical !== backup) {
|
|
14423
|
+
const alreadyStored = facts.backups.some(
|
|
14424
|
+
(candidate) => candidate.path === canonical && candidate.sha256 === pristineSha256
|
|
14425
|
+
);
|
|
14426
|
+
if (!alreadyStored) publishBackupFile(backup, canonical);
|
|
14427
|
+
backup = canonical;
|
|
14428
|
+
}
|
|
14429
|
+
publishBackupFile(backup, tweakccMirrorBackupPath());
|
|
14430
|
+
const patched = applyClodexPatches(loaded.source, desired.config);
|
|
13786
14431
|
results = patched.results;
|
|
13787
|
-
|
|
14432
|
+
const failedEffortPatches = requiredEffortPatchFailures(results);
|
|
14433
|
+
if (failedEffortPatches.length > 0) {
|
|
14434
|
+
throw new PatchApplyError(
|
|
14435
|
+
`clodex patch: required effort patches failed: ${failedEffortPatches.map((result) => result.name).join("; ")}`,
|
|
14436
|
+
results
|
|
14437
|
+
);
|
|
14438
|
+
}
|
|
14439
|
+
await writeContent(loaded.installation, patched.content);
|
|
14440
|
+
patchedSize = statSync4(candidatePath).size;
|
|
14441
|
+
patchedSha256 = sha256File(candidatePath);
|
|
14442
|
+
renameSync3(candidatePath, binaryPath);
|
|
13788
14443
|
} catch (err) {
|
|
13789
14444
|
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
13790
14445
|
if (opts.trace && detailLines.length) {
|
|
@@ -13796,6 +14451,19 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13796
14451
|
message: `Patch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
13797
14452
|
detailLines
|
|
13798
14453
|
};
|
|
14454
|
+
} finally {
|
|
14455
|
+
if (candidateDir !== void 0) {
|
|
14456
|
+
try {
|
|
14457
|
+
rmSync(candidateDir, { recursive: true, force: true });
|
|
14458
|
+
} catch (err) {
|
|
14459
|
+
if (opts.trace) {
|
|
14460
|
+
process.stderr.write(
|
|
14461
|
+
`clodex patch: could not remove temporary candidate directory: ${err instanceof Error ? err.message : String(err)}
|
|
14462
|
+
`
|
|
14463
|
+
);
|
|
14464
|
+
}
|
|
14465
|
+
}
|
|
14466
|
+
}
|
|
13799
14467
|
}
|
|
13800
14468
|
if (opts.trace) {
|
|
13801
14469
|
process.stderr.write(`${summarizePatchResults(results).join("\n")}
|
|
@@ -13805,9 +14473,10 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13805
14473
|
binaryPath,
|
|
13806
14474
|
claudeVersion: version,
|
|
13807
14475
|
configHash,
|
|
13808
|
-
patchedSize
|
|
13809
|
-
patchedSha256
|
|
14476
|
+
patchedSize,
|
|
14477
|
+
patchedSha256,
|
|
13810
14478
|
backupPath: backup,
|
|
14479
|
+
pristineSha256,
|
|
13811
14480
|
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13812
14481
|
};
|
|
13813
14482
|
writePatchManifest(manifest);
|
|
@@ -13820,28 +14489,54 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13820
14489
|
detailLines: summarizePatchResults(results)
|
|
13821
14490
|
};
|
|
13822
14491
|
}
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
p11.log.error("claude binary not found. Install Claude Code or set TWEAKCC_CC_INSTALLATION_PATH.");
|
|
14492
|
+
function runRestoreCommand(target) {
|
|
14493
|
+
if (!target.ok && target.reason === "binary-not-found") {
|
|
14494
|
+
p11.log.error(describePatchTargetFailure(target));
|
|
13827
14495
|
return 1;
|
|
13828
14496
|
}
|
|
13829
|
-
const
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
return
|
|
14497
|
+
const binaryPath = target.binaryPath;
|
|
14498
|
+
const manifest = readPatchManifest();
|
|
14499
|
+
let version;
|
|
14500
|
+
if (target.ok) {
|
|
14501
|
+
version = target.version;
|
|
14502
|
+
} else if (manifest?.binaryPath === binaryPath && manifest.claudeVersion) {
|
|
14503
|
+
version = manifest.claudeVersion;
|
|
14504
|
+
p11.log.warn(
|
|
14505
|
+
`Could not read the version of ${binaryPath} (\`claude --version\` failed) \u2014 using claude ${version} from the patch manifest, which recorded it when this binary was patched. This is expected when a bad patch left the install unable to run.`
|
|
14506
|
+
);
|
|
14507
|
+
} else {
|
|
14508
|
+
p11.log.error(
|
|
14509
|
+
`Could not determine the version of ${binaryPath} (\`claude --version\` failed), and no patch manifest records a pristine backup for it, so clodex cannot tell which backup belongs to this install. Restore it by hand from ${backupDir()} (or reinstall Claude Code).`
|
|
14510
|
+
);
|
|
14511
|
+
return 1;
|
|
14512
|
+
}
|
|
14513
|
+
const plan = planRestoreOnly(collectPristineFacts({ version, binaryPath, manifest }));
|
|
14514
|
+
if (plan.action === "error") {
|
|
14515
|
+
p11.log.error(plan.message);
|
|
14516
|
+
return 1;
|
|
14517
|
+
}
|
|
14518
|
+
for (const note of plan.notes) p11.log.warn(note);
|
|
14519
|
+
const verified = verifyPristineSource(plan, version);
|
|
14520
|
+
if (!verified.ok) {
|
|
14521
|
+
p11.log.error(verified.message);
|
|
14522
|
+
return 1;
|
|
14523
|
+
}
|
|
14524
|
+
copyFileSync(plan.backupPath, binaryPath);
|
|
14525
|
+
try {
|
|
14526
|
+
unlinkSync4(getPatchManifestPath());
|
|
14527
|
+
} catch {
|
|
14528
|
+
}
|
|
14529
|
+
p11.log.success(`Restored pristine claude ${version} from ${plan.backupPath}.`);
|
|
14530
|
+
return 0;
|
|
14531
|
+
}
|
|
14532
|
+
async function runPatchCommand(opts = {}) {
|
|
14533
|
+
const target = resolveClaudeBinaryForPatch();
|
|
14534
|
+
if (opts.restore) return runRestoreCommand(target);
|
|
14535
|
+
if (!target.ok) {
|
|
14536
|
+
p11.log.error(describePatchTargetFailure(target));
|
|
14537
|
+
return 1;
|
|
13844
14538
|
}
|
|
14539
|
+
const { binaryPath, version } = target;
|
|
13845
14540
|
const desired = buildDesiredPatchConfig();
|
|
13846
14541
|
if (Object.keys(desired.config).length === 0) {
|
|
13847
14542
|
p11.log.error("No favorite models to patch. Save favorites with `clodex models` first.");
|
|
@@ -13850,13 +14545,14 @@ async function runPatchCommand(opts = {}) {
|
|
|
13850
14545
|
for (const id of desired.unknownWindows) {
|
|
13851
14546
|
p11.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
|
|
13852
14547
|
}
|
|
14548
|
+
reportRejectedModelAliases(desired.rejectedAliasRejections);
|
|
13853
14549
|
const configHash = computePatchConfigHash(desired.config);
|
|
13854
14550
|
const manifest = readPatchManifest();
|
|
13855
14551
|
const state = evaluatePatchState(manifest, {
|
|
13856
14552
|
binaryPath,
|
|
13857
14553
|
claudeVersion: version,
|
|
13858
14554
|
configHash,
|
|
13859
|
-
binarySize:
|
|
14555
|
+
binarySize: statSync4(binaryPath).size
|
|
13860
14556
|
});
|
|
13861
14557
|
if (state === "current") {
|
|
13862
14558
|
p11.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
|
|
@@ -13868,14 +14564,9 @@ async function runPatchCommand(opts = {}) {
|
|
|
13868
14564
|
return 1;
|
|
13869
14565
|
}
|
|
13870
14566
|
try {
|
|
13871
|
-
const backup = pristineBackupPath(version, binaryPath);
|
|
13872
|
-
const restoreFirst = existsSync6(backup) && sha256File(backup) !== sha256File(binaryPath);
|
|
13873
|
-
if (restoreFirst) {
|
|
13874
|
-
p11.log.info("Binary differs from its pristine backup \u2014 restoring it before patching fresh.");
|
|
13875
|
-
}
|
|
13876
14567
|
const outcome = await applyPatch(binaryPath, version, desired, configHash, {
|
|
13877
14568
|
trace: opts.trace ?? false,
|
|
13878
|
-
|
|
14569
|
+
manifest
|
|
13879
14570
|
});
|
|
13880
14571
|
if (!outcome.ok) {
|
|
13881
14572
|
p11.log.error(outcome.message);
|
|
@@ -13895,15 +14586,21 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
13895
14586
|
try {
|
|
13896
14587
|
const desired = buildDesiredPatchConfig();
|
|
13897
14588
|
if (Object.keys(desired.config).length === 0) return;
|
|
13898
|
-
const
|
|
13899
|
-
if (!
|
|
14589
|
+
const target = resolveClaudeBinaryForPatch();
|
|
14590
|
+
if (!target.ok) {
|
|
14591
|
+
if (target.reason === "version-unknown" && !opts.agentStdout) {
|
|
14592
|
+
console.error(pc12.dim(`clodex: ${describePatchTargetFailure(target)}`));
|
|
14593
|
+
}
|
|
14594
|
+
return;
|
|
14595
|
+
}
|
|
14596
|
+
const resolved = target;
|
|
13900
14597
|
const configHash = computePatchConfigHash(desired.config);
|
|
13901
14598
|
const manifest = readPatchManifest();
|
|
13902
14599
|
const state = evaluatePatchState(manifest, {
|
|
13903
14600
|
binaryPath: resolved.binaryPath,
|
|
13904
14601
|
claudeVersion: resolved.version,
|
|
13905
14602
|
configHash,
|
|
13906
|
-
binarySize:
|
|
14603
|
+
binarySize: statSync4(resolved.binaryPath).size
|
|
13907
14604
|
});
|
|
13908
14605
|
if (state === "current") return;
|
|
13909
14606
|
const interactive = !opts.dryRun && !opts.agentStdout && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -14343,6 +15040,7 @@ ${pc13.bold("Behavior:")}
|
|
|
14343
15040
|
proxy mode, without opening the interactive manager.
|
|
14344
15041
|
--alias <name=target> saves a short name for a proxy-mode favorite. The
|
|
14345
15042
|
target is clodex:<provider-id>:<model-id> (the clodex: prefix is optional).
|
|
15043
|
+
Alias names are stored lowercase and cannot use client-reserved model names.
|
|
14346
15044
|
--unalias <name> removes a saved short name.
|
|
14347
15045
|
|
|
14348
15046
|
${pc13.bold("How it works:")}
|
|
@@ -14384,7 +15082,19 @@ function printHelp(text4) {
|
|
|
14384
15082
|
${text4}
|
|
14385
15083
|
`);
|
|
14386
15084
|
}
|
|
15085
|
+
function reportInactiveCatalogAliases(modelAliases) {
|
|
15086
|
+
const unavailableAliases = modelAliases.filter((alias) => alias.unavailableReason !== void 0);
|
|
15087
|
+
if (unavailableAliases.length === 0) return;
|
|
15088
|
+
const warningLines = unavailableAliases.flatMap((alias) => alias.sourceNames?.length ? alias.sourceNames.map((name) => ` ${JSON.stringify(name)} \u2014 ${alias.unavailableReason}`) : [
|
|
15089
|
+
` ${JSON.stringify(alias.savedName ?? alias.name)} \u2014 ${alias.unavailableReason}`
|
|
15090
|
+
]);
|
|
15091
|
+
p12.log.warn(
|
|
15092
|
+
`${warningLines.length} saved model alias${warningLines.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
|
|
15093
|
+
` + warningLines.join("\n")
|
|
15094
|
+
);
|
|
15095
|
+
}
|
|
14387
15096
|
async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases, contextWindow, trace, claudeArgs) {
|
|
15097
|
+
reportInactiveCatalogAliases(modelAliases);
|
|
14388
15098
|
let proxyHandle;
|
|
14389
15099
|
try {
|
|
14390
15100
|
proxyHandle = await startProxyCatalog(
|
|
@@ -14444,27 +15154,36 @@ async function runModelsCommand(opts = {}) {
|
|
|
14444
15154
|
p12.log.info("Add it with `clodex models`, then save the alias.");
|
|
14445
15155
|
return 1;
|
|
14446
15156
|
}
|
|
14447
|
-
|
|
15157
|
+
if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
|
|
15158
|
+
p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
15159
|
+
return 1;
|
|
15160
|
+
}
|
|
15161
|
+
const aliases = prefs2.modelAliases ?? [];
|
|
15162
|
+
const modelAliases = aliases.filter((alias) => !modelAliasMatchesName(alias, parsed.name));
|
|
14448
15163
|
modelAliases.push(parsed);
|
|
14449
15164
|
savePreferences({ modelAliases });
|
|
14450
15165
|
p12.log.success(`Saved model alias ${parsed.name} \u2192 ${modelAliasTarget(parsed)}.`);
|
|
14451
15166
|
return 0;
|
|
14452
15167
|
}
|
|
14453
15168
|
if (opts.unalias !== void 0) {
|
|
14454
|
-
const
|
|
14455
|
-
|
|
14456
|
-
|
|
15169
|
+
const requestedName = opts.unalias.trim();
|
|
15170
|
+
const name = canonicalModelAliasName(requestedName);
|
|
15171
|
+
const prefs2 = loadPreferences();
|
|
15172
|
+
if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
|
|
15173
|
+
p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
14457
15174
|
return 1;
|
|
14458
15175
|
}
|
|
14459
|
-
const prefs2 = loadPreferences();
|
|
14460
15176
|
const aliases = prefs2.modelAliases ?? [];
|
|
14461
|
-
const modelAliases = aliases.filter((alias) => alias
|
|
14462
|
-
|
|
14463
|
-
|
|
15177
|
+
const modelAliases = aliases.filter((alias) => !modelAliasMatchesStoredName(alias, requestedName));
|
|
15178
|
+
const removedCount = aliases.length - modelAliases.length;
|
|
15179
|
+
if (removedCount === 0) {
|
|
15180
|
+
p12.log.error(`No model alias named ${JSON.stringify(requestedName)} is saved.`);
|
|
14464
15181
|
return 1;
|
|
14465
15182
|
}
|
|
14466
15183
|
savePreferences({ modelAliases });
|
|
14467
|
-
p12.log.success(
|
|
15184
|
+
p12.log.success(
|
|
15185
|
+
removedCount === 1 ? `Removed model alias ${name || JSON.stringify(requestedName)}.` : `Removed ${removedCount} model aliases named ${name || JSON.stringify(requestedName)}.`
|
|
15186
|
+
);
|
|
14468
15187
|
return 0;
|
|
14469
15188
|
}
|
|
14470
15189
|
if (opts.list) {
|
|
@@ -14948,7 +15667,11 @@ Error: ${launchPlan.error}
|
|
|
14948
15667
|
return launchClaudeViaCatalog(
|
|
14949
15668
|
catalogRoutes,
|
|
14950
15669
|
startingRoute,
|
|
14951
|
-
resolveCatalogModelAliases(
|
|
15670
|
+
resolveCatalogModelAliases(
|
|
15671
|
+
prefs.modelAliases,
|
|
15672
|
+
resolveRoute,
|
|
15673
|
+
catalogRoutes
|
|
15674
|
+
),
|
|
14952
15675
|
selectedModel.contextWindow,
|
|
14953
15676
|
trace,
|
|
14954
15677
|
claudeArgs
|
|
@@ -15195,6 +15918,7 @@ export {
|
|
|
15195
15918
|
modelsHelpText,
|
|
15196
15919
|
parseArgs,
|
|
15197
15920
|
patchHelpText,
|
|
15921
|
+
reportInactiveCatalogAliases,
|
|
15198
15922
|
rootHelpText,
|
|
15199
15923
|
runClaudeCommand,
|
|
15200
15924
|
runModelsCommand,
|