@bman654/clodex 2.1.4 → 2.1.6
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 +1016 -137
- 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.6",
|
|
220
221
|
publishConfig: {
|
|
221
222
|
access: "public"
|
|
222
223
|
},
|
|
@@ -4006,6 +4007,11 @@ function clampRetryAfterSeconds(value) {
|
|
|
4006
4007
|
}
|
|
4007
4008
|
return Math.min(Math.round(value), MAX_RETRY_AFTER_SECONDS);
|
|
4008
4009
|
}
|
|
4010
|
+
function retryAfterFromText(message) {
|
|
4011
|
+
if (typeof message !== "string") return void 0;
|
|
4012
|
+
const match = /retry after (\d+)s\b/i.exec(message);
|
|
4013
|
+
return match ? Number(match[1]) : void 0;
|
|
4014
|
+
}
|
|
4009
4015
|
function numericRetryAfterSeconds(inner) {
|
|
4010
4016
|
const data = inner.data;
|
|
4011
4017
|
const fromBody = data?.error?.retry_after_seconds;
|
|
@@ -4013,12 +4019,84 @@ function numericRetryAfterSeconds(inner) {
|
|
|
4013
4019
|
const fromHeader = inner.responseHeaders?.["retry-after"];
|
|
4014
4020
|
if (typeof fromHeader === "string" && /^\d+$/.test(fromHeader.trim())) return Number(fromHeader.trim());
|
|
4015
4021
|
for (const message of [data?.error?.message, inner.message]) {
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
if (match) return Number(match[1]);
|
|
4022
|
+
const fromText = retryAfterFromText(message);
|
|
4023
|
+
if (fromText !== void 0) return fromText;
|
|
4019
4024
|
}
|
|
4020
4025
|
return void 0;
|
|
4021
4026
|
}
|
|
4027
|
+
var MAX_CLIENT_MESSAGE_CHARS = 240;
|
|
4028
|
+
var CHUNK_DISCRIMINATOR_TYPES = /* @__PURE__ */ new Set(["error", "response.failed"]);
|
|
4029
|
+
function asRecord(value) {
|
|
4030
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
4031
|
+
}
|
|
4032
|
+
function nonEmptyString(record, key) {
|
|
4033
|
+
const value = record[key];
|
|
4034
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
4035
|
+
}
|
|
4036
|
+
function errorCodeValue(record) {
|
|
4037
|
+
const value = record.code;
|
|
4038
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
4039
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
4040
|
+
}
|
|
4041
|
+
function frameStatusCode(code, discriminator) {
|
|
4042
|
+
if (code !== void 0 && /^\d{3}$/.test(code)) {
|
|
4043
|
+
const numeric = Number(code);
|
|
4044
|
+
if (numeric >= 400 && numeric <= 599) return numeric;
|
|
4045
|
+
}
|
|
4046
|
+
if (/insufficient_quota|rate_limit/.test(discriminator)) return 429;
|
|
4047
|
+
if (discriminator.includes("authentication")) return 401;
|
|
4048
|
+
if (discriminator.includes("permission")) return 403;
|
|
4049
|
+
if (discriminator.includes("not_found")) return 404;
|
|
4050
|
+
if (/invalid|bad_request|context_length/.test(discriminator)) return 400;
|
|
4051
|
+
if (discriminator.includes("overload")) return 503;
|
|
4052
|
+
if (discriminator.includes("timeout")) return 504;
|
|
4053
|
+
return 500;
|
|
4054
|
+
}
|
|
4055
|
+
function frameIsContextLengthExceeded(discriminator, message) {
|
|
4056
|
+
if (/context_length|context_window/.test(discriminator)) return true;
|
|
4057
|
+
return /context_length_exceeded|maximum context length|prompt is too long/i.test(message);
|
|
4058
|
+
}
|
|
4059
|
+
function frameIsRetryable(frame) {
|
|
4060
|
+
if (frame.transportCode !== void 0) return true;
|
|
4061
|
+
const { statusCode } = frame;
|
|
4062
|
+
return statusCode === 408 || statusCode === 409 || statusCode === 429 || statusCode >= 500;
|
|
4063
|
+
}
|
|
4064
|
+
function providerErrorFrame(err) {
|
|
4065
|
+
const outer = asRecord(err);
|
|
4066
|
+
if (!outer) return void 0;
|
|
4067
|
+
const nested = asRecord(outer.error) ?? asRecord(asRecord(outer.response)?.error);
|
|
4068
|
+
const unwrapped = !(err instanceof Error) && (typeof outer.type === "string" || Object.hasOwn(outer, "code") || Object.hasOwn(outer, "param")) ? outer : void 0;
|
|
4069
|
+
const payload = nested ?? unwrapped;
|
|
4070
|
+
if (!payload) return void 0;
|
|
4071
|
+
const rawMessage = payload.message;
|
|
4072
|
+
if (typeof rawMessage !== "string") return void 0;
|
|
4073
|
+
const message = rawMessage.trim() !== "" ? rawMessage : "Provider returned an error";
|
|
4074
|
+
const code = errorCodeValue(payload);
|
|
4075
|
+
const rawType = nonEmptyString(payload, "type");
|
|
4076
|
+
const type = rawType !== void 0 && CHUNK_DISCRIMINATOR_TYPES.has(rawType) ? void 0 : rawType;
|
|
4077
|
+
const discriminator = [code, type].filter((value) => value !== void 0).join(" ").toLowerCase();
|
|
4078
|
+
const statusCode = frameStatusCode(code, discriminator);
|
|
4079
|
+
const rawRetryAfter = statusCode === 429 ? typeof payload.retry_after_seconds === "number" && Number.isFinite(payload.retry_after_seconds) && payload.retry_after_seconds >= 0 ? payload.retry_after_seconds : retryAfterFromText(message) : void 0;
|
|
4080
|
+
let serialized;
|
|
4081
|
+
try {
|
|
4082
|
+
serialized = JSON.stringify(payload) ?? message;
|
|
4083
|
+
} catch {
|
|
4084
|
+
serialized = message;
|
|
4085
|
+
}
|
|
4086
|
+
return {
|
|
4087
|
+
message,
|
|
4088
|
+
statusCode,
|
|
4089
|
+
contextLengthExceeded: frameIsContextLengthExceeded(discriminator, message),
|
|
4090
|
+
...rawRetryAfter !== void 0 ? { retryAfterSeconds: clampRetryAfterSeconds(rawRetryAfter) } : {},
|
|
4091
|
+
...code === "websocket_transport_error" ? { transportCode: "websocket_transport_error" } : {},
|
|
4092
|
+
serialized
|
|
4093
|
+
};
|
|
4094
|
+
}
|
|
4095
|
+
function frameFromError(err) {
|
|
4096
|
+
const retry = RetryError.isInstance(err) ? err : void 0;
|
|
4097
|
+
const frame = providerErrorFrame(retry?.lastError ?? err);
|
|
4098
|
+
return frame ? { frame, attemptCount: retry?.errors.length ?? 1 } : void 0;
|
|
4099
|
+
}
|
|
4022
4100
|
function boundedTransportCode(data) {
|
|
4023
4101
|
if (!data || typeof data !== "object") return void 0;
|
|
4024
4102
|
const error = data.error;
|
|
@@ -4028,7 +4106,19 @@ function boundedTransportCode(data) {
|
|
|
4028
4106
|
function sdkUpstreamErrorDetails(err) {
|
|
4029
4107
|
const retry = RetryError.isInstance(err) ? err : void 0;
|
|
4030
4108
|
const inner = retry?.lastError ?? err;
|
|
4031
|
-
if (!APICallError.isInstance(inner))
|
|
4109
|
+
if (!APICallError.isInstance(inner)) {
|
|
4110
|
+
const recovered = frameFromError(err);
|
|
4111
|
+
if (!recovered) return void 0;
|
|
4112
|
+
const { frame, attemptCount } = recovered;
|
|
4113
|
+
return {
|
|
4114
|
+
statusCode: frame.statusCode,
|
|
4115
|
+
errorContent: frame.serialized,
|
|
4116
|
+
isRetryable: frameIsRetryable(frame),
|
|
4117
|
+
attemptCount,
|
|
4118
|
+
...frame.retryAfterSeconds !== void 0 ? { retryAfterSeconds: frame.retryAfterSeconds } : {},
|
|
4119
|
+
...frame.transportCode !== void 0 ? { transportCode: frame.transportCode } : {}
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4032
4122
|
let errorContent = inner.responseBody;
|
|
4033
4123
|
if (!errorContent && inner.data !== void 0) {
|
|
4034
4124
|
try {
|
|
@@ -4049,6 +4139,8 @@ function sdkUpstreamErrorDetails(err) {
|
|
|
4049
4139
|
};
|
|
4050
4140
|
}
|
|
4051
4141
|
function isContextLengthExceededError(err, formattedMessage = "") {
|
|
4142
|
+
const frame = frameFromError(err)?.frame;
|
|
4143
|
+
if (frame) return frame.contextLengthExceeded;
|
|
4052
4144
|
const details = sdkUpstreamErrorDetails(err);
|
|
4053
4145
|
const rec = err && typeof err === "object" ? err : void 0;
|
|
4054
4146
|
const candidates = [
|
|
@@ -4067,6 +4159,11 @@ function isContextLengthExceededError(err, formattedMessage = "") {
|
|
|
4067
4159
|
function formatUpstreamError(err) {
|
|
4068
4160
|
if (!err || typeof err !== "object") return "Upstream model request failed.";
|
|
4069
4161
|
const rec = err;
|
|
4162
|
+
const frame = frameFromError(err)?.frame;
|
|
4163
|
+
if (frame) {
|
|
4164
|
+
const short = truncateForClient(sanitizeMessage(frame.message)) || "Upstream model request failed.";
|
|
4165
|
+
return `${short} (HTTP ${frame.statusCode})`;
|
|
4166
|
+
}
|
|
4070
4167
|
if (rec.data?.error?.message) {
|
|
4071
4168
|
const short = sanitizeMessage(rec.data.error.message);
|
|
4072
4169
|
return rec.statusCode ? `${short} (HTTP ${rec.statusCode})` : short;
|
|
@@ -4105,6 +4202,8 @@ function upstreamHttpStatus(err, message) {
|
|
|
4105
4202
|
const code = err.statusCode;
|
|
4106
4203
|
if (typeof code === "number" && code >= 400 && code <= 599) return code;
|
|
4107
4204
|
}
|
|
4205
|
+
const frameStatus = frameFromError(err)?.frame.statusCode;
|
|
4206
|
+
if (frameStatus !== void 0) return frameStatus;
|
|
4108
4207
|
if (message.includes("HTTP 429") || message.includes("429")) return 429;
|
|
4109
4208
|
if (message.includes("HTTP 400")) return 400;
|
|
4110
4209
|
return 500;
|
|
@@ -4125,6 +4224,10 @@ function anthropicErrorType(status) {
|
|
|
4125
4224
|
return "api_error";
|
|
4126
4225
|
}
|
|
4127
4226
|
}
|
|
4227
|
+
function truncateForClient(message) {
|
|
4228
|
+
if (message.length <= MAX_CLIENT_MESSAGE_CHARS) return message;
|
|
4229
|
+
return `${[...message].slice(0, MAX_CLIENT_MESSAGE_CHARS - 1).join("")}\u2026`;
|
|
4230
|
+
}
|
|
4128
4231
|
function sanitizeMessage(message) {
|
|
4129
4232
|
const line = message.split("\n")[0]?.trim() ?? message;
|
|
4130
4233
|
if (line.startsWith("RetryError") || line.includes("AI_RetryError")) {
|
|
@@ -4373,6 +4476,15 @@ function responseErrorCode(event) {
|
|
|
4373
4476
|
const responseError = response?.error && typeof response.error === "object" ? response.error : void 0;
|
|
4374
4477
|
return typeof responseError?.code === "string" ? responseError.code : void 0;
|
|
4375
4478
|
}
|
|
4479
|
+
function responseErrorType(event) {
|
|
4480
|
+
if (!event || typeof event !== "object") return void 0;
|
|
4481
|
+
const record = event;
|
|
4482
|
+
const error = record.error && typeof record.error === "object" ? record.error : void 0;
|
|
4483
|
+
if (typeof error?.type === "string") return error.type;
|
|
4484
|
+
const response = record.response && typeof record.response === "object" ? record.response : void 0;
|
|
4485
|
+
const responseError = response?.error && typeof response.error === "object" ? response.error : void 0;
|
|
4486
|
+
return typeof responseError?.type === "string" ? responseError.type : void 0;
|
|
4487
|
+
}
|
|
4376
4488
|
function responseRetryAfterSeconds(event) {
|
|
4377
4489
|
if (!event || typeof event !== "object") return void 0;
|
|
4378
4490
|
const record = event;
|
|
@@ -4387,6 +4499,27 @@ function responseRetryAfterSeconds(event) {
|
|
|
4387
4499
|
}
|
|
4388
4500
|
return void 0;
|
|
4389
4501
|
}
|
|
4502
|
+
function responseErrorStatus(event) {
|
|
4503
|
+
if (!event || typeof event !== "object") return void 0;
|
|
4504
|
+
const record = event;
|
|
4505
|
+
for (const candidate of [record.status, record.error?.status]) {
|
|
4506
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599) {
|
|
4507
|
+
return candidate;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
return void 0;
|
|
4511
|
+
}
|
|
4512
|
+
function responseErrorMessage(event) {
|
|
4513
|
+
if (!event || typeof event !== "object") return void 0;
|
|
4514
|
+
const record = event;
|
|
4515
|
+
const response = record.response && typeof record.response === "object" ? record.response : void 0;
|
|
4516
|
+
for (const candidate of [record.error, response?.error, record]) {
|
|
4517
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
4518
|
+
const message = candidate.message;
|
|
4519
|
+
if (typeof message === "string" && message.trim()) return message.trim();
|
|
4520
|
+
}
|
|
4521
|
+
return void 0;
|
|
4522
|
+
}
|
|
4390
4523
|
function boundedDiagnosticIdentifier(value) {
|
|
4391
4524
|
if (typeof value !== "string") return void 0;
|
|
4392
4525
|
const normalized = value.trim();
|
|
@@ -4893,7 +5026,8 @@ function handleSocketMessage(entry, data) {
|
|
|
4893
5026
|
);
|
|
4894
5027
|
return;
|
|
4895
5028
|
}
|
|
4896
|
-
|
|
5029
|
+
const errorStatus = type === "error" && !ctx.emittedModelData ? responseErrorStatus(event) : void 0;
|
|
5030
|
+
if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry)) {
|
|
4897
5031
|
emitResponseErrorDiagnostic(entry, ctx, {
|
|
4898
5032
|
source: "response_event",
|
|
4899
5033
|
upstreamEventType: type,
|
|
@@ -4910,6 +5044,32 @@ function handleSocketMessage(entry, data) {
|
|
|
4910
5044
|
dispatchContext(replacement, ctx);
|
|
4911
5045
|
return;
|
|
4912
5046
|
}
|
|
5047
|
+
if (errorStatus !== void 0) {
|
|
5048
|
+
const statedRetryAfter = errorStatus === 429 ? responseRetryAfterSeconds(event) : void 0;
|
|
5049
|
+
const retryAfterSeconds = statedRetryAfter === void 0 ? void 0 : clampRetryAfterSeconds(statedRetryAfter);
|
|
5050
|
+
const reason = responseErrorMessage(event) ?? `OpenAI rejected the request (HTTP ${errorStatus})`;
|
|
5051
|
+
failContext(
|
|
5052
|
+
entry,
|
|
5053
|
+
ctx,
|
|
5054
|
+
retryAfterSeconds === void 0 ? reason : `${reason}; retry after ${retryAfterSeconds}s`,
|
|
5055
|
+
{
|
|
5056
|
+
source: "error_frame",
|
|
5057
|
+
// Names the failure. Without it this record — now the ONLY one for a
|
|
5058
|
+
// rejection — can carry no indication of what failed, since a bare
|
|
5059
|
+
// error frame often has no `code` at all.
|
|
5060
|
+
errorType: boundedDiagnosticIdentifier(responseErrorType(event)),
|
|
5061
|
+
// Upstream-controlled, so bounded like every other identifier in this
|
|
5062
|
+
// file's diagnostics. The connection-limit branch can pass its code raw
|
|
5063
|
+
// only because it has just been compared `===` to a known constant.
|
|
5064
|
+
errorCode: boundedDiagnosticIdentifier(errorCode),
|
|
5065
|
+
mappedStatusCode: errorStatus,
|
|
5066
|
+
...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
|
|
5067
|
+
},
|
|
5068
|
+
errorStatus,
|
|
5069
|
+
retryAfterSeconds
|
|
5070
|
+
);
|
|
5071
|
+
return;
|
|
5072
|
+
}
|
|
4913
5073
|
ctx.pendingEvents.push(event);
|
|
4914
5074
|
if (isModelDataEvent(type)) flushPending(ctx);
|
|
4915
5075
|
if (TERMINAL_EVENT_TYPES.has(type ?? "") || type === "error") {
|
|
@@ -5538,6 +5698,7 @@ async function createLanguageModel(spec) {
|
|
|
5538
5698
|
}
|
|
5539
5699
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
5540
5700
|
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
|
|
5701
|
+
var GPT_56_EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
5541
5702
|
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
5542
5703
|
var MISTRAL_EFFORT_LEVELS = ["high", "off"];
|
|
5543
5704
|
var XAI_EFFORT_LEVELS = ["none", "low", "medium", "high"];
|
|
@@ -5701,7 +5862,16 @@ function mapCodexEffortToAnthropic(effort) {
|
|
|
5701
5862
|
return void 0;
|
|
5702
5863
|
}
|
|
5703
5864
|
}
|
|
5704
|
-
function
|
|
5865
|
+
function isGpt56Model(modelId) {
|
|
5866
|
+
return /^gpt-5\.6(?:-|$)/i.test(modelId);
|
|
5867
|
+
}
|
|
5868
|
+
function isReasoningSummaryUnsupportedModel(modelId) {
|
|
5869
|
+
return /codex-spark(?:-|$)/i.test(modelId);
|
|
5870
|
+
}
|
|
5871
|
+
function mapCodexEffortToOpenAI(effort, modelId) {
|
|
5872
|
+
if (modelId && isGpt56Model(modelId) && GPT_56_EFFORT_LEVELS.includes(effort)) {
|
|
5873
|
+
return effort;
|
|
5874
|
+
}
|
|
5705
5875
|
if (effort === "xhigh") return "high";
|
|
5706
5876
|
const allowed = ["low", "medium", "high"];
|
|
5707
5877
|
return allowed.includes(effort) ? effort : void 0;
|
|
@@ -5782,7 +5952,7 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
5782
5952
|
const prefersResponses = modelPrefersResponsesApi(modelId);
|
|
5783
5953
|
if (prefersResponses || metadata?.reasoning) {
|
|
5784
5954
|
return {
|
|
5785
|
-
levels: [...OPENAI_EFFORT_LEVELS],
|
|
5955
|
+
levels: isGpt56Model(modelId) ? [...GPT_56_EFFORT_LEVELS] : [...OPENAI_EFFORT_LEVELS],
|
|
5786
5956
|
defaultLevel: "medium",
|
|
5787
5957
|
supportsSummaries: true,
|
|
5788
5958
|
mode: "controllable",
|
|
@@ -5904,6 +6074,24 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
5904
6074
|
}
|
|
5905
6075
|
return EMPTY_REASONING;
|
|
5906
6076
|
}
|
|
6077
|
+
function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
6078
|
+
if (metadata?.reasoning === false && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
|
|
6079
|
+
return EMPTY_REASONING;
|
|
6080
|
+
}
|
|
6081
|
+
const capabilities = getReasoningCapabilities(npm, modelId, metadata);
|
|
6082
|
+
const seenProviderOptions = /* @__PURE__ */ new Set();
|
|
6083
|
+
return {
|
|
6084
|
+
...capabilities,
|
|
6085
|
+
levels: capabilities.levels.filter((level) => {
|
|
6086
|
+
const providerOptions = effortProviderOptions(npm, level, modelId, metadata);
|
|
6087
|
+
if (!providerOptions) return false;
|
|
6088
|
+
const fingerprint = JSON.stringify(providerOptions);
|
|
6089
|
+
if (seenProviderOptions.has(fingerprint)) return false;
|
|
6090
|
+
seenProviderOptions.add(fingerprint);
|
|
6091
|
+
return true;
|
|
6092
|
+
})
|
|
6093
|
+
};
|
|
6094
|
+
}
|
|
5907
6095
|
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
5908
6096
|
if (!effort) return void 0;
|
|
5909
6097
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
@@ -5915,8 +6103,9 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
5915
6103
|
}
|
|
5916
6104
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
5917
6105
|
if (!modelId || !modelPrefersResponsesApi(modelId)) return void 0;
|
|
5918
|
-
const reasoningEffort = mapCodexEffortToOpenAI(effort);
|
|
5919
|
-
|
|
6106
|
+
const reasoningEffort = mapCodexEffortToOpenAI(effort, modelId);
|
|
6107
|
+
if (!reasoningEffort) return void 0;
|
|
6108
|
+
return isReasoningSummaryUnsupportedModel(modelId) ? { openai: { reasoningEffort, reasoningSummary: null } } : { openai: { reasoningEffort } };
|
|
5920
6109
|
}
|
|
5921
6110
|
if (npm === "@ai-sdk/xai") {
|
|
5922
6111
|
if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;
|
|
@@ -8817,31 +9006,151 @@ function sendJson(res, status, body) {
|
|
|
8817
9006
|
}
|
|
8818
9007
|
|
|
8819
9008
|
// src/model-aliases.ts
|
|
8820
|
-
var MODEL_ALIAS_PATTERN = /^[
|
|
8821
|
-
|
|
8822
|
-
|
|
9009
|
+
var MODEL_ALIAS_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
9010
|
+
var RESERVED_MODEL_ALIASES = /* @__PURE__ */ new Set([
|
|
9011
|
+
"sonnet",
|
|
9012
|
+
"opus",
|
|
9013
|
+
"haiku",
|
|
9014
|
+
"fable",
|
|
9015
|
+
"best",
|
|
9016
|
+
"default",
|
|
9017
|
+
"opusplan",
|
|
9018
|
+
"inherit"
|
|
9019
|
+
]);
|
|
9020
|
+
function canonicalModelAliasName(name) {
|
|
9021
|
+
return name.trim().toLowerCase();
|
|
9022
|
+
}
|
|
9023
|
+
function isReservedModelAlias(name) {
|
|
9024
|
+
return RESERVED_MODEL_ALIASES.has(
|
|
9025
|
+
stripOneMContextSuffix(canonicalModelAliasName(name))
|
|
9026
|
+
);
|
|
9027
|
+
}
|
|
9028
|
+
function isModelAliasNameSyntax(name) {
|
|
9029
|
+
return MODEL_ALIAS_PATTERN.test(canonicalModelAliasName(name));
|
|
9030
|
+
}
|
|
9031
|
+
function modelAliasMatchesName(value, name) {
|
|
9032
|
+
if (!value || typeof value !== "object" || !("name" in value)) return false;
|
|
9033
|
+
const candidate = value.name;
|
|
9034
|
+
return typeof candidate === "string" && canonicalModelAliasName(candidate) === canonicalModelAliasName(name);
|
|
9035
|
+
}
|
|
9036
|
+
function modelAliasMatchesStoredName(value, name) {
|
|
9037
|
+
if (!value || typeof value !== "object" || !("name" in value)) return false;
|
|
9038
|
+
const candidate = value.name;
|
|
9039
|
+
if (typeof candidate !== "string") return false;
|
|
9040
|
+
const requested = name.trim();
|
|
9041
|
+
return isModelAliasNameSyntax(requested) ? canonicalModelAliasName(candidate) === canonicalModelAliasName(requested) : candidate.trim() === requested;
|
|
9042
|
+
}
|
|
9043
|
+
function describeModelAliasRejection(reason) {
|
|
9044
|
+
switch (reason) {
|
|
9045
|
+
case "invalid-name":
|
|
9046
|
+
return "invalid name";
|
|
9047
|
+
case "reserved-name":
|
|
9048
|
+
return "reserved client name";
|
|
9049
|
+
case "invalid-target":
|
|
9050
|
+
return "invalid target";
|
|
9051
|
+
case "conflicting-targets":
|
|
9052
|
+
return "conflicting targets";
|
|
9053
|
+
case "target-not-favorite":
|
|
9054
|
+
return "target is not a saved favorite";
|
|
9055
|
+
}
|
|
9056
|
+
}
|
|
9057
|
+
function normalizeModelAliases(value) {
|
|
9058
|
+
if (value === void 0) {
|
|
9059
|
+
return { aliases: [], accepted: [], rejected: [], rejections: [] };
|
|
9060
|
+
}
|
|
9061
|
+
if (!Array.isArray(value)) {
|
|
9062
|
+
throw new TypeError('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
9063
|
+
}
|
|
9064
|
+
const candidates = [];
|
|
9065
|
+
const groups = /* @__PURE__ */ new Map();
|
|
9066
|
+
for (const [index, item] of value.entries()) {
|
|
9067
|
+
if (!item || typeof item !== "object" || typeof item.name !== "string") {
|
|
9068
|
+
throw new TypeError(
|
|
9069
|
+
`Saved model aliases are malformed: "modelAliases[${index}]" must be an object with a string "name".`
|
|
9070
|
+
);
|
|
9071
|
+
}
|
|
9072
|
+
const source = item;
|
|
9073
|
+
const candidate = { source };
|
|
9074
|
+
candidates.push(candidate);
|
|
9075
|
+
if (typeof source.providerId !== "string" || typeof source.modelId !== "string") {
|
|
9076
|
+
candidate.rejectionReason = "invalid-target";
|
|
9077
|
+
continue;
|
|
9078
|
+
}
|
|
9079
|
+
const normalized = {
|
|
9080
|
+
name: canonicalModelAliasName(source.name),
|
|
9081
|
+
providerId: source.providerId,
|
|
9082
|
+
modelId: source.modelId
|
|
9083
|
+
};
|
|
9084
|
+
if (!isModelAliasNameSyntax(normalized.name)) {
|
|
9085
|
+
candidate.rejectionReason = "invalid-name";
|
|
9086
|
+
continue;
|
|
9087
|
+
}
|
|
9088
|
+
if (isReservedModelAlias(normalized.name)) {
|
|
9089
|
+
candidate.rejectionReason = "reserved-name";
|
|
9090
|
+
continue;
|
|
9091
|
+
}
|
|
9092
|
+
if (!normalized.providerId.trim() || !normalized.modelId.trim()) {
|
|
9093
|
+
candidate.rejectionReason = "invalid-target";
|
|
9094
|
+
continue;
|
|
9095
|
+
}
|
|
9096
|
+
candidate.normalized = normalized;
|
|
9097
|
+
const group = groups.get(normalized.name) ?? [];
|
|
9098
|
+
group.push(candidate);
|
|
9099
|
+
groups.set(normalized.name, group);
|
|
9100
|
+
}
|
|
9101
|
+
for (const group of groups.values()) {
|
|
9102
|
+
const targets = new Set(
|
|
9103
|
+
group.map((candidate) => `${candidate.normalized.providerId}\0${candidate.normalized.modelId}`)
|
|
9104
|
+
);
|
|
9105
|
+
if (targets.size === 1) {
|
|
9106
|
+
group[0].accepted = true;
|
|
9107
|
+
group[0].sources = group.map((candidate) => candidate.source);
|
|
9108
|
+
} else {
|
|
9109
|
+
for (const candidate of group) candidate.rejectionReason = "conflicting-targets";
|
|
9110
|
+
}
|
|
9111
|
+
}
|
|
9112
|
+
const rejections = candidates.filter((candidate) => candidate.rejectionReason !== void 0).map((candidate) => ({
|
|
9113
|
+
alias: candidate.source,
|
|
9114
|
+
reason: candidate.rejectionReason
|
|
9115
|
+
}));
|
|
9116
|
+
const accepted = candidates.filter((candidate) => candidate.accepted === true && candidate.normalized !== void 0).map((candidate) => ({
|
|
9117
|
+
alias: candidate.normalized,
|
|
9118
|
+
source: candidate.source,
|
|
9119
|
+
sources: candidate.sources ?? [candidate.source]
|
|
9120
|
+
}));
|
|
9121
|
+
return {
|
|
9122
|
+
aliases: accepted.map((entry) => entry.alias),
|
|
9123
|
+
accepted,
|
|
9124
|
+
rejected: rejections.map((rejection) => rejection.alias),
|
|
9125
|
+
rejections
|
|
9126
|
+
};
|
|
8823
9127
|
}
|
|
8824
9128
|
function parseModelAliasAssignment(value) {
|
|
8825
9129
|
const separator = value.indexOf("=");
|
|
8826
9130
|
if (separator < 1 || separator === value.length - 1) {
|
|
8827
9131
|
return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
|
|
8828
9132
|
}
|
|
8829
|
-
const name = value.slice(0, separator)
|
|
8830
|
-
if (!
|
|
9133
|
+
const name = canonicalModelAliasName(value.slice(0, separator));
|
|
9134
|
+
if (!MODEL_ALIAS_PATTERN.test(name)) {
|
|
8831
9135
|
return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
|
|
8832
9136
|
}
|
|
9137
|
+
if (isReservedModelAlias(name)) {
|
|
9138
|
+
return { error: "That alias name is reserved by the client." };
|
|
9139
|
+
}
|
|
8833
9140
|
const rawTarget = value.slice(separator + 1).trim();
|
|
8834
9141
|
const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
|
|
8835
9142
|
const targetSeparator = target.indexOf(":");
|
|
8836
|
-
|
|
9143
|
+
const providerId = target.slice(0, targetSeparator).trim();
|
|
9144
|
+
const modelId = stripOneMContextSuffix(target.slice(targetSeparator + 1).trim());
|
|
9145
|
+
if (targetSeparator < 1 || targetSeparator === target.length - 1 || !providerId || !modelId) {
|
|
8837
9146
|
return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
|
|
8838
9147
|
}
|
|
8839
9148
|
return {
|
|
8840
9149
|
name,
|
|
8841
|
-
providerId
|
|
9150
|
+
providerId,
|
|
8842
9151
|
// `models --list` prints Claude's synthetic context suffix. It is a client
|
|
8843
9152
|
// routing hint, not part of the provider catalog id stored in favorites.
|
|
8844
|
-
modelId
|
|
9153
|
+
modelId
|
|
8845
9154
|
};
|
|
8846
9155
|
}
|
|
8847
9156
|
function modelAliasTarget(alias) {
|
|
@@ -8888,11 +9197,32 @@ function makeRouteResolver(localProviders) {
|
|
|
8888
9197
|
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
8889
9198
|
};
|
|
8890
9199
|
}
|
|
8891
|
-
function resolveCatalogModelAliases(modelAliases, resolveRoute) {
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
9200
|
+
function resolveCatalogModelAliases(modelAliases, resolveRoute, catalogRoutes = []) {
|
|
9201
|
+
const normalized = normalizeModelAliases(modelAliases);
|
|
9202
|
+
const catalogRouteIds = new Set(
|
|
9203
|
+
catalogRoutes.map((route) => normalizeRouteLookupId(route.aliasId))
|
|
9204
|
+
);
|
|
9205
|
+
return [
|
|
9206
|
+
...normalized.accepted.map(({ alias, source, sources }) => {
|
|
9207
|
+
const route = resolveRoute(alias.providerId, alias.modelId);
|
|
9208
|
+
const collidesWithCatalog = catalogRouteIds.has(
|
|
9209
|
+
normalizeRouteLookupId(alias.name)
|
|
9210
|
+
);
|
|
9211
|
+
const sourceNames = [...new Set(sources.map((entry) => entry.name))];
|
|
9212
|
+
return {
|
|
9213
|
+
name: alias.name,
|
|
9214
|
+
...source.name === alias.name ? {} : { savedName: source.name },
|
|
9215
|
+
...sourceNames.length === 1 && sourceNames[0] === alias.name ? {} : { sourceNames },
|
|
9216
|
+
routeId: route?.aliasId ?? modelAliasTarget(alias),
|
|
9217
|
+
...collidesWithCatalog ? { unavailableReason: "conflicts with a catalog model id" } : route ? {} : { unavailableReason: "target unavailable" }
|
|
9218
|
+
};
|
|
9219
|
+
}),
|
|
9220
|
+
...normalized.rejections.map((rejection) => ({
|
|
9221
|
+
name: canonicalModelAliasName(rejection.alias.name),
|
|
9222
|
+
...rejection.alias.name === canonicalModelAliasName(rejection.alias.name) ? {} : { savedName: rejection.alias.name },
|
|
9223
|
+
unavailableReason: describeModelAliasRejection(rejection.reason)
|
|
9224
|
+
}))
|
|
9225
|
+
];
|
|
8896
9226
|
}
|
|
8897
9227
|
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
8898
9228
|
const droppedFavorites = [];
|
|
@@ -8916,7 +9246,7 @@ function httpProxyModelId(providerId, modelId) {
|
|
|
8916
9246
|
function httpProxyDisplayName(model, providerName) {
|
|
8917
9247
|
return `${formatModelLabel(model)} (${providerName})`;
|
|
8918
9248
|
}
|
|
8919
|
-
function buildHttpProxyRoutes(providers, favorites, modelAliases =
|
|
9249
|
+
function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max = MAX_MODEL_CATALOG) {
|
|
8920
9250
|
const routes = [];
|
|
8921
9251
|
const unavailable = [];
|
|
8922
9252
|
const unsupported = [];
|
|
@@ -8954,16 +9284,21 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = [], max = MAX
|
|
|
8954
9284
|
routesByFavorite.set(`${favorite.providerId}:${favorite.modelId}`, proxyRoute);
|
|
8955
9285
|
}
|
|
8956
9286
|
const aliases = [];
|
|
8957
|
-
const
|
|
8958
|
-
const
|
|
8959
|
-
for (const alias of
|
|
9287
|
+
const normalizedAliases = normalizeModelAliases(modelAliases);
|
|
9288
|
+
const unavailableAliases = [...normalizedAliases.rejected];
|
|
9289
|
+
for (const { alias, sources } of normalizedAliases.accepted) {
|
|
8960
9290
|
const route = routesByFavorite.get(`${alias.providerId}:${alias.modelId}`);
|
|
8961
|
-
if (!
|
|
8962
|
-
unavailableAliases.push(
|
|
9291
|
+
if (!route) {
|
|
9292
|
+
unavailableAliases.push(...sources);
|
|
8963
9293
|
continue;
|
|
8964
9294
|
}
|
|
8965
|
-
|
|
8966
|
-
aliases.push({
|
|
9295
|
+
const sourceNames = [...new Set(sources.map((source) => source.name))];
|
|
9296
|
+
aliases.push({
|
|
9297
|
+
name: alias.name,
|
|
9298
|
+
routeId: route.aliasId,
|
|
9299
|
+
displayName: route.displayName,
|
|
9300
|
+
...sourceNames.length === 1 && sourceNames[0] === alias.name ? {} : { sourceNames }
|
|
9301
|
+
});
|
|
8967
9302
|
}
|
|
8968
9303
|
return { routes, unavailable, unsupported, aliases, unavailableAliases };
|
|
8969
9304
|
}
|
|
@@ -9044,7 +9379,7 @@ function createGatewayModelCatalog(models, opts, modelAliases) {
|
|
|
9044
9379
|
const canonicalId = httpProxyModelId(gatewayProviderId(model), model.id);
|
|
9045
9380
|
if (!byId.has(canonicalId)) byId.set(canonicalId, model);
|
|
9046
9381
|
}
|
|
9047
|
-
for (const alias of modelAliases
|
|
9382
|
+
for (const alias of normalizeModelAliases(modelAliases).aliases) {
|
|
9048
9383
|
if (byId.has(alias.name)) continue;
|
|
9049
9384
|
const target = models.find(
|
|
9050
9385
|
(model) => gatewayProviderId(model) === alias.providerId && model.id === alias.modelId
|
|
@@ -9092,8 +9427,9 @@ function formatOpenAIModels(models) {
|
|
|
9092
9427
|
}
|
|
9093
9428
|
|
|
9094
9429
|
// src/route-unavailable.ts
|
|
9095
|
-
function routeUnavailableMessage(modelId) {
|
|
9096
|
-
|
|
9430
|
+
function routeUnavailableMessage(modelId, reason) {
|
|
9431
|
+
const detail = reason ? `: ${reason}` : "";
|
|
9432
|
+
return `Clodex model route '${modelId}' is unavailable${detail}. Run \`clodex models --list\` to inspect saved routes and aliases.`;
|
|
9097
9433
|
}
|
|
9098
9434
|
|
|
9099
9435
|
// src/upstream-forward.ts
|
|
@@ -10256,6 +10592,17 @@ function aliasModelId(realId, providerId) {
|
|
|
10256
10592
|
function lookupRoute(byAlias, id) {
|
|
10257
10593
|
return byAlias.get(normalizeRouteLookupId(id));
|
|
10258
10594
|
}
|
|
10595
|
+
function configuredAliasLookupNames(alias) {
|
|
10596
|
+
const sourceNames = [
|
|
10597
|
+
alias.name,
|
|
10598
|
+
...alias.savedName === void 0 ? [] : [alias.savedName],
|
|
10599
|
+
...alias.sourceNames ?? []
|
|
10600
|
+
];
|
|
10601
|
+
return [...new Set(sourceNames.flatMap((name) => {
|
|
10602
|
+
const trimmed = name.trim();
|
|
10603
|
+
return trimmed === name ? [name] : [name, trimmed];
|
|
10604
|
+
}))].map(normalizeRouteLookupId);
|
|
10605
|
+
}
|
|
10259
10606
|
async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
|
|
10260
10607
|
const proxyToken = randomUUID4();
|
|
10261
10608
|
silenceSdkWarnings();
|
|
@@ -10264,9 +10611,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10264
10611
|
}
|
|
10265
10612
|
const byAlias = new Map(routes.map((r) => [normalizeRouteLookupId(r.aliasId), r]));
|
|
10266
10613
|
const configuredAliasNames = new Set(
|
|
10267
|
-
(modelAliases ?? []).
|
|
10614
|
+
(modelAliases ?? []).flatMap(configuredAliasLookupNames)
|
|
10615
|
+
);
|
|
10616
|
+
const unavailableAliasReasons = new Map(
|
|
10617
|
+
(modelAliases ?? []).filter((alias) => alias.unavailableReason !== void 0).flatMap((alias) => configuredAliasLookupNames(alias).map((name) => [name, alias.unavailableReason]))
|
|
10268
10618
|
);
|
|
10269
10619
|
for (const alias of modelAliases ?? []) {
|
|
10620
|
+
if (alias.routeId === void 0 || alias.unavailableReason !== void 0) continue;
|
|
10270
10621
|
const route = lookupRoute(byAlias, alias.routeId);
|
|
10271
10622
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
10272
10623
|
if (route && !byAlias.has(aliasId)) byAlias.set(aliasId, route);
|
|
@@ -10341,7 +10692,14 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10341
10692
|
const resolvedRoute = typeof originalModel === "string" ? lookupRoute(byAlias, originalModel) : void 0;
|
|
10342
10693
|
const configuredModelUnavailable = typeof originalModel === "string" && (normalizeRouteLookupId(originalModel).startsWith("clodex:") || configuredAliasNames.has(normalizeRouteLookupId(originalModel)));
|
|
10343
10694
|
if (!resolvedRoute && configuredModelUnavailable) {
|
|
10344
|
-
anthropicError(
|
|
10695
|
+
anthropicError(
|
|
10696
|
+
res,
|
|
10697
|
+
400,
|
|
10698
|
+
routeUnavailableMessage(
|
|
10699
|
+
originalModel,
|
|
10700
|
+
unavailableAliasReasons.get(normalizeRouteLookupId(originalModel))
|
|
10701
|
+
)
|
|
10702
|
+
);
|
|
10345
10703
|
return;
|
|
10346
10704
|
}
|
|
10347
10705
|
const route = resolvedRoute ?? defaultRoute;
|
|
@@ -12382,6 +12740,10 @@ async function startHttpProxy(options) {
|
|
|
12382
12740
|
for (const alias of options.modelAliases ?? []) {
|
|
12383
12741
|
const aliasId = normalizeRouteLookupId(alias.name);
|
|
12384
12742
|
reservedModelIds.add(aliasId);
|
|
12743
|
+
for (const sourceName of alias.sourceNames ?? []) {
|
|
12744
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName));
|
|
12745
|
+
reservedModelIds.add(normalizeRouteLookupId(sourceName.trim()));
|
|
12746
|
+
}
|
|
12385
12747
|
const route = routesById.get(normalizeRouteLookupId(alias.routeId));
|
|
12386
12748
|
if (!route) continue;
|
|
12387
12749
|
routesById.set(aliasId, route);
|
|
@@ -12632,13 +12994,17 @@ async function startHttpProxy(options) {
|
|
|
12632
12994
|
async function loadHttpProxyRoutes() {
|
|
12633
12995
|
const prefs = loadPreferences();
|
|
12634
12996
|
const favorites = prefs.favoriteModels ?? [];
|
|
12997
|
+
const normalizedAliases = normalizeModelAliases(prefs.modelAliases);
|
|
12635
12998
|
if (favorites.length === 0) {
|
|
12636
12999
|
return {
|
|
12637
13000
|
routes: [],
|
|
12638
13001
|
unavailable: [],
|
|
12639
13002
|
unsupported: [],
|
|
12640
13003
|
aliases: [],
|
|
12641
|
-
unavailableAliases:
|
|
13004
|
+
unavailableAliases: [
|
|
13005
|
+
...normalizedAliases.rejected,
|
|
13006
|
+
...normalizedAliases.accepted.flatMap(({ sources }) => sources)
|
|
13007
|
+
],
|
|
12642
13008
|
favoriteCount: 0
|
|
12643
13009
|
};
|
|
12644
13010
|
}
|
|
@@ -12648,7 +13014,7 @@ async function loadHttpProxyRoutes() {
|
|
|
12648
13014
|
apiKey: await resolveLocalProviderApiKey(provider) ?? ""
|
|
12649
13015
|
})));
|
|
12650
13016
|
return {
|
|
12651
|
-
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases
|
|
13017
|
+
...buildHttpProxyRoutes(catalog, favorites, prefs.modelAliases),
|
|
12652
13018
|
favoriteCount: favorites.length
|
|
12653
13019
|
};
|
|
12654
13020
|
}
|
|
@@ -12682,8 +13048,16 @@ function reportSkippedHttpProxyFavorites(loaded) {
|
|
|
12682
13048
|
);
|
|
12683
13049
|
}
|
|
12684
13050
|
if (loaded.unavailableAliases.length > 0) {
|
|
13051
|
+
const normalizedAliases = normalizeModelAliases(loaded.unavailableAliases);
|
|
13052
|
+
const reasonByAlias = new Map(
|
|
13053
|
+
normalizedAliases.rejections.map((rejection) => [
|
|
13054
|
+
rejection.alias,
|
|
13055
|
+
describeModelAliasRejection(rejection.reason)
|
|
13056
|
+
])
|
|
13057
|
+
);
|
|
12685
13058
|
p8.log.warn(
|
|
12686
|
-
`${loaded.unavailableAliases.length} model alias${loaded.unavailableAliases.length === 1 ? "" : "es"} skipped
|
|
13059
|
+
`${loaded.unavailableAliases.length} model alias${loaded.unavailableAliases.length === 1 ? "" : "es"} skipped. Saved entries were preserved.
|
|
13060
|
+
` + loaded.unavailableAliases.map((alias) => ` ${JSON.stringify(alias.name)} \u2014 ${reasonByAlias.get(alias) ?? "target unavailable"}`).join("\n")
|
|
12687
13061
|
);
|
|
12688
13062
|
}
|
|
12689
13063
|
}
|
|
@@ -12693,7 +13067,14 @@ function buildConfiguredHttpProxyOptions(loaded, port, debug = false, inferenceL
|
|
|
12693
13067
|
port,
|
|
12694
13068
|
routes: loaded.routes,
|
|
12695
13069
|
modelAliases: loaded.aliases,
|
|
12696
|
-
reservedModelIds:
|
|
13070
|
+
reservedModelIds: [...new Set([
|
|
13071
|
+
...loaded.aliases.flatMap((alias) => alias.sourceNames ?? []),
|
|
13072
|
+
...loaded.unavailableAliases.map((alias) => alias.name)
|
|
13073
|
+
].flatMap((name) => {
|
|
13074
|
+
const trimmedName = name.trim();
|
|
13075
|
+
const canonicalName = canonicalModelAliasName(name);
|
|
13076
|
+
return [name, trimmedName, canonicalName].filter(Boolean);
|
|
13077
|
+
}))],
|
|
12697
13078
|
debug,
|
|
12698
13079
|
debugLogPath,
|
|
12699
13080
|
inferenceLogPath,
|
|
@@ -13104,7 +13485,28 @@ async function runServerCommand(options = {}) {
|
|
|
13104
13485
|
return 1;
|
|
13105
13486
|
}
|
|
13106
13487
|
const gateway = runConfig.maskGatewayIds ? { maskGatewayIds: true } : void 0;
|
|
13107
|
-
const
|
|
13488
|
+
const normalizedAliases = normalizeModelAliases(loadPreferences().modelAliases);
|
|
13489
|
+
const baseCatalog = createGatewayModelCatalog(models, gateway);
|
|
13490
|
+
const unavailableAliases = normalizedAliases.accepted.filter(({ alias }) => !models.some((model) => gatewayProviderId(model) === alias.providerId && model.id === alias.modelId));
|
|
13491
|
+
const collidingAliases = normalizedAliases.accepted.filter(({ alias }) => baseCatalog.get(alias.name) !== void 0);
|
|
13492
|
+
const collidingAliasNames = new Set(collidingAliases.map(({ alias }) => alias.name));
|
|
13493
|
+
const targetUnavailableAliases = unavailableAliases.filter(({ alias }) => !collidingAliasNames.has(alias.name));
|
|
13494
|
+
const inactiveAliasNames = /* @__PURE__ */ new Set([
|
|
13495
|
+
...targetUnavailableAliases.map(({ alias }) => alias.name),
|
|
13496
|
+
...collidingAliasNames
|
|
13497
|
+
]);
|
|
13498
|
+
const modelAliases = normalizedAliases.aliases.filter((alias) => !inactiveAliasNames.has(alias.name));
|
|
13499
|
+
const aliasWarnings = [
|
|
13500
|
+
...normalizedAliases.rejections.map((rejection) => `${JSON.stringify(rejection.alias.name)} \u2014 ${describeModelAliasRejection(rejection.reason)}`),
|
|
13501
|
+
...targetUnavailableAliases.flatMap(({ sources }) => sources.map((source) => `${JSON.stringify(source.name)} \u2014 target unavailable`)),
|
|
13502
|
+
...collidingAliases.flatMap(({ sources }) => sources.map((source) => `${JSON.stringify(source.name)} \u2014 conflicts with a catalog model id`))
|
|
13503
|
+
];
|
|
13504
|
+
if (aliasWarnings.length > 0) {
|
|
13505
|
+
p9.log.warn(
|
|
13506
|
+
`${aliasWarnings.length} saved model alias${aliasWarnings.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
|
|
13507
|
+
${aliasWarnings.join("\n ")}`
|
|
13508
|
+
);
|
|
13509
|
+
}
|
|
13108
13510
|
const inferenceLogPath = getInferenceRequestLogPath();
|
|
13109
13511
|
const webSocketDiagnosticsLogPath = options.wsDiagnostics ? getSessionLogPath("server-websocket-diagnostics", "jsonl") : void 0;
|
|
13110
13512
|
const server = await startServer({
|
|
@@ -13416,25 +13818,192 @@ function planLaunchWizard(opts) {
|
|
|
13416
13818
|
}
|
|
13417
13819
|
|
|
13418
13820
|
// src/patcher.ts
|
|
13419
|
-
import { createHash as
|
|
13821
|
+
import { createHash as createHash8 } from "crypto";
|
|
13420
13822
|
import {
|
|
13421
13823
|
copyFileSync,
|
|
13422
|
-
existsSync as
|
|
13824
|
+
existsSync as existsSync7,
|
|
13825
|
+
mkdtempSync,
|
|
13423
13826
|
mkdirSync as mkdirSync7,
|
|
13424
|
-
readFileSync as
|
|
13425
|
-
|
|
13827
|
+
readFileSync as readFileSync9,
|
|
13828
|
+
renameSync as renameSync3,
|
|
13829
|
+
rmSync,
|
|
13830
|
+
statSync as statSync4,
|
|
13426
13831
|
unlinkSync as unlinkSync4,
|
|
13427
13832
|
writeFileSync as writeFileSync7,
|
|
13428
13833
|
openSync as openSync4,
|
|
13429
13834
|
closeSync as closeSync4,
|
|
13430
13835
|
realpathSync
|
|
13431
13836
|
} from "fs";
|
|
13432
|
-
import { homedir as
|
|
13433
|
-
import { basename, join as
|
|
13837
|
+
import { homedir as homedir3 } from "os";
|
|
13838
|
+
import { basename, dirname as dirname5, join as join8 } from "path";
|
|
13434
13839
|
import pc12 from "picocolors";
|
|
13435
13840
|
import * as p11 from "@clack/prompts";
|
|
13436
13841
|
|
|
13842
|
+
// src/patch-backup.ts
|
|
13843
|
+
import { createHash as createHash7 } from "crypto";
|
|
13844
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, readdirSync, statSync as statSync3 } from "fs";
|
|
13845
|
+
import { homedir as homedir2 } from "os";
|
|
13846
|
+
import { join as join7 } from "path";
|
|
13847
|
+
var BACKUP_SHA_PREFIX_LENGTH = 16;
|
|
13848
|
+
function backupDir() {
|
|
13849
|
+
return process.env["TWEAKCC_CONFIG_DIR"]?.trim() || join7(homedir2(), ".tweakcc");
|
|
13850
|
+
}
|
|
13851
|
+
function sha256File(path) {
|
|
13852
|
+
return createHash7("sha256").update(readFileSync8(path)).digest("hex");
|
|
13853
|
+
}
|
|
13854
|
+
function backupVersionTag(version) {
|
|
13855
|
+
const tag = version.trim().replace(/[^\w.-]+/g, "_");
|
|
13856
|
+
if (!tag) throw new Error("clodex patch: refusing to name a pristine backup for an empty claude version");
|
|
13857
|
+
return tag;
|
|
13858
|
+
}
|
|
13859
|
+
function contentAddressedBackupPath(version, sha256, dir = backupDir()) {
|
|
13860
|
+
return join7(dir, `claude-${backupVersionTag(version)}-${sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH)}.orig`);
|
|
13861
|
+
}
|
|
13862
|
+
function tweakccMirrorBackupPath(dir = backupDir()) {
|
|
13863
|
+
return join7(dir, "native-binary.backup");
|
|
13864
|
+
}
|
|
13865
|
+
function scanPristineBackups(version, dir = backupDir()) {
|
|
13866
|
+
const tag = backupVersionTag(version);
|
|
13867
|
+
const pattern = new RegExp(`^claude-${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:-([0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}))?\\.orig$`);
|
|
13868
|
+
let entries;
|
|
13869
|
+
try {
|
|
13870
|
+
entries = readdirSync(dir);
|
|
13871
|
+
} catch {
|
|
13872
|
+
return { valid: [], corrupt: [] };
|
|
13873
|
+
}
|
|
13874
|
+
const valid = [];
|
|
13875
|
+
const corrupt = [];
|
|
13876
|
+
for (const entry of entries.sort()) {
|
|
13877
|
+
const match = pattern.exec(entry);
|
|
13878
|
+
if (!match) continue;
|
|
13879
|
+
const path = join7(dir, entry);
|
|
13880
|
+
let sha256;
|
|
13881
|
+
try {
|
|
13882
|
+
if (!statSync3(path).isFile()) continue;
|
|
13883
|
+
sha256 = sha256File(path);
|
|
13884
|
+
} catch {
|
|
13885
|
+
corrupt.push(path);
|
|
13886
|
+
continue;
|
|
13887
|
+
}
|
|
13888
|
+
const embedded = match[1];
|
|
13889
|
+
if (embedded) {
|
|
13890
|
+
if (sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH) !== embedded) {
|
|
13891
|
+
corrupt.push(path);
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
valid.push({ path, kind: "content-addressed", sha256 });
|
|
13895
|
+
} else {
|
|
13896
|
+
valid.push({ path, kind: "legacy", sha256 });
|
|
13897
|
+
}
|
|
13898
|
+
}
|
|
13899
|
+
return { valid, corrupt };
|
|
13900
|
+
}
|
|
13901
|
+
var CLODEX_PATCH_COMMENT_PREFIX = "/*ccpatch:";
|
|
13902
|
+
var LEGACY_CLODEX_PATCH_MARKERS = [
|
|
13903
|
+
"Additional custom models: ",
|
|
13904
|
+
// PATCH 4 — Agent tool model description
|
|
13905
|
+
"function(_i){return _i.value===_o.value}",
|
|
13906
|
+
// PATCH 5 — picker dedupe guard
|
|
13907
|
+
'"clodex:'
|
|
13908
|
+
// PATCH 1/3 — canonical ids as model identities
|
|
13909
|
+
];
|
|
13910
|
+
function isPatchedClaudeSource(source) {
|
|
13911
|
+
return source.includes(CLODEX_PATCH_COMMENT_PREFIX);
|
|
13912
|
+
}
|
|
13913
|
+
function looksLikeLegacyClodexPatch(source) {
|
|
13914
|
+
return !isPatchedClaudeSource(source) && LEGACY_CLODEX_PATCH_MARKERS.some((marker) => source.includes(marker));
|
|
13915
|
+
}
|
|
13916
|
+
function noBackupMessage(facts) {
|
|
13917
|
+
const corrupt = facts.corruptBackups?.length ? ` (${facts.corruptBackups.length} backup file(s) for this version failed integrity checks and were ignored)` : "";
|
|
13918
|
+
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\`.`;
|
|
13919
|
+
}
|
|
13920
|
+
function selectRestoreSource(facts) {
|
|
13921
|
+
const notes = [];
|
|
13922
|
+
const manifest = facts.manifest && facts.manifest.binaryPath === facts.binaryPath ? facts.manifest : null;
|
|
13923
|
+
let chosen = manifest?.pristineSha256 ? facts.backups.find((backup) => backup.sha256 === manifest.pristineSha256) : void 0;
|
|
13924
|
+
if (!chosen && manifest?.backupPath) {
|
|
13925
|
+
chosen = facts.backups.find((backup) => backup.path === manifest.backupPath);
|
|
13926
|
+
if (!chosen) {
|
|
13927
|
+
notes.push(`Recorded pristine backup ${manifest.backupPath} is missing, corrupt, or belongs to another claude version \u2014 ignoring it.`);
|
|
13928
|
+
}
|
|
13929
|
+
}
|
|
13930
|
+
if (!chosen) {
|
|
13931
|
+
const distinct = [...new Set(facts.backups.map((backup) => backup.sha256))];
|
|
13932
|
+
if (distinct.length > 1) {
|
|
13933
|
+
return {
|
|
13934
|
+
action: "error",
|
|
13935
|
+
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\`.`
|
|
13936
|
+
};
|
|
13937
|
+
}
|
|
13938
|
+
chosen = facts.backups.find((backup) => backup.kind === "content-addressed") ?? facts.backups[0];
|
|
13939
|
+
}
|
|
13940
|
+
if (!chosen) return { action: "error", message: noBackupMessage(facts) };
|
|
13941
|
+
return {
|
|
13942
|
+
action: "restore",
|
|
13943
|
+
backupPath: chosen.path,
|
|
13944
|
+
pristineSha256: chosen.sha256,
|
|
13945
|
+
// A legacy name carries no hash, so its bytes could be anything — including
|
|
13946
|
+
// another version's binary, stored under a mislabeled name by an older
|
|
13947
|
+
// clodex. Executing it is the only evidence available; require it.
|
|
13948
|
+
probeVersion: chosen.kind === "legacy",
|
|
13949
|
+
notes
|
|
13950
|
+
};
|
|
13951
|
+
}
|
|
13952
|
+
function planPristineSource(facts) {
|
|
13953
|
+
const identical = facts.backups.find((backup) => backup.sha256 === facts.liveSha256 && backup.kind === "content-addressed") ?? facts.backups.find((backup) => backup.sha256 === facts.liveSha256);
|
|
13954
|
+
if (identical) {
|
|
13955
|
+
return { action: "reuse", backupPath: identical.path, pristineSha256: identical.sha256, notes: [] };
|
|
13956
|
+
}
|
|
13957
|
+
const manifest = facts.manifest;
|
|
13958
|
+
if (manifest && manifest.binaryPath === facts.binaryPath && manifest.patchedSha256 === facts.liveSha256) {
|
|
13959
|
+
return selectRestoreSource(facts);
|
|
13960
|
+
}
|
|
13961
|
+
return { action: "inspect" };
|
|
13962
|
+
}
|
|
13963
|
+
function planInspectedPristineSource(facts, inspection) {
|
|
13964
|
+
if (inspection.patched) return selectRestoreSource(facts);
|
|
13965
|
+
const notes = [];
|
|
13966
|
+
const conflicting = facts.backups.filter((backup) => backup.sha256 !== facts.liveSha256);
|
|
13967
|
+
if (conflicting.length) {
|
|
13968
|
+
notes.push(
|
|
13969
|
+
`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.`
|
|
13970
|
+
);
|
|
13971
|
+
}
|
|
13972
|
+
return {
|
|
13973
|
+
action: "snapshot",
|
|
13974
|
+
backupPath: contentAddressedBackupPath(facts.version, facts.liveSha256),
|
|
13975
|
+
pristineSha256: facts.liveSha256,
|
|
13976
|
+
notes
|
|
13977
|
+
};
|
|
13978
|
+
}
|
|
13979
|
+
function planRestoreOnly(facts) {
|
|
13980
|
+
return selectRestoreSource(facts);
|
|
13981
|
+
}
|
|
13982
|
+
function collectPristineFacts(args) {
|
|
13983
|
+
const dir = args.dir ?? backupDir();
|
|
13984
|
+
const scan = scanPristineBackups(args.version, dir);
|
|
13985
|
+
return {
|
|
13986
|
+
version: args.version,
|
|
13987
|
+
binaryPath: args.binaryPath,
|
|
13988
|
+
liveSha256: existsSync6(args.binaryPath) ? sha256File(args.binaryPath) : "",
|
|
13989
|
+
manifest: args.manifest,
|
|
13990
|
+
backups: scan.valid,
|
|
13991
|
+
corruptBackups: scan.corrupt
|
|
13992
|
+
};
|
|
13993
|
+
}
|
|
13994
|
+
|
|
13437
13995
|
// src/patch-transforms.ts
|
|
13996
|
+
var PATCH_TRANSFORMS_VERSION = 2;
|
|
13997
|
+
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
13998
|
+
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
13999
|
+
function projectNativeEffort(effort) {
|
|
14000
|
+
if (!effort || !Array.isArray(effort.levels) || typeof effort.defaultLevel !== "string") return void 0;
|
|
14001
|
+
const declared = new Set(effort.levels);
|
|
14002
|
+
const levels = NATIVE_EFFORT_LEVELS.filter((level) => declared.has(level));
|
|
14003
|
+
if (!BASE_EFFORT_LEVELS.every((level) => declared.has(level))) return void 0;
|
|
14004
|
+
if (!levels.some((level) => level === effort.defaultLevel)) return void 0;
|
|
14005
|
+
return { levels, defaultLevel: "high" };
|
|
14006
|
+
}
|
|
13438
14007
|
var PatchApplyError = class extends Error {
|
|
13439
14008
|
results;
|
|
13440
14009
|
constructor(message, results) {
|
|
@@ -13449,21 +14018,37 @@ function formatPatchSiteLine(result) {
|
|
|
13449
14018
|
function applyClodexPatches(source, config) {
|
|
13450
14019
|
let js = source;
|
|
13451
14020
|
const MODEL_CONFIG = config;
|
|
13452
|
-
const ALIAS_TO_ID =
|
|
14021
|
+
const ALIAS_TO_ID = /* @__PURE__ */ Object.create(null);
|
|
13453
14022
|
const IDENTITIES = [];
|
|
13454
|
-
const DISPLAY_BY_IDENTITY =
|
|
13455
|
-
const CONTEXT_BY_KEY =
|
|
14023
|
+
const DISPLAY_BY_IDENTITY = /* @__PURE__ */ Object.create(null);
|
|
14024
|
+
const CONTEXT_BY_KEY = /* @__PURE__ */ Object.create(null);
|
|
14025
|
+
const CONFIGURED_CAPABILITY_KEYS = /* @__PURE__ */ new Set();
|
|
14026
|
+
const EFFORT_BY_KEY = /* @__PURE__ */ Object.create(null);
|
|
13456
14027
|
const report = [];
|
|
13457
14028
|
const fail = (message) => {
|
|
13458
14029
|
throw new PatchApplyError(message, report);
|
|
13459
14030
|
};
|
|
14031
|
+
const capabilityKeys = (value) => {
|
|
14032
|
+
const normalized = value.trim().toLowerCase();
|
|
14033
|
+
const bare = normalized.replace(/\[1m\]$/i, "");
|
|
14034
|
+
return [.../* @__PURE__ */ new Set([bare, `${bare}[1m]`])];
|
|
14035
|
+
};
|
|
14036
|
+
const registerCapabilityKeys = (value) => {
|
|
14037
|
+
for (const key of capabilityKeys(value)) {
|
|
14038
|
+
CONFIGURED_CAPABILITY_KEYS.add(key);
|
|
14039
|
+
}
|
|
14040
|
+
};
|
|
13460
14041
|
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
13461
14042
|
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
13462
14043
|
if (spec.alias !== void 0) {
|
|
13463
|
-
const
|
|
14044
|
+
const rawAlias = String(spec.alias).trim();
|
|
14045
|
+
const a = rawAlias.toLowerCase();
|
|
13464
14046
|
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
13465
14047
|
fail('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
13466
14048
|
}
|
|
14049
|
+
if (isReservedModelAlias(a)) {
|
|
14050
|
+
fail('clodex patch: reserved alias "' + a + '" cannot be reassigned');
|
|
14051
|
+
}
|
|
13467
14052
|
ALIAS_TO_ID[a] = String(id);
|
|
13468
14053
|
IDENTITIES.push(a);
|
|
13469
14054
|
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
@@ -13471,6 +14056,10 @@ function applyClodexPatches(source, config) {
|
|
|
13471
14056
|
IDENTITIES.push(String(id));
|
|
13472
14057
|
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
13473
14058
|
}
|
|
14059
|
+
if (spec.alias !== void 0) {
|
|
14060
|
+
registerCapabilityKeys(String(spec.alias));
|
|
14061
|
+
}
|
|
14062
|
+
registerCapabilityKeys(String(id));
|
|
13474
14063
|
if (spec.context !== void 0) {
|
|
13475
14064
|
const n = Number(spec.context);
|
|
13476
14065
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -13484,6 +14073,22 @@ function applyClodexPatches(source, config) {
|
|
|
13484
14073
|
if (spec.alias !== void 0) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
13485
14074
|
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
13486
14075
|
}
|
|
14076
|
+
if (spec.effort) {
|
|
14077
|
+
const effort = projectNativeEffort(spec.effort);
|
|
14078
|
+
if (!effort) {
|
|
14079
|
+
fail(
|
|
14080
|
+
`clodex patch: effort for "${id}" must include low, medium, and high with a native default`
|
|
14081
|
+
);
|
|
14082
|
+
}
|
|
14083
|
+
if (spec.alias !== void 0) {
|
|
14084
|
+
for (const key of capabilityKeys(String(spec.alias))) {
|
|
14085
|
+
EFFORT_BY_KEY[key] = effort;
|
|
14086
|
+
}
|
|
14087
|
+
}
|
|
14088
|
+
for (const key of capabilityKeys(String(id))) {
|
|
14089
|
+
EFFORT_BY_KEY[key] = effort;
|
|
14090
|
+
}
|
|
14091
|
+
}
|
|
13487
14092
|
}
|
|
13488
14093
|
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
13489
14094
|
const MODELS = Object.keys(MODEL_CONFIG);
|
|
@@ -13611,19 +14216,91 @@ function applyClodexPatches(source, config) {
|
|
|
13611
14216
|
);
|
|
13612
14217
|
}
|
|
13613
14218
|
}
|
|
14219
|
+
function patchEffortCapability(capability, marker, name, anchor) {
|
|
14220
|
+
const verdicts = Object.fromEntries(
|
|
14221
|
+
[...CONFIGURED_CAPABILITY_KEYS].map((key) => {
|
|
14222
|
+
const effort = EFFORT_BY_KEY[key];
|
|
14223
|
+
return [
|
|
14224
|
+
key,
|
|
14225
|
+
effort !== void 0 && (capability === "effort" || effort.levels.includes(capability === "xhigh_effort" ? "xhigh" : "max"))
|
|
14226
|
+
];
|
|
14227
|
+
})
|
|
14228
|
+
);
|
|
14229
|
+
const hasMarker = js.includes(marker);
|
|
14230
|
+
if (Object.keys(verdicts).length === 0 && !hasMarker) return;
|
|
14231
|
+
const snippet = (arg) => marker + "var _ccv=Object.assign(Object.create(null)," + JSON.stringify(verdicts) + ")[String(" + arg + '||"").trim().toLowerCase()];if(_ccv!==void 0)return _ccv;';
|
|
14232
|
+
if (hasMarker) {
|
|
14233
|
+
const markerPattern = reEsc(marker);
|
|
14234
|
+
applyOnce(
|
|
14235
|
+
name + " (refresh)",
|
|
14236
|
+
new RegExp(
|
|
14237
|
+
markerPattern + 'var _ccv=Object\\.assign\\(Object\\.create\\(null\\),\\{[^{}]*\\}\\)\\[String\\(([\\w$]+)\\|\\|""\\)\\.trim\\(\\)\\.toLowerCase\\(\\)\\];if\\(_ccv!==void 0\\)return _ccv;'
|
|
14238
|
+
),
|
|
14239
|
+
(_m, arg) => snippet(arg),
|
|
14240
|
+
{ required: false, noopIsSkip: true }
|
|
14241
|
+
);
|
|
14242
|
+
return;
|
|
14243
|
+
}
|
|
14244
|
+
applyOnce(
|
|
14245
|
+
name,
|
|
14246
|
+
anchor,
|
|
14247
|
+
(_m, head, arg, body) => head + snippet(arg) + body,
|
|
14248
|
+
{ required: false }
|
|
14249
|
+
);
|
|
14250
|
+
}
|
|
14251
|
+
patchEffortCapability(
|
|
14252
|
+
"effort",
|
|
14253
|
+
"/*ccpatch:effort*/",
|
|
14254
|
+
"PATCH 8a: effort capability",
|
|
14255
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"effort"\);)/
|
|
14256
|
+
);
|
|
14257
|
+
patchEffortCapability(
|
|
14258
|
+
"xhigh_effort",
|
|
14259
|
+
"/*ccpatch:xhigh-effort*/",
|
|
14260
|
+
"PATCH 8b: xhigh effort capability",
|
|
14261
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"xhigh_effort"\);)/
|
|
14262
|
+
);
|
|
14263
|
+
patchEffortCapability(
|
|
14264
|
+
"max_effort",
|
|
14265
|
+
"/*ccpatch:max-effort*/",
|
|
14266
|
+
"PATCH 8c: max effort capability",
|
|
14267
|
+
/(function [\w$]+\(([\w$]+)\)\{if\([\w$]+\(\2\)\)return!1;)(let [\w$]+=[\w$]+\(\2,"max_effort"\);)/
|
|
14268
|
+
);
|
|
14269
|
+
const DEFAULT_EFFORT_MARKER = "/*ccpatch:default-effort*/";
|
|
14270
|
+
const defaults = Object.fromEntries(
|
|
14271
|
+
Object.entries(EFFORT_BY_KEY).map(([key, effort]) => [key, effort.defaultLevel])
|
|
14272
|
+
);
|
|
14273
|
+
if (Object.keys(defaults).length || js.includes(DEFAULT_EFFORT_MARKER)) {
|
|
14274
|
+
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;';
|
|
14275
|
+
if (js.includes(DEFAULT_EFFORT_MARKER)) {
|
|
14276
|
+
applyOnce(
|
|
14277
|
+
"PATCH 9: default effort (refresh)",
|
|
14278
|
+
/\/\*ccpatch:default-effort\*\/var _cce=Object\.assign\(Object\.create\(null\),\{[^{}]*\}\)\[String\(([\w$]+)\|\|""\)\.trim\(\)\.toLowerCase\(\)\];if\(_cce!==void 0\)return _cce;/,
|
|
14279
|
+
(_m, arg) => snippet(arg),
|
|
14280
|
+
{ required: false, noopIsSkip: true }
|
|
14281
|
+
);
|
|
14282
|
+
} else {
|
|
14283
|
+
applyOnce(
|
|
14284
|
+
"PATCH 9: default effort",
|
|
14285
|
+
/(function [\w$]+\(([\w$]+)\)\{)(return [\w$]+\([\w$]+\(\2\)\)\?\.default_effort\?\?"high"\})/,
|
|
14286
|
+
(_m, head, arg, body) => head + snippet(arg) + body,
|
|
14287
|
+
{ required: false }
|
|
14288
|
+
);
|
|
14289
|
+
}
|
|
14290
|
+
}
|
|
13614
14291
|
return { content: js, results: report };
|
|
13615
14292
|
}
|
|
13616
14293
|
|
|
13617
14294
|
// src/patcher.ts
|
|
13618
14295
|
function getPatchManifestPath() {
|
|
13619
|
-
return
|
|
14296
|
+
return join8(getAppHome(), "patch-state.json");
|
|
13620
14297
|
}
|
|
13621
14298
|
function getPatchLockPath() {
|
|
13622
|
-
return
|
|
14299
|
+
return join8(getAppHome(), "patch.lock");
|
|
13623
14300
|
}
|
|
13624
14301
|
function readPatchManifest(path = getPatchManifestPath()) {
|
|
13625
14302
|
try {
|
|
13626
|
-
const parsed = JSON.parse(
|
|
14303
|
+
const parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
13627
14304
|
if (parsed && typeof parsed.binaryPath === "string" && typeof parsed.configHash === "string") {
|
|
13628
14305
|
return parsed;
|
|
13629
14306
|
}
|
|
@@ -13639,7 +14316,20 @@ function writePatchManifest(manifest, path = getPatchManifestPath()) {
|
|
|
13639
14316
|
function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
13640
14317
|
const config = {};
|
|
13641
14318
|
const unknownWindows = [];
|
|
13642
|
-
const
|
|
14319
|
+
const normalizedAliases = normalizeModelAliases(aliases);
|
|
14320
|
+
const favoriteTargets = new Set(
|
|
14321
|
+
favorites.map((favorite) => `${favorite.providerId}:${favorite.modelId}`)
|
|
14322
|
+
);
|
|
14323
|
+
const targetRejections = normalizedAliases.accepted.filter(({ alias }) => !favoriteTargets.has(`${alias.providerId}:${alias.modelId}`)).flatMap(({ sources }) => sources.map((source) => ({
|
|
14324
|
+
alias: source,
|
|
14325
|
+
reason: "target-not-favorite"
|
|
14326
|
+
})));
|
|
14327
|
+
const aliasByFavorite = new Map(
|
|
14328
|
+
normalizedAliases.aliases.filter((alias) => favoriteTargets.has(`${alias.providerId}:${alias.modelId}`)).map((alias) => [
|
|
14329
|
+
`${alias.providerId}:${alias.modelId}`,
|
|
14330
|
+
alias.name
|
|
14331
|
+
])
|
|
14332
|
+
);
|
|
13643
14333
|
for (const favorite of favorites) {
|
|
13644
14334
|
const id = stripOneMContextSuffix(httpProxyModelId(favorite.providerId, favorite.modelId));
|
|
13645
14335
|
if (config[id]) continue;
|
|
@@ -13652,29 +14342,68 @@ function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
|
13652
14342
|
else if (context !== 2e5) entry.context = context;
|
|
13653
14343
|
const display = meta?.displayName?.trim();
|
|
13654
14344
|
if (display) entry.display = display;
|
|
14345
|
+
const effort = projectNativeEffort(meta?.effort);
|
|
14346
|
+
if (effort) entry.effort = effort;
|
|
13655
14347
|
config[id] = entry;
|
|
13656
14348
|
}
|
|
13657
|
-
return {
|
|
14349
|
+
return {
|
|
14350
|
+
config,
|
|
14351
|
+
unknownWindows,
|
|
14352
|
+
rejectedAliases: [
|
|
14353
|
+
...normalizedAliases.rejected,
|
|
14354
|
+
...targetRejections.map((rejection) => rejection.alias)
|
|
14355
|
+
],
|
|
14356
|
+
rejectedAliasRejections: [
|
|
14357
|
+
...normalizedAliases.rejections,
|
|
14358
|
+
...targetRejections
|
|
14359
|
+
]
|
|
14360
|
+
};
|
|
13658
14361
|
}
|
|
13659
|
-
function
|
|
14362
|
+
function reportRejectedModelAliases(rejections) {
|
|
14363
|
+
for (const rejection of rejections) {
|
|
14364
|
+
p11.log.warn(
|
|
14365
|
+
`Saved model alias ${JSON.stringify(rejection.alias.name)} was not patched \u2014 ${describeModelAliasRejection(rejection.reason)}. The saved entry was preserved.`
|
|
14366
|
+
);
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
14369
|
+
function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VERSION) {
|
|
13660
14370
|
const canonical = Object.keys(config).sort().map((key) => {
|
|
13661
14371
|
const entry = config[key];
|
|
13662
|
-
return [
|
|
14372
|
+
return [
|
|
14373
|
+
key,
|
|
14374
|
+
entry.alias ?? null,
|
|
14375
|
+
entry.context ?? null,
|
|
14376
|
+
entry.display ?? null,
|
|
14377
|
+
entry.effort?.levels ?? null,
|
|
14378
|
+
entry.effort?.defaultLevel ?? null
|
|
14379
|
+
];
|
|
13663
14380
|
});
|
|
13664
|
-
return
|
|
14381
|
+
return createHash8("sha256").update(JSON.stringify([transformsVersion, canonical])).digest("hex");
|
|
13665
14382
|
}
|
|
13666
14383
|
function buildDesiredPatchConfig() {
|
|
13667
14384
|
const prefs = loadPreferences();
|
|
13668
14385
|
const favorites = prefs.favoriteModels ?? [];
|
|
13669
|
-
const aliases = prefs.modelAliases
|
|
14386
|
+
const aliases = prefs.modelAliases;
|
|
13670
14387
|
const registry = loadRegistry();
|
|
13671
14388
|
const meta = /* @__PURE__ */ new Map();
|
|
13672
14389
|
for (const provider of registry.providers) {
|
|
13673
14390
|
for (const model of provider.modelsCache?.models ?? []) {
|
|
14391
|
+
const npm = model.npm ?? provider.api.npm ?? "";
|
|
14392
|
+
const upstreamModelId2 = model.upstreamModelId ?? model.id;
|
|
14393
|
+
const modelsDev = findModelsDevModel(provider.id, model.id);
|
|
14394
|
+
const effort = getPatchReasoningCapabilities(npm, upstreamModelId2, {
|
|
14395
|
+
providerId: provider.id,
|
|
14396
|
+
apiBaseUrl: model.apiUrl ?? provider.api.url,
|
|
14397
|
+
supportedParameters: model.supportedParameters,
|
|
14398
|
+
reasoning: model.reasoning ?? modelsDev?.reasoning,
|
|
14399
|
+
interleavedReasoningField: model.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
14400
|
+
upstreamModelId: upstreamModelId2
|
|
14401
|
+
});
|
|
13674
14402
|
meta.set(`${provider.id}:${model.id}`, {
|
|
13675
14403
|
contextWindow: model.contextWindow && model.contextWindow > 0 ? model.contextWindow : void 0,
|
|
13676
14404
|
// Same label `clodex server` prints at startup and `models --list` shows.
|
|
13677
|
-
displayName: httpProxyDisplayName(model, provider.name)
|
|
14405
|
+
displayName: httpProxyDisplayName(model, provider.name),
|
|
14406
|
+
effort: effort.mode === "controllable" ? { levels: effort.levels, defaultLevel: effort.defaultLevel } : void 0
|
|
13678
14407
|
});
|
|
13679
14408
|
}
|
|
13680
14409
|
}
|
|
@@ -13704,7 +14433,7 @@ function pidIsAlive(pid) {
|
|
|
13704
14433
|
function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
13705
14434
|
const now = opts.now ?? Date.now();
|
|
13706
14435
|
const isAlive = opts.isAlive ?? pidIsAlive;
|
|
13707
|
-
mkdirSync7(
|
|
14436
|
+
mkdirSync7(join8(lockPath, ".."), { recursive: true, mode: 448 });
|
|
13708
14437
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13709
14438
|
try {
|
|
13710
14439
|
const fd = openSync4(lockPath, "wx");
|
|
@@ -13720,7 +14449,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13720
14449
|
} catch {
|
|
13721
14450
|
let stale = false;
|
|
13722
14451
|
try {
|
|
13723
|
-
const existing = JSON.parse(
|
|
14452
|
+
const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
13724
14453
|
stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
|
|
13725
14454
|
} catch {
|
|
13726
14455
|
stale = true;
|
|
@@ -13734,33 +14463,28 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
13734
14463
|
}
|
|
13735
14464
|
return null;
|
|
13736
14465
|
}
|
|
13737
|
-
function sha256File(path) {
|
|
13738
|
-
return createHash7("sha256").update(readFileSync8(path)).digest("hex");
|
|
13739
|
-
}
|
|
13740
14466
|
function resolveClaudeBinaryForPatch() {
|
|
13741
14467
|
const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"];
|
|
13742
|
-
const nativeSymlink =
|
|
13743
|
-
const source = envOverride?.trim() || (
|
|
13744
|
-
if (!source) return
|
|
14468
|
+
const nativeSymlink = join8(homedir3(), ".local", "bin", "claude");
|
|
14469
|
+
const source = envOverride?.trim() || (existsSync7(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
|
|
14470
|
+
if (!source) return { ok: false, reason: "binary-not-found" };
|
|
13745
14471
|
let resolved;
|
|
13746
14472
|
try {
|
|
13747
14473
|
resolved = realpathSync(source);
|
|
13748
14474
|
} catch {
|
|
13749
|
-
return
|
|
14475
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13750
14476
|
}
|
|
13751
14477
|
try {
|
|
13752
|
-
if (!
|
|
14478
|
+
if (!statSync4(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
|
|
13753
14479
|
} catch {
|
|
13754
|
-
return
|
|
14480
|
+
return { ok: false, reason: "binary-not-found" };
|
|
13755
14481
|
}
|
|
13756
|
-
|
|
14482
|
+
const version = getClaudeVersionForBinary(resolved);
|
|
14483
|
+
if (!version) return { ok: false, reason: "version-unknown", binaryPath: resolved };
|
|
14484
|
+
return { ok: true, binaryPath: resolved, version };
|
|
13757
14485
|
}
|
|
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`);
|
|
14486
|
+
function describePatchTargetFailure(target) {
|
|
14487
|
+
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
14488
|
}
|
|
13765
14489
|
function summarizePatchResults(results) {
|
|
13766
14490
|
const lines = results.map(formatPatchSiteLine);
|
|
@@ -13773,26 +14497,112 @@ function summarizePatchResults(results) {
|
|
|
13773
14497
|
}
|
|
13774
14498
|
return lines;
|
|
13775
14499
|
}
|
|
14500
|
+
function verifyPristineSource(plan, version) {
|
|
14501
|
+
if (!plan.probeVersion) return { ok: true };
|
|
14502
|
+
const backupVersion = getClaudeVersionForBinary(plan.backupPath);
|
|
14503
|
+
if (backupVersion === version) return { ok: true };
|
|
14504
|
+
return {
|
|
14505
|
+
ok: false,
|
|
14506
|
+
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\`.`
|
|
14507
|
+
};
|
|
14508
|
+
}
|
|
14509
|
+
function publishBackupFile(from, to) {
|
|
14510
|
+
const temp = `${to}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
14511
|
+
try {
|
|
14512
|
+
copyFileSync(from, temp);
|
|
14513
|
+
renameSync3(temp, to);
|
|
14514
|
+
} catch (err) {
|
|
14515
|
+
try {
|
|
14516
|
+
rmSync(temp, { force: true });
|
|
14517
|
+
} catch {
|
|
14518
|
+
}
|
|
14519
|
+
throw err;
|
|
14520
|
+
}
|
|
14521
|
+
}
|
|
14522
|
+
function describePoisonedPristineSource(backupPath, version) {
|
|
14523
|
+
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\`.`;
|
|
14524
|
+
}
|
|
14525
|
+
function requiredEffortPatchFailures(results) {
|
|
14526
|
+
return results.filter(
|
|
14527
|
+
(result) => result.status === "FAIL" && (result.name.startsWith("PATCH 8a:") || result.name.startsWith("PATCH 8b:") || result.name.startsWith("PATCH 8c:") || result.name.startsWith("PATCH 9:"))
|
|
14528
|
+
);
|
|
14529
|
+
}
|
|
13776
14530
|
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");
|
|
14531
|
+
let candidateDir;
|
|
13789
14532
|
let results;
|
|
14533
|
+
let patchedSize;
|
|
14534
|
+
let patchedSha256;
|
|
14535
|
+
let backup;
|
|
14536
|
+
let pristineSha256;
|
|
13790
14537
|
try {
|
|
13791
|
-
|
|
13792
|
-
const
|
|
13793
|
-
|
|
14538
|
+
mkdirSync7(backupDir(), { recursive: true });
|
|
14539
|
+
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
14540
|
+
candidateDir = mkdtempSync(join8(dirname5(binaryPath), ".clodex-patch-"));
|
|
14541
|
+
const candidatePath = join8(candidateDir, basename(binaryPath));
|
|
14542
|
+
const seedCandidate = async (from) => {
|
|
14543
|
+
copyFileSync(from, candidatePath);
|
|
14544
|
+
const installation = await tryDetectInstallation({ path: candidatePath });
|
|
14545
|
+
return { installation, source: await readContent(installation) };
|
|
14546
|
+
};
|
|
14547
|
+
const facts = collectPristineFacts({
|
|
14548
|
+
version,
|
|
14549
|
+
binaryPath,
|
|
14550
|
+
manifest: opts.manifest
|
|
14551
|
+
});
|
|
14552
|
+
const initial = planPristineSource(facts);
|
|
14553
|
+
let loaded = null;
|
|
14554
|
+
let plan;
|
|
14555
|
+
if (initial.action === "inspect") {
|
|
14556
|
+
loaded = await seedCandidate(binaryPath);
|
|
14557
|
+
if (looksLikeLegacyClodexPatch(loaded.source)) {
|
|
14558
|
+
p11.log.warn(
|
|
14559
|
+
`${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.`
|
|
14560
|
+
);
|
|
14561
|
+
}
|
|
14562
|
+
plan = planInspectedPristineSource(facts, { patched: isPatchedClaudeSource(loaded.source) });
|
|
14563
|
+
if (plan.action !== "snapshot") loaded = null;
|
|
14564
|
+
} else {
|
|
14565
|
+
plan = initial;
|
|
14566
|
+
}
|
|
14567
|
+
if (plan.action === "error") return { ok: false, message: plan.message };
|
|
14568
|
+
for (const note of plan.notes) p11.log.warn(note);
|
|
14569
|
+
if (plan.action === "restore") {
|
|
14570
|
+
const verified = verifyPristineSource(plan, version);
|
|
14571
|
+
if (!verified.ok) return { ok: false, message: verified.message };
|
|
14572
|
+
p11.log.info(`Binary differs from its pristine backup \u2014 building a fresh patch candidate from ${plan.backupPath}.`);
|
|
14573
|
+
}
|
|
14574
|
+
pristineSha256 = plan.pristineSha256;
|
|
14575
|
+
backup = plan.backupPath;
|
|
14576
|
+
if (!loaded) {
|
|
14577
|
+
loaded = await seedCandidate(backup);
|
|
14578
|
+
if (isPatchedClaudeSource(loaded.source)) {
|
|
14579
|
+
return { ok: false, message: describePoisonedPristineSource(backup, version) };
|
|
14580
|
+
}
|
|
14581
|
+
} else if (plan.action === "snapshot") {
|
|
14582
|
+
publishBackupFile(candidatePath, plan.backupPath);
|
|
14583
|
+
}
|
|
14584
|
+
const canonical = contentAddressedBackupPath(version, pristineSha256);
|
|
14585
|
+
if (canonical !== backup) {
|
|
14586
|
+
const alreadyStored = facts.backups.some(
|
|
14587
|
+
(candidate) => candidate.path === canonical && candidate.sha256 === pristineSha256
|
|
14588
|
+
);
|
|
14589
|
+
if (!alreadyStored) publishBackupFile(backup, canonical);
|
|
14590
|
+
backup = canonical;
|
|
14591
|
+
}
|
|
14592
|
+
publishBackupFile(backup, tweakccMirrorBackupPath());
|
|
14593
|
+
const patched = applyClodexPatches(loaded.source, desired.config);
|
|
13794
14594
|
results = patched.results;
|
|
13795
|
-
|
|
14595
|
+
const failedEffortPatches = requiredEffortPatchFailures(results);
|
|
14596
|
+
if (failedEffortPatches.length > 0) {
|
|
14597
|
+
throw new PatchApplyError(
|
|
14598
|
+
`clodex patch: required effort patches failed: ${failedEffortPatches.map((result) => result.name).join("; ")}`,
|
|
14599
|
+
results
|
|
14600
|
+
);
|
|
14601
|
+
}
|
|
14602
|
+
await writeContent(loaded.installation, patched.content);
|
|
14603
|
+
patchedSize = statSync4(candidatePath).size;
|
|
14604
|
+
patchedSha256 = sha256File(candidatePath);
|
|
14605
|
+
renameSync3(candidatePath, binaryPath);
|
|
13796
14606
|
} catch (err) {
|
|
13797
14607
|
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
13798
14608
|
if (opts.trace && detailLines.length) {
|
|
@@ -13804,6 +14614,19 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13804
14614
|
message: `Patch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
13805
14615
|
detailLines
|
|
13806
14616
|
};
|
|
14617
|
+
} finally {
|
|
14618
|
+
if (candidateDir !== void 0) {
|
|
14619
|
+
try {
|
|
14620
|
+
rmSync(candidateDir, { recursive: true, force: true });
|
|
14621
|
+
} catch (err) {
|
|
14622
|
+
if (opts.trace) {
|
|
14623
|
+
process.stderr.write(
|
|
14624
|
+
`clodex patch: could not remove temporary candidate directory: ${err instanceof Error ? err.message : String(err)}
|
|
14625
|
+
`
|
|
14626
|
+
);
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
}
|
|
13807
14630
|
}
|
|
13808
14631
|
if (opts.trace) {
|
|
13809
14632
|
process.stderr.write(`${summarizePatchResults(results).join("\n")}
|
|
@@ -13813,9 +14636,10 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13813
14636
|
binaryPath,
|
|
13814
14637
|
claudeVersion: version,
|
|
13815
14638
|
configHash,
|
|
13816
|
-
patchedSize
|
|
13817
|
-
patchedSha256
|
|
14639
|
+
patchedSize,
|
|
14640
|
+
patchedSha256,
|
|
13818
14641
|
backupPath: backup,
|
|
14642
|
+
pristineSha256,
|
|
13819
14643
|
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13820
14644
|
};
|
|
13821
14645
|
writePatchManifest(manifest);
|
|
@@ -13828,28 +14652,54 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
13828
14652
|
detailLines: summarizePatchResults(results)
|
|
13829
14653
|
};
|
|
13830
14654
|
}
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
p11.log.error("claude binary not found. Install Claude Code or set TWEAKCC_CC_INSTALLATION_PATH.");
|
|
14655
|
+
function runRestoreCommand(target) {
|
|
14656
|
+
if (!target.ok && target.reason === "binary-not-found") {
|
|
14657
|
+
p11.log.error(describePatchTargetFailure(target));
|
|
13835
14658
|
return 1;
|
|
13836
14659
|
}
|
|
13837
|
-
const
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
return
|
|
14660
|
+
const binaryPath = target.binaryPath;
|
|
14661
|
+
const manifest = readPatchManifest();
|
|
14662
|
+
let version;
|
|
14663
|
+
if (target.ok) {
|
|
14664
|
+
version = target.version;
|
|
14665
|
+
} else if (manifest?.binaryPath === binaryPath && manifest.claudeVersion) {
|
|
14666
|
+
version = manifest.claudeVersion;
|
|
14667
|
+
p11.log.warn(
|
|
14668
|
+
`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.`
|
|
14669
|
+
);
|
|
14670
|
+
} else {
|
|
14671
|
+
p11.log.error(
|
|
14672
|
+
`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).`
|
|
14673
|
+
);
|
|
14674
|
+
return 1;
|
|
13852
14675
|
}
|
|
14676
|
+
const plan = planRestoreOnly(collectPristineFacts({ version, binaryPath, manifest }));
|
|
14677
|
+
if (plan.action === "error") {
|
|
14678
|
+
p11.log.error(plan.message);
|
|
14679
|
+
return 1;
|
|
14680
|
+
}
|
|
14681
|
+
for (const note of plan.notes) p11.log.warn(note);
|
|
14682
|
+
const verified = verifyPristineSource(plan, version);
|
|
14683
|
+
if (!verified.ok) {
|
|
14684
|
+
p11.log.error(verified.message);
|
|
14685
|
+
return 1;
|
|
14686
|
+
}
|
|
14687
|
+
copyFileSync(plan.backupPath, binaryPath);
|
|
14688
|
+
try {
|
|
14689
|
+
unlinkSync4(getPatchManifestPath());
|
|
14690
|
+
} catch {
|
|
14691
|
+
}
|
|
14692
|
+
p11.log.success(`Restored pristine claude ${version} from ${plan.backupPath}.`);
|
|
14693
|
+
return 0;
|
|
14694
|
+
}
|
|
14695
|
+
async function runPatchCommand(opts = {}) {
|
|
14696
|
+
const target = resolveClaudeBinaryForPatch();
|
|
14697
|
+
if (opts.restore) return runRestoreCommand(target);
|
|
14698
|
+
if (!target.ok) {
|
|
14699
|
+
p11.log.error(describePatchTargetFailure(target));
|
|
14700
|
+
return 1;
|
|
14701
|
+
}
|
|
14702
|
+
const { binaryPath, version } = target;
|
|
13853
14703
|
const desired = buildDesiredPatchConfig();
|
|
13854
14704
|
if (Object.keys(desired.config).length === 0) {
|
|
13855
14705
|
p11.log.error("No favorite models to patch. Save favorites with `clodex models` first.");
|
|
@@ -13858,13 +14708,14 @@ async function runPatchCommand(opts = {}) {
|
|
|
13858
14708
|
for (const id of desired.unknownWindows) {
|
|
13859
14709
|
p11.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
|
|
13860
14710
|
}
|
|
14711
|
+
reportRejectedModelAliases(desired.rejectedAliasRejections);
|
|
13861
14712
|
const configHash = computePatchConfigHash(desired.config);
|
|
13862
14713
|
const manifest = readPatchManifest();
|
|
13863
14714
|
const state = evaluatePatchState(manifest, {
|
|
13864
14715
|
binaryPath,
|
|
13865
14716
|
claudeVersion: version,
|
|
13866
14717
|
configHash,
|
|
13867
|
-
binarySize:
|
|
14718
|
+
binarySize: statSync4(binaryPath).size
|
|
13868
14719
|
});
|
|
13869
14720
|
if (state === "current") {
|
|
13870
14721
|
p11.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
|
|
@@ -13876,14 +14727,9 @@ async function runPatchCommand(opts = {}) {
|
|
|
13876
14727
|
return 1;
|
|
13877
14728
|
}
|
|
13878
14729
|
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
14730
|
const outcome = await applyPatch(binaryPath, version, desired, configHash, {
|
|
13885
14731
|
trace: opts.trace ?? false,
|
|
13886
|
-
|
|
14732
|
+
manifest
|
|
13887
14733
|
});
|
|
13888
14734
|
if (!outcome.ok) {
|
|
13889
14735
|
p11.log.error(outcome.message);
|
|
@@ -13903,15 +14749,21 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
13903
14749
|
try {
|
|
13904
14750
|
const desired = buildDesiredPatchConfig();
|
|
13905
14751
|
if (Object.keys(desired.config).length === 0) return;
|
|
13906
|
-
const
|
|
13907
|
-
if (!
|
|
14752
|
+
const target = resolveClaudeBinaryForPatch();
|
|
14753
|
+
if (!target.ok) {
|
|
14754
|
+
if (target.reason === "version-unknown" && !opts.agentStdout) {
|
|
14755
|
+
console.error(pc12.dim(`clodex: ${describePatchTargetFailure(target)}`));
|
|
14756
|
+
}
|
|
14757
|
+
return;
|
|
14758
|
+
}
|
|
14759
|
+
const resolved = target;
|
|
13908
14760
|
const configHash = computePatchConfigHash(desired.config);
|
|
13909
14761
|
const manifest = readPatchManifest();
|
|
13910
14762
|
const state = evaluatePatchState(manifest, {
|
|
13911
14763
|
binaryPath: resolved.binaryPath,
|
|
13912
14764
|
claudeVersion: resolved.version,
|
|
13913
14765
|
configHash,
|
|
13914
|
-
binarySize:
|
|
14766
|
+
binarySize: statSync4(resolved.binaryPath).size
|
|
13915
14767
|
});
|
|
13916
14768
|
if (state === "current") return;
|
|
13917
14769
|
const interactive = !opts.dryRun && !opts.agentStdout && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -14351,6 +15203,7 @@ ${pc13.bold("Behavior:")}
|
|
|
14351
15203
|
proxy mode, without opening the interactive manager.
|
|
14352
15204
|
--alias <name=target> saves a short name for a proxy-mode favorite. The
|
|
14353
15205
|
target is clodex:<provider-id>:<model-id> (the clodex: prefix is optional).
|
|
15206
|
+
Alias names are stored lowercase and cannot use client-reserved model names.
|
|
14354
15207
|
--unalias <name> removes a saved short name.
|
|
14355
15208
|
|
|
14356
15209
|
${pc13.bold("How it works:")}
|
|
@@ -14392,7 +15245,19 @@ function printHelp(text4) {
|
|
|
14392
15245
|
${text4}
|
|
14393
15246
|
`);
|
|
14394
15247
|
}
|
|
15248
|
+
function reportInactiveCatalogAliases(modelAliases) {
|
|
15249
|
+
const unavailableAliases = modelAliases.filter((alias) => alias.unavailableReason !== void 0);
|
|
15250
|
+
if (unavailableAliases.length === 0) return;
|
|
15251
|
+
const warningLines = unavailableAliases.flatMap((alias) => alias.sourceNames?.length ? alias.sourceNames.map((name) => ` ${JSON.stringify(name)} \u2014 ${alias.unavailableReason}`) : [
|
|
15252
|
+
` ${JSON.stringify(alias.savedName ?? alias.name)} \u2014 ${alias.unavailableReason}`
|
|
15253
|
+
]);
|
|
15254
|
+
p12.log.warn(
|
|
15255
|
+
`${warningLines.length} saved model alias${warningLines.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
|
|
15256
|
+
` + warningLines.join("\n")
|
|
15257
|
+
);
|
|
15258
|
+
}
|
|
14395
15259
|
async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases, contextWindow, trace, claudeArgs) {
|
|
15260
|
+
reportInactiveCatalogAliases(modelAliases);
|
|
14396
15261
|
let proxyHandle;
|
|
14397
15262
|
try {
|
|
14398
15263
|
proxyHandle = await startProxyCatalog(
|
|
@@ -14452,27 +15317,36 @@ async function runModelsCommand(opts = {}) {
|
|
|
14452
15317
|
p12.log.info("Add it with `clodex models`, then save the alias.");
|
|
14453
15318
|
return 1;
|
|
14454
15319
|
}
|
|
14455
|
-
|
|
15320
|
+
if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
|
|
15321
|
+
p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
15322
|
+
return 1;
|
|
15323
|
+
}
|
|
15324
|
+
const aliases = prefs2.modelAliases ?? [];
|
|
15325
|
+
const modelAliases = aliases.filter((alias) => !modelAliasMatchesName(alias, parsed.name));
|
|
14456
15326
|
modelAliases.push(parsed);
|
|
14457
15327
|
savePreferences({ modelAliases });
|
|
14458
15328
|
p12.log.success(`Saved model alias ${parsed.name} \u2192 ${modelAliasTarget(parsed)}.`);
|
|
14459
15329
|
return 0;
|
|
14460
15330
|
}
|
|
14461
15331
|
if (opts.unalias !== void 0) {
|
|
14462
|
-
const
|
|
14463
|
-
|
|
14464
|
-
|
|
15332
|
+
const requestedName = opts.unalias.trim();
|
|
15333
|
+
const name = canonicalModelAliasName(requestedName);
|
|
15334
|
+
const prefs2 = loadPreferences();
|
|
15335
|
+
if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
|
|
15336
|
+
p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
|
|
14465
15337
|
return 1;
|
|
14466
15338
|
}
|
|
14467
|
-
const prefs2 = loadPreferences();
|
|
14468
15339
|
const aliases = prefs2.modelAliases ?? [];
|
|
14469
|
-
const modelAliases = aliases.filter((alias) => alias
|
|
14470
|
-
|
|
14471
|
-
|
|
15340
|
+
const modelAliases = aliases.filter((alias) => !modelAliasMatchesStoredName(alias, requestedName));
|
|
15341
|
+
const removedCount = aliases.length - modelAliases.length;
|
|
15342
|
+
if (removedCount === 0) {
|
|
15343
|
+
p12.log.error(`No model alias named ${JSON.stringify(requestedName)} is saved.`);
|
|
14472
15344
|
return 1;
|
|
14473
15345
|
}
|
|
14474
15346
|
savePreferences({ modelAliases });
|
|
14475
|
-
p12.log.success(
|
|
15347
|
+
p12.log.success(
|
|
15348
|
+
removedCount === 1 ? `Removed model alias ${name || JSON.stringify(requestedName)}.` : `Removed ${removedCount} model aliases named ${name || JSON.stringify(requestedName)}.`
|
|
15349
|
+
);
|
|
14476
15350
|
return 0;
|
|
14477
15351
|
}
|
|
14478
15352
|
if (opts.list) {
|
|
@@ -14956,7 +15830,11 @@ Error: ${launchPlan.error}
|
|
|
14956
15830
|
return launchClaudeViaCatalog(
|
|
14957
15831
|
catalogRoutes,
|
|
14958
15832
|
startingRoute,
|
|
14959
|
-
resolveCatalogModelAliases(
|
|
15833
|
+
resolveCatalogModelAliases(
|
|
15834
|
+
prefs.modelAliases,
|
|
15835
|
+
resolveRoute,
|
|
15836
|
+
catalogRoutes
|
|
15837
|
+
),
|
|
14960
15838
|
selectedModel.contextWindow,
|
|
14961
15839
|
trace,
|
|
14962
15840
|
claudeArgs
|
|
@@ -15203,6 +16081,7 @@ export {
|
|
|
15203
16081
|
modelsHelpText,
|
|
15204
16082
|
parseArgs,
|
|
15205
16083
|
patchHelpText,
|
|
16084
|
+
reportInactiveCatalogAliases,
|
|
15206
16085
|
rootHelpText,
|
|
15207
16086
|
runClaudeCommand,
|
|
15208
16087
|
runModelsCommand,
|