@bman654/clodex 2.1.4 → 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 +847 -131
- 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
|
|
@@ -10256,6 +10429,17 @@ function aliasModelId(realId, providerId) {
|
|
|
10256
10429
|
function lookupRoute(byAlias, id) {
|
|
10257
10430
|
return byAlias.get(normalizeRouteLookupId(id));
|
|
10258
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
|
+
}
|
|
10259
10443
|
async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
|
|
10260
10444
|
const proxyToken = randomUUID4();
|
|
10261
10445
|
silenceSdkWarnings();
|
|
@@ -10264,9 +10448,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10264
10448
|
}
|
|
10265
10449
|
const byAlias = new Map(routes.map((r) => [normalizeRouteLookupId(r.aliasId), r]));
|
|
10266
10450
|
const configuredAliasNames = new Set(
|
|
10267
|
-
(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]))
|
|
10268
10455
|
);
|
|
10269
10456
|
for (const alias of modelAliases ?? []) {
|
|
10457
|
+
if (alias.routeId === void 0 || alias.unavailableReason !== void 0) continue;
|
|
10270
10458
|
const route = lookupRoute(byAlias, alias.routeId);
|
|
10271
10459
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
10272
10460
|
if (route && !byAlias.has(aliasId)) byAlias.set(aliasId, route);
|
|
@@ -10341,7 +10529,14 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10341
10529
|
const resolvedRoute = typeof originalModel === "string" ? lookupRoute(byAlias, originalModel) : void 0;
|
|
10342
10530
|
const configuredModelUnavailable = typeof originalModel === "string" && (normalizeRouteLookupId(originalModel).startsWith("clodex:") || configuredAliasNames.has(normalizeRouteLookupId(originalModel)));
|
|
10343
10531
|
if (!resolvedRoute && configuredModelUnavailable) {
|
|
10344
|
-
anthropicError(
|
|
10532
|
+
anthropicError(
|
|
10533
|
+
res,
|
|
10534
|
+
400,
|
|
10535
|
+
routeUnavailableMessage(
|
|
10536
|
+
originalModel,
|
|
10537
|
+
unavailableAliasReasons.get(normalizeRouteLookupId(originalModel))
|
|
10538
|
+
)
|
|
10539
|
+
);
|
|
10345
10540
|
return;
|
|
10346
10541
|
}
|
|
10347
10542
|
const route = resolvedRoute ?? defaultRoute;
|
|
@@ -12382,6 +12577,10 @@ async function startHttpProxy(options) {
|
|
|
12382
12577
|
for (const alias of options.modelAliases ?? []) {
|
|
12383
12578
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
12384
12579
|
reservedModelIds.add(aliasId);
|
|
12580
|
+
for (const sourceName of alias.sourceNames ?? []) {
|
|
12581
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName));
|
|
12582
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName.trim()));
|
|
12583
|
+
}
|
|
12385
12584
|
const route = routesById.get(normalizeRouteLookupId(alias.routeId));
|
|
12386
12585
|
if (!route) continue;
|
|
12387
12586
|
routesById.set(aliasId, route);
|
|
@@ -12632,13 +12831,17 @@ async function startHttpProxy(options) {
|
|
|
12632
12831
|
async function loadHttpProxyRoutes() {
|
|
12633
12832
|
const prefs = loadPreferences();
|
|
12634
12833
|
const favorites = prefs.favoriteModels ?? [];
|
|
12834
|
+
const normalizedAliases = normalizeModelAliases(prefs.modelAliases);
|
|
12635
12835
|
if (favorites.length === 0) {
|
|
12636
12836
|
return {
|
|
12637
12837
|
routes: [],
|
|
12638
12838
|
unavailable: [],
|
|
12639
12839
|
unsupported: [],
|
|
12640
12840
|
aliases: [],
|
|
12641
|
-
unavailableAliases:
|
|
12841
|
+
unavailableAliases: [
|
|
12842
|
+
...normalizedAliases.rejected,
|
|
12843
|
+
...normalizedAliases.accepted.flatMap(({ sources }) => sources)
|
|
12844
|
+
],
|
|
12642
12845
|
favoriteCount: 0
|
|
12643
12846
|
};
|
|
12644
12847
|
}
|
|
@@ -12648,7 +12851,7 @@ async function loadHttpProxyRoutes() {
|
|
|
12648
12851
|
apiKey: await resolveLocalProviderApiKey(provider) ?? ""
|
|
12649
12852
|
})));
|
|
12650
12853
|
return {
|
|
12651
|
-
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases
|
|
12854
|
+
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases),
|
|
12652
12855
|
favoriteCount: favorites.length
|
|
12653
12856
|
};
|
|
12654
12857
|
}
|
|
@@ -12682,8 +12885,16 @@ function reportSkippedHttpProxyFavorites(loaded) {
|
|
|
12682
12885
|
);
|
|
12683
12886
|
}
|
|
12684
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
|
+
);
|
|
12685
12895
|
p8.log.warn(
|
|
12686
|
-
`${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")
|
|
12687
12898
|
);
|
|
12688
12899
|
}
|
|
12689
12900
|
}
|
|
@@ -12693,7 +12904,14 @@ function buildConfiguredHttpProxyOptions(loaded, port, debug = false, inferenceL
|
|
|
12693
12904
|
port,
|
|
12694
12905
|
routes: loaded.routes,
|
|
12695
12906
|
modelAliases: loaded.aliases,
|
|
12696
|
-
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
|
+
}))],
|
|
12697
12915
|
debug,
|
|
12698
12916
|
debugLogPath,
|
|
12699
12917
|
inferenceLogPath,
|
|
@@ -13104,7 +13322,28 @@ async function runServerCommand(options = {}) {
|
|
|
13104
13322
|
return 1;
|
|
13105
13323
|
}
|
|
13106
13324
|
const gateway = runConfig.maskGatewayIds ? { maskGatewayIds: true } : void 0;
|
|
13107
|
-
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
|
+
}
|
|
13108
13347
|
const inferenceLogPath = getInferenceRequestLogPath();
|
|
13109
13348
|
const webSocketDiagnosticsLogPath = options.wsDiagnostics ? getSessionLogPath("server-websocket-diagnostics", "jsonl") : void 0;
|
|
13110
13349
|
const server = await startServer({
|
|
@@ -13416,25 +13655,192 @@ function planLaunchWizard(opts) {
|
|
|
13416
13655
|
}
|
|
13417
13656
|
|
|
13418
13657
|
// src/patcher.ts
|
|
13419
|
-
import { createHash as
|
|
13658
|
+
import { createHash as createHash8 } from "crypto";
|
|
13420
13659
|
import {
|
|
13421
13660
|
copyFileSync,
|
|
13422
|
-
existsSync as
|
|
13661
|
+
existsSync as existsSync7,
|
|
13662
|
+
mkdtempSync,
|
|
13423
13663
|
mkdirSync as mkdirSync7,
|
|
13424
|
-
readFileSync as
|
|
13425
|
-
|
|
13664
|
+
readFileSync as readFileSync9,
|
|
13665
|
+
renameSync as renameSync3,
|
|
13666
|
+
rmSync,
|
|
13667
|
+
statSync as statSync4,
|
|
13426
13668
|
unlinkSync as unlinkSync4,
|
|
13427
13669
|
writeFileSync as writeFileSync7,
|
|
13428
13670
|
openSync as openSync4,
|
|
13429
13671
|
closeSync as closeSync4,
|
|
13430
13672
|
realpathSync
|
|
13431
13673
|
} from "fs";
|
|
13432
|
-
import { homedir as
|
|
13433
|
-
import { basename, join as
|
|
13674
|
+
import { homedir as homedir3 } from "os";
|
|
13675
|
+
import { basename, dirname as dirname5, join as join8 } from "path";
|
|
13434
13676
|
import pc12 from "picocolors";
|
|
13435
13677
|
import * as p11 from "@clack/prompts";
|
|
13436
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
|
+
|
|
13437
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
|
+
}
|
|
13438
13844
|
var PatchApplyError = class extends Error {
|
|
13439
13845
|
results;
|
|
13440
13846
|
constructor(message, results) {
|
|
@@ -13449,21 +13855,37 @@ function formatPatchSiteLine(result) {
|
|
|
13449
13855
|
function applyClodexPatches(source, config) {
|
|
13450
13856
|
let js = source;
|
|
13451
13857
|
const MODEL_CONFIG = config;
|
|
13452
|
-
const ALIAS_TO_ID =
|
|
13858
|
+
const ALIAS_TO_ID = /* @__PURE__ */ Object.create(null);
|
|
13453
13859
|
const IDENTITIES = [];
|
|
13454
|
-
const DISPLAY_BY_IDENTITY =
|
|
13455
|
-
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);
|
|
13456
13864
|
const report = [];
|
|
13457
13865
|
const fail = (message) => {
|
|
13458
13866
|
throw new PatchApplyError(message, report);
|
|
13459
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
|
+
};
|
|
13460
13878
|
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
13461
13879
|
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
13462
13880
|
if (spec.alias !== void 0) {
|
|
13463
|
-
const
|
|
13881
|
+
const rawAlias = String(spec.alias).trim();
|
|
13882
|
+
const a = rawAlias.toLowerCase();
|
|
13464
13883
|
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
13465
13884
|
fail('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
13466
13885
|
}
|
|
13886
|
+
if (isReservedModelAlias(a)) {
|
|
13887
|
+
fail('clodex patch: reserved alias "' + a + '" cannot be reassigned');
|
|
13888
|
+
}
|
|
13467
13889
|
ALIAS_TO_ID[a] = String(id);
|
|
13468
13890
|
IDENTITIES.push(a);
|
|
13469
13891
|
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
@@ -13471,6 +13893,10 @@ function applyClodexPatches(source, config) {
|
|
|
13471
13893
|
IDENTITIES.push(String(id));
|
|
13472
13894
|
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
13473
13895
|
}
|
|
13896
|
+
if (spec.alias !== void 0) {
|
|
13897
|
+
registerCapabilityKeys(String(spec.alias));
|
|
13898
|
+
}
|
|
13899
|
+
registerCapabilityKeys(String(id));
|
|
13474
13900
|
if (spec.context !== void 0) {
|
|
13475
13901
|
const n = Number(spec.context);
|
|
13476
13902
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -13484,6 +13910,22 @@ function applyClodexPatches(source, config) {
|
|
|
13484
13910
|
if (spec.alias !== void 0) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
13485
13911
|
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
13486
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
|
+
}
|
|
13487
13929
|
}
|
|
13488
13930
|
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
13489
13931
|
const MODELS = Object.keys(MODEL_CONFIG);
|
|
@@ -13611,19 +14053,91 @@ function applyClodexPatches(source, config) {
|
|
|
13611
14053
|
);
|
|
13612
14054
|
}
|
|
13613
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
|
+
}
|
|
13614
14128
|
return { content: js, results: report };
|
|
13615
14129
|
}
|
|
13616
14130
|
|
|
13617
14131
|
// src/patcher.ts
|
|
13618
14132
|
function getPatchManifestPath() {
|
|
13619
|
-
return
|
|
14133
|
+
return join8(getAppHome(), "patch-state.json");
|
|
13620
14134
|
}
|
|
13621
14135
|
function getPatchLockPath() {
|
|
13622
|
-
return
|
|
14136
|
+
return join8(getAppHome(), "patch.lock");
|
|
13623
14137
|
}
|
|
13624
14138
|
function readPatchManifest(path = getPatchManifestPath()) {
|
|
13625
14139
|
try {
|
|
13626
|
-
const parsed = JSON.parse(
|
|
14140
|
+
const parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
13627
14141
|
if (parsed && typeof parsed.binaryPath === "string" && typeof parsed.configHash === "string") {
|
|
13628
14142
|
return parsed;
|
|
13629
14143
|
}
|
|
@@ -13639,7 +14153,20 @@ function writePatchManifest(manifest, path = getPatchManifestPath()) {
|
|
|
13639
14153
|
function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
13640
14154
|
const config = {};
|
|
13641
14155
|
const unknownWindows = [];
|
|
13642
|
-
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
|
+
);
|
|
13643
14170
|
for (const favorite of favorites) {
|
|
13644
14171
|
const id = stripOneMContextSuffix(httpProxyModelId(favorite.providerId, favorite.modelId));
|
|
13645
14172
|
if (config[id]) continue;
|
|
@@ -13652,29 +14179,68 @@ function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
|
13652
14179
|
else if (context !== 2e5) entry.context = context;
|
|
13653
14180
|
const display = meta?.displayName?.trim();
|
|
13654
14181
|
if (display) entry.display = display;
|
|
14182
|
+
const effort = projectNativeEffort(meta?.effort);
|
|
14183
|
+
if (effort) entry.effort = effort;
|
|
13655
14184
|
config[id] = entry;
|
|
13656
14185
|
}
|
|
13657
|
-
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
|
+
};
|
|
13658
14198
|
}
|
|
13659
|
-
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) {
|
|
13660
14207
|
const canonical = Object.keys(config).sort().map((key) => {
|
|
13661
14208
|
const entry = config[key];
|
|
13662
|
-
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
|
+
];
|
|
13663
14217
|
});
|
|
13664
|
-
return
|
|
14218
|
+
return createHash8("sha256").update(JSON.stringify([transformsVersion, canonical])).digest("hex");
|
|
13665
14219
|
}
|
|
13666
14220
|
function buildDesiredPatchConfig() {
|
|
13667
14221
|
const prefs = loadPreferences();
|
|
13668
14222
|
const favorites = prefs.favoriteModels ?? [];
|
|
13669
|
-
const aliases = prefs.modelAliases
|
|
14223
|
+
const aliases = prefs.modelAliases;
|
|
13670
14224
|
const registry = loadRegistry();
|
|
13671
14225
|
const meta = /* @__PURE__ */ new Map();
|
|
13672
14226
|
for (const provider of registry.providers) {
|
|
13673
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
|
+
});
|
|
13674
14239
|
meta.set(`${provider.id}:${model.id}`, {
|
|
13675
14240
|
contextWindow: model.contextWindow && model.contextWindow > 0 ? model.contextWindow : void 0,
|
|
13676
14241
|
// Same label `clodex server` prints at startup and `models --list` shows.
|
|
13677
|
-
displayName: httpProxyDisplayName(model, provider.name)
|
|
14242
|
+
displayName: httpProxyDisplayName(model, provider.name),
|
|
14243
|
+
effort: effort.mode === "controllable" ? { levels: effort.levels, defaultLevel: effort.defaultLevel } : void 0
|
|
13678
14244
|
});
|
|
13679
14245
|
}
|
|
13680
14246
|
}
|
|
@@ -13704,7 +14270,7 @@ function pidIsAlive(pid) {
|
|
|
13704
14270
|
function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
13705
14271
|
const now = opts.now ?? Date.now();
|
|
13706
14272
|
const isAlive = opts.isAlive ?? pidIsAlive;
|
|
13707
|
-
mkdirSync7(
|
|
14273
|
+
mkdirSync7(join8(lockPath, ".."), { recursive: true, mode: 448 });
|
|
13708
14274
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13709
14275
|
try {
|
|
13710
14276
|
const fd = openSync4(lockPath, "wx");
|
|
@@ -13720,7 +14286,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13720
14286
|
} catch {
|
|
13721
14287
|
let stale = false;
|
|
13722
14288
|
try {
|
|
13723
|
-
const existing = JSON.parse(
|
|
14289
|
+
const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
13724
14290
|
stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
|
|
13725
14291
|
} catch {
|
|
13726
14292
|
stale = true;
|
|
@@ -13734,33 +14300,28 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13734
14300
|
}
|
|
13735
14301
|
return null;
|
|
13736
14302
|
}
|
|
13737
|
-
function sha256File(path) {
|
|
13738
|
-
return createHash7("sha256").update(readFileSync8(path)).digest("hex");
|
|
13739
|
-
}
|
|
13740
14303
|
function resolveClaudeBinaryForPatch() {
|
|
13741
14304
|
const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"];
|
|
13742
|
-
const nativeSymlink =
|
|
13743
|
-
const source = envOverride?.trim() || (
|
|
13744
|
-
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" };
|
|
13745
14308
|
let resolved;
|
|
13746
14309
|
try {
|
|
13747
14310
|
resolved = realpathSync(source);
|
|
13748
14311
|
} catch {
|
|
13749
|
-
return
|
|
14312
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13750
14313
|
}
|
|
13751
14314
|
try {
|
|
13752
|
-
if (!
|
|
14315
|
+
if (!statSync4(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
|
|
13753
14316
|
} catch {
|
|
13754
|
-
return
|
|
14317
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13755
14318
|
}
|
|
13756
|
-
|
|
14319
|
+
const version = getClaudeVersionForBinary(resolved);
|
|
14320
|
+
if (!version) return { ok: false, reason: "version-unknown", binaryPath: resolved };
|
|
14321
|
+
return { ok: true, binaryPath: resolved, version };
|
|
13757
14322
|
}
|
|
13758
|
-
function
|
|
13759
|
-
return
|
|
13760
|
-
}
|
|
13761
|
-
function pristineBackupPath(version, binaryPath) {
|
|
13762
|
-
const tag = version.replace(/[^\w.-]+/g, "_") || basename(binaryPath);
|
|
13763
|
-
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.`;
|
|
13764
14325
|
}
|
|
13765
14326
|
function summarizePatchResults(results) {
|
|
13766
14327
|
const lines = results.map(formatPatchSiteLine);
|
|
@@ -13773,26 +14334,112 @@ function summarizePatchResults(results) {
|
|
|
13773
14334
|
}
|
|
13774
14335
|
return lines;
|
|
13775
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
|
+
}
|
|
13776
14367
|
async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
13777
|
-
|
|
13778
|
-
mkdirSync7(backupDir(), { recursive: true });
|
|
13779
|
-
if (opts.restoreFirst) {
|
|
13780
|
-
if (!existsSync6(backup)) {
|
|
13781
|
-
return { ok: false, message: `Cannot re-patch: pristine backup missing at ${backup}. Reinstall claude, then run clodex patch.` };
|
|
13782
|
-
}
|
|
13783
|
-
copyFileSync(backup, binaryPath);
|
|
13784
|
-
} else if (!existsSync6(backup)) {
|
|
13785
|
-
copyFileSync(binaryPath, backup);
|
|
13786
|
-
}
|
|
13787
|
-
copyFileSync(backup, join7(backupDir(), "native-binary.backup"));
|
|
13788
|
-
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
14368
|
+
let candidateDir;
|
|
13789
14369
|
let results;
|
|
14370
|
+
let patchedSize;
|
|
14371
|
+
let patchedSha256;
|
|
14372
|
+
let backup;
|
|
14373
|
+
let pristineSha256;
|
|
13790
14374
|
try {
|
|
13791
|
-
|
|
13792
|
-
const
|
|
13793
|
-
|
|
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);
|
|
13794
14431
|
results = patched.results;
|
|
13795
|
-
|
|
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);
|
|
13796
14443
|
} catch (err) {
|
|
13797
14444
|
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
13798
14445
|
if (opts.trace && detailLines.length) {
|
|
@@ -13804,6 +14451,19 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13804
14451
|
message: `Patch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
13805
14452
|
detailLines
|
|
13806
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
|
+
}
|
|
13807
14467
|
}
|
|
13808
14468
|
if (opts.trace) {
|
|
13809
14469
|
process.stderr.write(`${summarizePatchResults(results).join("\n")}
|
|
@@ -13813,9 +14473,10 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13813
14473
|
binaryPath,
|
|
13814
14474
|
claudeVersion: version,
|
|
13815
14475
|
configHash,
|
|
13816
|
-
patchedSize
|
|
13817
|
-
patchedSha256
|
|
14476
|
+
patchedSize,
|
|
14477
|
+
patchedSha256,
|
|
13818
14478
|
backupPath: backup,
|
|
14479
|
+
pristineSha256,
|
|
13819
14480
|
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13820
14481
|
};
|
|
13821
14482
|
writePatchManifest(manifest);
|
|
@@ -13828,28 +14489,54 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13828
14489
|
detailLines: summarizePatchResults(results)
|
|
13829
14490
|
};
|
|
13830
14491
|
}
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
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));
|
|
13835
14495
|
return 1;
|
|
13836
14496
|
}
|
|
13837
|
-
const
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
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;
|
|
13852
14538
|
}
|
|
14539
|
+
const { binaryPath, version } = target;
|
|
13853
14540
|
const desired = buildDesiredPatchConfig();
|
|
13854
14541
|
if (Object.keys(desired.config).length === 0) {
|
|
13855
14542
|
p11.log.error("No favorite models to patch. Save favorites with `clodex models` first.");
|
|
@@ -13858,13 +14545,14 @@ async function runPatchCommand(opts = {}) {
|
|
|
13858
14545
|
for (const id of desired.unknownWindows) {
|
|
13859
14546
|
p11.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
|
|
13860
14547
|
}
|
|
14548
|
+
reportRejectedModelAliases(desired.rejectedAliasRejections);
|
|
13861
14549
|
const configHash = computePatchConfigHash(desired.config);
|
|
13862
14550
|
const manifest = readPatchManifest();
|
|
13863
14551
|
const state = evaluatePatchState(manifest, {
|
|
13864
14552
|
binaryPath,
|
|
13865
14553
|
claudeVersion: version,
|
|
13866
14554
|
configHash,
|
|
13867
|
-
binarySize:
|
|
14555
|
+
binarySize: statSync4(binaryPath).size
|
|
13868
14556
|
});
|
|
13869
14557
|
if (state === "current") {
|
|
13870
14558
|
p11.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
|
|
@@ -13876,14 +14564,9 @@ async function runPatchCommand(opts = {}) {
|
|
|
13876
14564
|
return 1;
|
|
13877
14565
|
}
|
|
13878
14566
|
try {
|
|
13879
|
-
const backup = pristineBackupPath(version, binaryPath);
|
|
13880
|
-
const restoreFirst = existsSync6(backup) && sha256File(backup) !== sha256File(binaryPath);
|
|
13881
|
-
if (restoreFirst) {
|
|
13882
|
-
p11.log.info("Binary differs from its pristine backup \u2014 restoring it before patching fresh.");
|
|
13883
|
-
}
|
|
13884
14567
|
const outcome = await applyPatch(binaryPath, version, desired, configHash, {
|
|
13885
14568
|
trace: opts.trace ?? false,
|
|
13886
|
-
|
|
14569
|
+
manifest
|
|
13887
14570
|
});
|
|
13888
14571
|
if (!outcome.ok) {
|
|
13889
14572
|
p11.log.error(outcome.message);
|
|
@@ -13903,15 +14586,21 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
13903
14586
|
try {
|
|
13904
14587
|
const desired = buildDesiredPatchConfig();
|
|
13905
14588
|
if (Object.keys(desired.config).length === 0) return;
|
|
13906
|
-
const
|
|
13907
|
-
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;
|
|
13908
14597
|
const configHash = computePatchConfigHash(desired.config);
|
|
13909
14598
|
const manifest = readPatchManifest();
|
|
13910
14599
|
const state = evaluatePatchState(manifest, {
|
|
13911
14600
|
binaryPath: resolved.binaryPath,
|
|
13912
14601
|
claudeVersion: resolved.version,
|
|
13913
14602
|
configHash,
|
|
13914
|
-
binarySize:
|
|
14603
|
+
binarySize: statSync4(resolved.binaryPath).size
|
|
13915
14604
|
});
|
|
13916
14605
|
if (state === "current") return;
|
|
13917
14606
|
const interactive = !opts.dryRun && !opts.agentStdout && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -14351,6 +15040,7 @@ ${pc13.bold("Behavior:")}
|
|
|
14351
15040
|
proxy mode, without opening the interactive manager.
|
|
14352
15041
|
--alias <name=target> saves a short name for a proxy-mode favorite. The
|
|
14353
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.
|
|
14354
15044
|
--unalias <name> removes a saved short name.
|
|
14355
15045
|
|
|
14356
15046
|
${pc13.bold("How it works:")}
|
|
@@ -14392,7 +15082,19 @@ function printHelp(text4) {
|
|
|
14392
15082
|
${text4}
|
|
14393
15083
|
`);
|
|
14394
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
|
+
}
|
|
14395
15096
|
async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases, contextWindow, trace, claudeArgs) {
|
|
15097
|
+
reportInactiveCatalogAliases(modelAliases);
|
|
14396
15098
|
let proxyHandle;
|
|
14397
15099
|
try {
|
|
14398
15100
|
proxyHandle = await startProxyCatalog(
|
|
@@ -14452,27 +15154,36 @@ async function runModelsCommand(opts = {}) {
|
|
|
14452
15154
|
p12.log.info("Add it with `clodex models`, then save the alias.");
|
|
14453
15155
|
return 1;
|
|
14454
15156
|
}
|
|
14455
|
-
|
|
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));
|
|
14456
15163
|
modelAliases.push(parsed);
|
|
14457
15164
|
savePreferences({ modelAliases });
|
|
14458
15165
|
p12.log.success(`Saved model alias ${parsed.name} \u2192 ${modelAliasTarget(parsed)}.`);
|
|
14459
15166
|
return 0;
|
|
14460
15167
|
}
|
|
14461
15168
|
if (opts.unalias !== void 0) {
|
|
14462
|
-
const
|
|
14463
|
-
|
|
14464
|
-
|
|
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.');
|
|
14465
15174
|
return 1;
|
|
14466
15175
|
}
|
|
14467
|
-
const prefs2 = loadPreferences();
|
|
14468
15176
|
const aliases = prefs2.modelAliases ?? [];
|
|
14469
|
-
const modelAliases = aliases.filter((alias) => alias
|
|
14470
|
-
|
|
14471
|
-
|
|
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.`);
|
|
14472
15181
|
return 1;
|
|
14473
15182
|
}
|
|
14474
15183
|
savePreferences({ modelAliases });
|
|
14475
|
-
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
|
+
);
|
|
14476
15187
|
return 0;
|
|
14477
15188
|
}
|
|
14478
15189
|
if (opts.list) {
|
|
@@ -14956,7 +15667,11 @@ Error: ${launchPlan.error}
|
|
|
14956
15667
|
return launchClaudeViaCatalog(
|
|
14957
15668
|
catalogRoutes,
|
|
14958
15669
|
startingRoute,
|
|
14959
|
-
resolveCatalogModelAliases(
|
|
15670
|
+
resolveCatalogModelAliases(
|
|
15671
|
+
prefs.modelAliases,
|
|
15672
|
+
resolveRoute,
|
|
15673
|
+
catalogRoutes
|
|
15674
|
+
),
|
|
14960
15675
|
selectedModel.contextWindow,
|
|
14961
15676
|
trace,
|
|
14962
15677
|
claudeArgs
|
|
@@ -15203,6 +15918,7 @@ export {
|
|
|
15203
15918
|
modelsHelpText,
|
|
15204
15919
|
parseArgs,
|
|
15205
15920
|
patchHelpText,
|
|
15921
|
+
reportInactiveCatalogAliases,
|
|
15206
15922
|
rootHelpText,
|
|
15207
15923
|
runClaudeCommand,
|
|
15208
15924
|
runModelsCommand,
|