@bman654/clodex 1.0.0 → 1.0.3
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/cli.js +269 -359
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -202,7 +202,7 @@ import { join } from "path";
|
|
|
202
202
|
// package.json
|
|
203
203
|
var package_default = {
|
|
204
204
|
name: "@bman654/clodex",
|
|
205
|
-
version: "1.0.
|
|
205
|
+
version: "1.0.3",
|
|
206
206
|
publishConfig: {
|
|
207
207
|
access: "public"
|
|
208
208
|
},
|
|
@@ -256,6 +256,7 @@ var package_default = {
|
|
|
256
256
|
"node-forge": "1.4.0",
|
|
257
257
|
open: "11.0.0",
|
|
258
258
|
picocolors: "1.1.1",
|
|
259
|
+
tweakcc: "4.3.0",
|
|
259
260
|
undici: "7.28.0",
|
|
260
261
|
ws: "8.21.0"
|
|
261
262
|
},
|
|
@@ -7091,13 +7092,24 @@ function translateMessages(messages, npm, openAiPromptCacheBreakpoints = false)
|
|
|
7091
7092
|
}
|
|
7092
7093
|
return out;
|
|
7093
7094
|
}
|
|
7094
|
-
function
|
|
7095
|
+
function sanitizeToolInput(input, requiredProps) {
|
|
7095
7096
|
const out = {};
|
|
7096
7097
|
for (const [k, v] of Object.entries(input)) {
|
|
7097
|
-
if (v
|
|
7098
|
+
if (v === null) continue;
|
|
7099
|
+
if (Array.isArray(v) && v.length === 0 && !requiredProps?.has(k)) continue;
|
|
7100
|
+
out[k] = v;
|
|
7098
7101
|
}
|
|
7099
7102
|
return out;
|
|
7100
7103
|
}
|
|
7104
|
+
function toolRequiredProps(tools) {
|
|
7105
|
+
const map = /* @__PURE__ */ new Map();
|
|
7106
|
+
for (const [name, t] of Object.entries(tools ?? {})) {
|
|
7107
|
+
const schema = t.inputSchema?.jsonSchema;
|
|
7108
|
+
const required = Array.isArray(schema?.required) ? schema.required : [];
|
|
7109
|
+
map.set(name, new Set(required.filter((r) => typeof r === "string")));
|
|
7110
|
+
}
|
|
7111
|
+
return map;
|
|
7112
|
+
}
|
|
7101
7113
|
function translateTools(anthropicTools) {
|
|
7102
7114
|
if (!anthropicTools?.length) return void 0;
|
|
7103
7115
|
const tools = {};
|
|
@@ -7207,13 +7219,17 @@ function forwardAbortSignal(source, target) {
|
|
|
7207
7219
|
source.addEventListener("abort", forward, { once: true });
|
|
7208
7220
|
return () => source.removeEventListener("abort", forward);
|
|
7209
7221
|
}
|
|
7210
|
-
async function writeAnthropicStream(stream, modelId, write, log12, observer) {
|
|
7222
|
+
async function writeAnthropicStream(stream, modelId, write, log12, observer, tools) {
|
|
7211
7223
|
const messageId = "msg_" + Date.now();
|
|
7224
|
+
const requiredProps = toolRequiredProps(tools);
|
|
7212
7225
|
let blockIndex = -1;
|
|
7213
7226
|
let started = false;
|
|
7214
7227
|
let openType = null;
|
|
7215
7228
|
let pendingThinkingSig;
|
|
7216
7229
|
const idToBlock = /* @__PURE__ */ new Map();
|
|
7230
|
+
const toolJsonBuffer = /* @__PURE__ */ new Map();
|
|
7231
|
+
const flushedTools = /* @__PURE__ */ new Set();
|
|
7232
|
+
let openToolId = null;
|
|
7217
7233
|
let finishReason = "end_turn";
|
|
7218
7234
|
let usage = {
|
|
7219
7235
|
input_tokens: 0,
|
|
@@ -7253,8 +7269,20 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer) {
|
|
|
7253
7269
|
});
|
|
7254
7270
|
pendingThinkingSig = void 0;
|
|
7255
7271
|
}
|
|
7272
|
+
if (openType === "tool" && openToolId !== null && !flushedTools.has(openToolId)) {
|
|
7273
|
+
const buffered = toolJsonBuffer.get(openToolId);
|
|
7274
|
+
if (buffered) {
|
|
7275
|
+
emit("content_block_delta", {
|
|
7276
|
+
type: "content_block_delta",
|
|
7277
|
+
index: blockIndex,
|
|
7278
|
+
delta: { type: "input_json_delta", partial_json: buffered }
|
|
7279
|
+
});
|
|
7280
|
+
}
|
|
7281
|
+
flushedTools.add(openToolId);
|
|
7282
|
+
}
|
|
7256
7283
|
if (openType) emit("content_block_stop", { type: "content_block_stop", index: blockIndex });
|
|
7257
7284
|
openType = null;
|
|
7285
|
+
openToolId = null;
|
|
7258
7286
|
};
|
|
7259
7287
|
const openBlock = (type, contentBlock) => {
|
|
7260
7288
|
ensureStart();
|
|
@@ -7316,32 +7344,45 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer) {
|
|
|
7316
7344
|
input: {}
|
|
7317
7345
|
});
|
|
7318
7346
|
idToBlock.set(part.id ?? "", blockIndex);
|
|
7347
|
+
openToolId = part.id ?? "";
|
|
7319
7348
|
break;
|
|
7320
7349
|
}
|
|
7321
|
-
case "tool-input-delta":
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
index: idToBlock.get(part.id ?? "") ?? blockIndex,
|
|
7325
|
-
delta: { type: "input_json_delta", partial_json: part.delta ?? part.text ?? "" }
|
|
7326
|
-
});
|
|
7350
|
+
case "tool-input-delta": {
|
|
7351
|
+
const id = part.id ?? "";
|
|
7352
|
+
toolJsonBuffer.set(id, (toolJsonBuffer.get(id) ?? "") + (part.delta ?? part.text ?? ""));
|
|
7327
7353
|
break;
|
|
7354
|
+
}
|
|
7328
7355
|
case "tool-input-end":
|
|
7329
7356
|
break;
|
|
7330
7357
|
case "tool-call": {
|
|
7331
7358
|
finishReason = "tool_use";
|
|
7332
|
-
|
|
7359
|
+
const id = part.toolCallId ?? "";
|
|
7360
|
+
if (idToBlock.has(id)) {
|
|
7361
|
+
if (!flushedTools.has(id)) {
|
|
7362
|
+
const json = part.input !== void 0 && part.input !== null ? JSON.stringify(sanitizeToolInput(part.input, requiredProps.get(part.toolName ?? ""))) : toolJsonBuffer.get(id) ?? "";
|
|
7363
|
+
if (json) {
|
|
7364
|
+
emit("content_block_delta", {
|
|
7365
|
+
type: "content_block_delta",
|
|
7366
|
+
index: idToBlock.get(id) ?? blockIndex,
|
|
7367
|
+
delta: { type: "input_json_delta", partial_json: json }
|
|
7368
|
+
});
|
|
7369
|
+
}
|
|
7370
|
+
flushedTools.add(id);
|
|
7371
|
+
}
|
|
7372
|
+
} else if (openType !== "tool") {
|
|
7333
7373
|
const sig = grabRoundTripSignature(part);
|
|
7334
7374
|
openBlock("tool", {
|
|
7335
7375
|
type: "tool_use",
|
|
7336
|
-
id: encodeToolUseId(
|
|
7376
|
+
id: encodeToolUseId(id, sig),
|
|
7337
7377
|
name: part.toolName,
|
|
7338
7378
|
input: {}
|
|
7339
7379
|
});
|
|
7340
7380
|
emit("content_block_delta", {
|
|
7341
7381
|
type: "content_block_delta",
|
|
7342
7382
|
index: blockIndex,
|
|
7343
|
-
delta: { type: "input_json_delta", partial_json: JSON.stringify(
|
|
7383
|
+
delta: { type: "input_json_delta", partial_json: JSON.stringify(sanitizeToolInput(part.input ?? {}, requiredProps.get(part.toolName ?? ""))) }
|
|
7344
7384
|
});
|
|
7385
|
+
flushedTools.add(id);
|
|
7345
7386
|
}
|
|
7346
7387
|
break;
|
|
7347
7388
|
}
|
|
@@ -7406,7 +7447,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
7406
7447
|
}
|
|
7407
7448
|
})();
|
|
7408
7449
|
try {
|
|
7409
|
-
await writeAnthropicStream(watchedStream, modelId, write, log12, { ...observer, abortSignal });
|
|
7450
|
+
await writeAnthropicStream(watchedStream, modelId, write, log12, { ...observer, abortSignal }, params.tools);
|
|
7410
7451
|
} finally {
|
|
7411
7452
|
stopForwardingAbort();
|
|
7412
7453
|
clearTimeout(idleTimer);
|
|
@@ -7497,6 +7538,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
7497
7538
|
if (!generateAbort.signal.aborted) generateAbort.abort();
|
|
7498
7539
|
}
|
|
7499
7540
|
}
|
|
7541
|
+
const requiredProps = toolRequiredProps(params.tools);
|
|
7500
7542
|
return {
|
|
7501
7543
|
id: "msg_" + Date.now(),
|
|
7502
7544
|
type: "message",
|
|
@@ -7508,7 +7550,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
7508
7550
|
type: "tool_use",
|
|
7509
7551
|
id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
|
|
7510
7552
|
name: tc.toolName,
|
|
7511
|
-
input:
|
|
7553
|
+
input: sanitizeToolInput(tc.input ?? {}, requiredProps.get(tc.toolName))
|
|
7512
7554
|
}))
|
|
7513
7555
|
],
|
|
7514
7556
|
stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
|
|
@@ -10516,14 +10558,12 @@ function planLaunchWizard(opts) {
|
|
|
10516
10558
|
}
|
|
10517
10559
|
|
|
10518
10560
|
// src/patcher.ts
|
|
10519
|
-
import { spawn } from "child_process";
|
|
10520
10561
|
import { createHash as createHash5 } from "crypto";
|
|
10521
10562
|
import {
|
|
10522
10563
|
copyFileSync as copyFileSync2,
|
|
10523
10564
|
existsSync as existsSync6,
|
|
10524
10565
|
mkdirSync as mkdirSync6,
|
|
10525
10566
|
readFileSync as readFileSync7,
|
|
10526
|
-
rmSync,
|
|
10527
10567
|
statSync as statSync2,
|
|
10528
10568
|
unlinkSync as unlinkSync2,
|
|
10529
10569
|
writeFileSync as writeFileSync5,
|
|
@@ -10531,313 +10571,189 @@ import {
|
|
|
10531
10571
|
closeSync as closeSync3,
|
|
10532
10572
|
realpathSync
|
|
10533
10573
|
} from "fs";
|
|
10534
|
-
import { homedir as homedir2
|
|
10574
|
+
import { homedir as homedir2 } from "os";
|
|
10535
10575
|
import { basename, join as join6 } from "path";
|
|
10536
10576
|
import pc12 from "picocolors";
|
|
10537
10577
|
import * as p11 from "@clack/prompts";
|
|
10538
10578
|
|
|
10539
|
-
// src/patch-
|
|
10540
|
-
var
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
// clodex:config:start
|
|
10548
|
-
const MODEL_CONFIG = {};
|
|
10549
|
-
// clodex:config:end
|
|
10550
|
-
|
|
10551
|
-
// ---- derive helpers --------------------------------------------------------
|
|
10552
|
-
// alias -> model id (only for entries that define an alias)
|
|
10553
|
-
const ALIAS_TO_ID = {};
|
|
10554
|
-
// The name Claude Code knows a model by: its alias when it has one, else its
|
|
10555
|
-
// canonical id. This single value is used for the Agent-tool enum, the
|
|
10556
|
-
// known-alias validator, the /model picker value, and the context-window table,
|
|
10557
|
-
// so the name the binary validates == the name it sends upstream == the name
|
|
10558
|
-
// the proxy echoes back == the key its context window is stored under.
|
|
10559
|
-
const IDENTITIES = [];
|
|
10560
|
-
// identity -> human label for the /model picker (falls back at use site)
|
|
10561
|
-
const DISPLAY_BY_IDENTITY = {};
|
|
10562
|
-
// lowercased alias AND id -> context-window tokens (only for models that set it)
|
|
10563
|
-
const CONTEXT_BY_KEY = {};
|
|
10564
|
-
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
10565
|
-
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
10566
|
-
if (spec.alias !== undefined) {
|
|
10567
|
-
const a = String(spec.alias).trim().toLowerCase();
|
|
10568
|
-
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
10569
|
-
throw new Error('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
10570
|
-
}
|
|
10571
|
-
ALIAS_TO_ID[a] = String(id);
|
|
10572
|
-
IDENTITIES.push(a);
|
|
10573
|
-
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
10574
|
-
} else {
|
|
10575
|
-
IDENTITIES.push(String(id));
|
|
10576
|
-
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
10579
|
+
// src/patch-transforms.ts
|
|
10580
|
+
var PatchApplyError = class extends Error {
|
|
10581
|
+
results;
|
|
10582
|
+
constructor(message, results) {
|
|
10583
|
+
super(message);
|
|
10584
|
+
this.name = "PatchApplyError";
|
|
10585
|
+
this.results = results;
|
|
10577
10586
|
}
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10587
|
+
};
|
|
10588
|
+
function formatPatchSiteLine(result) {
|
|
10589
|
+
return " " + result.status.padEnd(4) + " " + result.name + (result.extra ? " \u2014 " + result.extra : "");
|
|
10590
|
+
}
|
|
10591
|
+
function applyClodexPatches(source, config) {
|
|
10592
|
+
let js = source;
|
|
10593
|
+
const MODEL_CONFIG = config;
|
|
10594
|
+
const ALIAS_TO_ID = {};
|
|
10595
|
+
const IDENTITIES = [];
|
|
10596
|
+
const DISPLAY_BY_IDENTITY = {};
|
|
10597
|
+
const CONTEXT_BY_KEY = {};
|
|
10598
|
+
const report = [];
|
|
10599
|
+
const fail = (message) => {
|
|
10600
|
+
throw new PatchApplyError(message, report);
|
|
10601
|
+
};
|
|
10602
|
+
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
10603
|
+
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
10604
|
+
if (spec.alias !== void 0) {
|
|
10605
|
+
const a = String(spec.alias).trim().toLowerCase();
|
|
10606
|
+
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
10607
|
+
fail('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
10608
|
+
}
|
|
10609
|
+
ALIAS_TO_ID[a] = String(id);
|
|
10610
|
+
IDENTITIES.push(a);
|
|
10611
|
+
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
10612
|
+
} else {
|
|
10613
|
+
IDENTITIES.push(String(id));
|
|
10614
|
+
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
10583
10615
|
}
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10616
|
+
if (spec.context !== void 0) {
|
|
10617
|
+
const n = Number(spec.context);
|
|
10618
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
10619
|
+
fail('clodex patch: context for "' + id + '" must be a positive integer, got ' + spec.context);
|
|
10620
|
+
}
|
|
10621
|
+
if (/\[1m\]/i.test(String(spec.alias ?? "")) || /\[1m\]/i.test(id)) {
|
|
10622
|
+
fail(
|
|
10623
|
+
'clodex patch: "' + id + '" sets context but keeps the [1m] suffix \u2014 drop the suffix from both the id and the alias'
|
|
10624
|
+
);
|
|
10625
|
+
}
|
|
10626
|
+
if (spec.alias !== void 0) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
10627
|
+
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
10591
10628
|
}
|
|
10592
|
-
if (spec.alias !== undefined) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
10593
|
-
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
10594
|
-
}
|
|
10595
|
-
}
|
|
10596
|
-
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
10597
|
-
const MODELS = Object.keys(MODEL_CONFIG);
|
|
10598
|
-
if (MODELS.length === 0) throw new Error("clodex patch: MODEL_CONFIG is empty");
|
|
10599
|
-
|
|
10600
|
-
/** Picker/description label for an identity; falls back to the old wording. */
|
|
10601
|
-
function displayFor(identity, fallbackId) {
|
|
10602
|
-
return DISPLAY_BY_IDENTITY[identity] || "Custom model (" + fallbackId + ")";
|
|
10603
|
-
}
|
|
10604
|
-
|
|
10605
|
-
const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
|
|
10606
|
-
const q = (s) => JSON.stringify(s); // safe JS string literal
|
|
10607
|
-
|
|
10608
|
-
// ---- reporting -------------------------------------------------------------
|
|
10609
|
-
const report = [];
|
|
10610
|
-
function log(status, name, extra) {
|
|
10611
|
-
report.push({ status, name });
|
|
10612
|
-
const line = " " + status.padEnd(4) + " " + name + (extra ? " — " + extra : "");
|
|
10613
|
-
// NOTE: tweakcc reads this script's STDOUT as the JSON result, so every
|
|
10614
|
-
// diagnostic MUST go to stderr — a stray stdout write corrupts the output.
|
|
10615
|
-
console.error(line);
|
|
10616
|
-
}
|
|
10617
|
-
|
|
10618
|
-
/**
|
|
10619
|
-
* Apply exactly one regex replacement.
|
|
10620
|
-
* - marker: if present in js, treat as already-patched -> SKIP.
|
|
10621
|
-
* - expects exactly one match; 0 -> FAIL, >1 -> FAIL (ambiguous).
|
|
10622
|
-
* - fn(match, ...groups) returns the replacement text.
|
|
10623
|
-
* - required: on FAIL, throw (aborts the whole adhoc-patch).
|
|
10624
|
-
*/
|
|
10625
|
-
function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
|
|
10626
|
-
if (marker && js.includes(marker)) { log("SKIP", name, "already patched"); return; }
|
|
10627
|
-
const g = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g");
|
|
10628
|
-
const matches = js.match(g);
|
|
10629
|
-
const count = matches ? matches.length : 0;
|
|
10630
|
-
if (count === 0) {
|
|
10631
|
-
log("FAIL", name, "anchor not found");
|
|
10632
|
-
if (required) throw new Error("clodex patch: required patch failed: " + name);
|
|
10633
|
-
return;
|
|
10634
10629
|
}
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10630
|
+
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
10631
|
+
const MODELS = Object.keys(MODEL_CONFIG);
|
|
10632
|
+
if (MODELS.length === 0) fail("clodex patch: MODEL_CONFIG is empty");
|
|
10633
|
+
function displayFor(identity, fallbackId) {
|
|
10634
|
+
return DISPLAY_BY_IDENTITY[identity] || "Custom model (" + fallbackId + ")";
|
|
10639
10635
|
}
|
|
10640
|
-
const
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
// already present (anchor matched, but fn had nothing new to add) -> SKIP.
|
|
10645
|
-
if (noopIsSkip) { log("SKIP", name, "already patched"); return; }
|
|
10646
|
-
log("FAIL", name, "replacement made no change");
|
|
10647
|
-
if (required) throw new Error(name);
|
|
10648
|
-
return;
|
|
10636
|
+
const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
|
|
10637
|
+
const q = (s) => JSON.stringify(s);
|
|
10638
|
+
function log12(status, name, extra) {
|
|
10639
|
+
report.push(extra === void 0 ? { status, name } : { status, name, extra });
|
|
10649
10640
|
}
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
// PATCH 3 — known-alias validator list (drives "is this a known alias?").
|
|
10681
|
-
// Anchor: the master list literal, matched loosely as
|
|
10682
|
-
// ["sonnet","opus","haiku","fable", ...anything... ,"opusplan"] so it
|
|
10683
|
-
// tolerates new built-ins being added in the middle. Appending our identities
|
|
10684
|
-
// makes them recognized as first-class aliases everywhere the gate runs.
|
|
10685
|
-
// ---------------------------------------------------------------------------
|
|
10686
|
-
applyOnce(
|
|
10687
|
-
"PATCH 3: known-alias validator list",
|
|
10688
|
-
/\["sonnet","opus","haiku","fable"(?:,"[^"]+")*,"opusplan"(?:,"[^"]+")*\]/,
|
|
10689
|
-
(m) => extendAliasArray(m),
|
|
10690
|
-
{ required: true, noopIsSkip: true }
|
|
10691
|
-
);
|
|
10692
|
-
|
|
10693
|
-
// ---------------------------------------------------------------------------
|
|
10694
|
-
// PATCH 6 — alias resolver switch (IDENTITY mapping).
|
|
10695
|
-
// Anchor: case"best":{ ... } (the case"best":{ is unique). We inject
|
|
10696
|
-
// case"<alias>":return"<alias>"; right after it (before the switch's
|
|
10697
|
-
// default:return null).
|
|
10698
|
-
//
|
|
10699
|
-
// The mapping is deliberately an identity, NOT alias -> canonical id: the alias
|
|
10700
|
-
// IS the model's identity everywhere else in the patched binary (enum,
|
|
10701
|
-
// validator, picker, context table), and the MITM proxy resolves short alias
|
|
10702
|
-
// names as request model ids and echoes request bodies unrewritten. Resolving
|
|
10703
|
-
// to the canonical id here would make Claude Code send one name and look its
|
|
10704
|
-
// context window up under another — the exact mismatch that stopped auto-compact
|
|
10705
|
-
// from firing and killed agents with "Prompt is too long". The case still has to
|
|
10706
|
-
// EXIST (rather than be skipped) so the resolver returns the name instead of
|
|
10707
|
-
// falling through to default:return null.
|
|
10708
|
-
// Only aliases not already present are inserted, so a rerun (or a config
|
|
10709
|
-
// edit) tops up cleanly rather than duplicating cases.
|
|
10710
|
-
// ---------------------------------------------------------------------------
|
|
10711
|
-
{
|
|
10712
|
-
const missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
|
|
10713
|
-
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
10714
|
-
if (ALIASES.length === 0) {
|
|
10715
|
-
log("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
10716
|
-
} else {
|
|
10717
|
-
applyOnce(
|
|
10718
|
-
"PATCH 6: alias resolver switch",
|
|
10719
|
-
/(case"best":\{[^{}]*\})/,
|
|
10720
|
-
(m) => m + cases,
|
|
10721
|
-
{ required: true, noopIsSkip: true }
|
|
10722
|
-
);
|
|
10641
|
+
function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
|
|
10642
|
+
if (marker && js.includes(marker)) {
|
|
10643
|
+
log12("SKIP", name, "already patched");
|
|
10644
|
+
return;
|
|
10645
|
+
}
|
|
10646
|
+
const g = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g");
|
|
10647
|
+
const matches = js.match(g);
|
|
10648
|
+
const count = matches ? matches.length : 0;
|
|
10649
|
+
if (count === 0) {
|
|
10650
|
+
log12("FAIL", name, "anchor not found");
|
|
10651
|
+
if (required) fail("clodex patch: required patch failed: " + name);
|
|
10652
|
+
return;
|
|
10653
|
+
}
|
|
10654
|
+
if (count > 1) {
|
|
10655
|
+
log12("FAIL", name, "anchor matched " + count + " times (expected 1)");
|
|
10656
|
+
if (required) fail("clodex patch: ambiguous anchor: " + name);
|
|
10657
|
+
return;
|
|
10658
|
+
}
|
|
10659
|
+
const before = js;
|
|
10660
|
+
js = js.replace(regex, fn);
|
|
10661
|
+
if (js === before) {
|
|
10662
|
+
if (noopIsSkip) {
|
|
10663
|
+
log12("SKIP", name, "already patched");
|
|
10664
|
+
return;
|
|
10665
|
+
}
|
|
10666
|
+
log12("FAIL", name, "replacement made no change");
|
|
10667
|
+
if (required) fail(name);
|
|
10668
|
+
return;
|
|
10669
|
+
}
|
|
10670
|
+
log12("OK", name);
|
|
10723
10671
|
}
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10672
|
+
function extendAliasArray(arrLiteral) {
|
|
10673
|
+
const toAdd = IDENTITIES.filter((a) => !new RegExp('"' + reEsc(a) + '"').test(arrLiteral));
|
|
10674
|
+
if (toAdd.length === 0) return arrLiteral;
|
|
10675
|
+
return arrLiteral.replace(/\]\s*$/, "," + toAdd.map(q).join(",") + "]");
|
|
10676
|
+
}
|
|
10677
|
+
applyOnce(
|
|
10678
|
+
"PATCH 1: Agent tool model enum",
|
|
10679
|
+
/\.enum\((\["sonnet","opus","haiku"(?:,"[^"]+")*\])\)\.optional\(\)\.describe\(/,
|
|
10680
|
+
(_m, arr) => ".enum(" + extendAliasArray(arr) + ").optional().describe(",
|
|
10681
|
+
{ required: true, noopIsSkip: true }
|
|
10682
|
+
);
|
|
10683
|
+
applyOnce(
|
|
10684
|
+
"PATCH 3: known-alias validator list",
|
|
10685
|
+
/\["sonnet","opus","haiku","fable"(?:,"[^"]+")*,"opusplan"(?:,"[^"]+")*\]/,
|
|
10686
|
+
(m) => extendAliasArray(m),
|
|
10687
|
+
{ required: true, noopIsSkip: true }
|
|
10688
|
+
);
|
|
10689
|
+
{
|
|
10690
|
+
const missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
|
|
10691
|
+
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
10692
|
+
if (ALIASES.length === 0) {
|
|
10693
|
+
log12("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
10694
|
+
} else {
|
|
10695
|
+
applyOnce(
|
|
10696
|
+
"PATCH 6: alias resolver switch",
|
|
10697
|
+
/(case"best":\{[^{}]*\})/,
|
|
10698
|
+
(m) => m + cases,
|
|
10699
|
+
{ required: true, noopIsSkip: true }
|
|
10700
|
+
);
|
|
10701
|
+
}
|
|
10702
|
+
}
|
|
10703
|
+
{
|
|
10704
|
+
const missing = ALIASES.filter((a) => !new RegExp("value:" + reEsc(q(a))).test(js));
|
|
10705
|
+
const entries = missing.map(
|
|
10740
10706
|
// value = the alias (the name the user types and the binary sends);
|
|
10741
10707
|
// description = the real model label, e.g. "GPT-5.6 Sol (OpenAI (ChatGPT))".
|
|
10708
|
+
// (tweakcc's writeContent round-trips utf8 faithfully — verified — so the
|
|
10709
|
+
// old adhoc-patch ASCII-only constraint no longer applies.)
|
|
10742
10710
|
(a) => "{value:" + q(a) + ",label:" + q(a.charAt(0).toUpperCase() + a.slice(1)) + ",description:" + q(displayFor(a, ALIAS_TO_ID[a])) + "}"
|
|
10743
|
-
)
|
|
10744
|
-
.
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
{ required: false, noopIsSkip: true }
|
|
10756
|
-
);
|
|
10711
|
+
).join(",");
|
|
10712
|
+
const inject = missing.length ? "[" + entries + "].forEach(function(_o){if(!e.some(function(_i){return _i.value===_o.value}))e.push(_o)});" : "";
|
|
10713
|
+
if (ALIASES.length === 0) {
|
|
10714
|
+
log12("SKIP", "PATCH 5: model picker options", "no aliases configured");
|
|
10715
|
+
} else {
|
|
10716
|
+
applyOnce(
|
|
10717
|
+
"PATCH 5: model picker options",
|
|
10718
|
+
/(\?\[[\w$]+,r\]:\[r\];for\(let [\w$]+ of [\w$]+\)[\w$]+\(e,[\w$]+,t\);)/,
|
|
10719
|
+
(m) => m + inject,
|
|
10720
|
+
{ required: false, noopIsSkip: true }
|
|
10721
|
+
);
|
|
10722
|
+
}
|
|
10757
10723
|
}
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
// actually are. Best-effort (cosmetic). The text is spliced into a backtick
|
|
10765
|
-
// template literal, so backticks and interpolation openers are stripped.
|
|
10766
|
-
// ---------------------------------------------------------------------------
|
|
10767
|
-
{
|
|
10768
|
-
const safe = (s) => String(s).replace(/\`/g, "'").replace(/\$\{/g, "(");
|
|
10769
|
-
const listing = IDENTITIES.map(function (i) {
|
|
10770
|
-
const d = DISPLAY_BY_IDENTITY[i];
|
|
10771
|
-
return d ? safe(i) + " = " + safe(d) : safe(i);
|
|
10772
|
-
}).join("; ");
|
|
10773
|
-
applyOnce(
|
|
10774
|
-
"PATCH 4: Agent tool model description",
|
|
10775
|
-
/(describe\(\`Optional model override for this agent[^\`]*?)(\`\))/,
|
|
10776
|
-
(_m, body, close) =>
|
|
10777
|
-
body.includes("Additional custom models")
|
|
10778
|
-
? body + close
|
|
10779
|
-
: body + " Additional custom models: " + listing + "." + close,
|
|
10780
|
-
{ required: false, noopIsSkip: true }
|
|
10781
|
-
);
|
|
10782
|
-
}
|
|
10783
|
-
|
|
10784
|
-
// ---------------------------------------------------------------------------
|
|
10785
|
-
// PATCH 7 — per-model context window.
|
|
10786
|
-
//
|
|
10787
|
-
// Claude Code funnels EVERY context-window consumer (autocompact threshold,
|
|
10788
|
-
// /context, the countdown, statusline, cost/usage records, subagent budgets)
|
|
10789
|
-
// through one resolver function. We inject a baked table lookup at the TOP of
|
|
10790
|
-
// that resolver, so it wins over the 200k clamp and the global
|
|
10791
|
-
// CLAUDE_CODE_MAX_CONTEXT_TOKENS env override. Lookup is on the raw,
|
|
10792
|
-
// lowercased model string — alias and id are both in the table, so it hits
|
|
10793
|
-
// pre- or post-alias-resolution.
|
|
10794
|
-
//
|
|
10795
|
-
// Anchor: the resolver's exact body shape. Identifiers are wildcarded (they
|
|
10796
|
-
// churn per build); the (e,t) arity + 3-statement shape matches once.
|
|
10797
|
-
// ---------------------------------------------------------------------------
|
|
10798
|
-
if (Object.keys(CONTEXT_BY_KEY).length) {
|
|
10799
|
-
const MARKER = "/*ccpatch:ctx*/";
|
|
10800
|
-
const SNIPPET =
|
|
10801
|
-
MARKER + 'var _ccw=(' + JSON.stringify(CONTEXT_BY_KEY) + ')[String(e||"").trim().toLowerCase()];if(_ccw!==void 0)return _ccw;';
|
|
10802
|
-
|
|
10803
|
-
if (js.includes(MARKER)) {
|
|
10804
|
-
// Re-patching an already-patched binary: refresh the baked table in place
|
|
10805
|
-
// so a MODEL_CONFIG edit takes effect without a restore first.
|
|
10806
|
-
applyOnce(
|
|
10807
|
-
"PATCH 7: per-model context window (refresh)",
|
|
10808
|
-
/\/\*ccpatch:ctx\*\/var _ccw=\(\{[^{}]*\}\)\[[^\]]*\];if\(_ccw!==void 0\)return _ccw;/,
|
|
10809
|
-
() => SNIPPET,
|
|
10810
|
-
{ required: true, noopIsSkip: true }
|
|
10811
|
-
);
|
|
10812
|
-
} else {
|
|
10724
|
+
{
|
|
10725
|
+
const safe = (s) => String(s).replace(/`/g, "'").replace(/\$\{/g, "(");
|
|
10726
|
+
const listing = IDENTITIES.map(function(i) {
|
|
10727
|
+
const d = DISPLAY_BY_IDENTITY[i];
|
|
10728
|
+
return d ? safe(i) + " = " + safe(d) : safe(i);
|
|
10729
|
+
}).join("; ");
|
|
10813
10730
|
applyOnce(
|
|
10814
|
-
"PATCH
|
|
10815
|
-
/(
|
|
10816
|
-
(_m,
|
|
10817
|
-
{ required: true }
|
|
10731
|
+
"PATCH 4: Agent tool model description",
|
|
10732
|
+
/(describe\(`Optional model override for this agent[^`]*?)(`\))/,
|
|
10733
|
+
(_m, body, close) => body.includes("Additional custom models") ? body + close : body + " Additional custom models: " + listing + "." + close,
|
|
10734
|
+
{ required: false, noopIsSkip: true }
|
|
10818
10735
|
);
|
|
10819
10736
|
}
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10737
|
+
if (Object.keys(CONTEXT_BY_KEY).length) {
|
|
10738
|
+
const MARKER = "/*ccpatch:ctx*/";
|
|
10739
|
+
const SNIPPET = MARKER + "var _ccw=(" + JSON.stringify(CONTEXT_BY_KEY) + ')[String(e||"").trim().toLowerCase()];if(_ccw!==void 0)return _ccw;';
|
|
10740
|
+
if (js.includes(MARKER)) {
|
|
10741
|
+
applyOnce(
|
|
10742
|
+
"PATCH 7: per-model context window (refresh)",
|
|
10743
|
+
/\/\*ccpatch:ctx\*\/var _ccw=\(\{[^{}]*\}\)\[[^\]]*\];if\(_ccw!==void 0\)return _ccw;/,
|
|
10744
|
+
() => SNIPPET,
|
|
10745
|
+
{ required: true, noopIsSkip: true }
|
|
10746
|
+
);
|
|
10747
|
+
} else {
|
|
10748
|
+
applyOnce(
|
|
10749
|
+
"PATCH 7: per-model context window",
|
|
10750
|
+
/(function [\w$]+\(e,t\)\{)(let [\w$]+=[\w$]+\(\);if\([\w$]+!==void 0\)return [\w$]+;if\([\w$]+\(e,t\)\)return [\w$]+;return [\w$]+\(e,t\)\})/,
|
|
10751
|
+
(_m, head, body) => head + SNIPPET + body,
|
|
10752
|
+
{ required: true }
|
|
10753
|
+
);
|
|
10754
|
+
}
|
|
10836
10755
|
}
|
|
10837
|
-
|
|
10838
|
-
const MODEL_CONFIG = ${JSON.stringify(config, null, 2)};
|
|
10839
|
-
${CLODEX_CONFIG_END}`;
|
|
10840
|
-
return TEMPLATE.slice(0, start) + block + TEMPLATE.slice(end + CLODEX_CONFIG_END.length);
|
|
10756
|
+
return { content: js, results: report };
|
|
10841
10757
|
}
|
|
10842
10758
|
|
|
10843
10759
|
// src/patcher.ts
|
|
@@ -10988,29 +10904,16 @@ function pristineBackupPath(version, binaryPath) {
|
|
|
10988
10904
|
const tag = version.replace(/[^\w.-]+/g, "_") || basename(binaryPath);
|
|
10989
10905
|
return join6(backupDir(), `claude-${tag}.orig`);
|
|
10990
10906
|
}
|
|
10991
|
-
function
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
let output = "";
|
|
11002
|
-
child.stdout.on("data", (chunk) => {
|
|
11003
|
-
output += String(chunk);
|
|
11004
|
-
});
|
|
11005
|
-
child.stderr.on("data", (chunk) => {
|
|
11006
|
-
output += String(chunk);
|
|
11007
|
-
});
|
|
11008
|
-
child.on("error", reject);
|
|
11009
|
-
child.on("close", (code) => resolve2({ code: code ?? 1, output }));
|
|
11010
|
-
});
|
|
11011
|
-
}
|
|
11012
|
-
function summarizePatchOutput(output) {
|
|
11013
|
-
return output.split(/\r?\n/).filter((line) => /^\s*(OK|SKIP|FAIL)\s{2,}/.test(line) || /^clodex patch:/.test(line.trim())).map((line) => line.trimEnd());
|
|
10907
|
+
function summarizePatchResults(results) {
|
|
10908
|
+
const lines = results.map(formatPatchSiteLine);
|
|
10909
|
+
const ok = results.filter((r) => r.status === "OK").length;
|
|
10910
|
+
const skip = results.filter((r) => r.status === "SKIP").length;
|
|
10911
|
+
const failed = results.filter((r) => r.status === "FAIL");
|
|
10912
|
+
lines.push(`clodex patch: ${ok} applied, ${skip} skipped, ${failed.length} failed`);
|
|
10913
|
+
if (failed.length) {
|
|
10914
|
+
lines.push(`clodex patch: FAILED patches: ${failed.map((f) => f.name).join("; ")}`);
|
|
10915
|
+
}
|
|
10916
|
+
return lines;
|
|
11014
10917
|
}
|
|
11015
10918
|
async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
11016
10919
|
const backup = pristineBackupPath(version, binaryPath);
|
|
@@ -11024,41 +10927,48 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
11024
10927
|
copyFileSync2(binaryPath, backup);
|
|
11025
10928
|
}
|
|
11026
10929
|
copyFileSync2(backup, join6(backupDir(), "native-binary.backup"));
|
|
11027
|
-
const
|
|
11028
|
-
|
|
10930
|
+
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
10931
|
+
let results;
|
|
11029
10932
|
try {
|
|
11030
|
-
const
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
10933
|
+
const installation = await tryDetectInstallation({ path: binaryPath });
|
|
10934
|
+
const source = await readContent(installation);
|
|
10935
|
+
const patched = applyClodexPatches(source, desired.config);
|
|
10936
|
+
results = patched.results;
|
|
10937
|
+
await writeContent(installation, patched.content);
|
|
10938
|
+
} catch (err) {
|
|
10939
|
+
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
10940
|
+
if (opts.trace && detailLines.length) {
|
|
10941
|
+
process.stderr.write(`${detailLines.join("\n")}
|
|
10942
|
+
`);
|
|
11040
10943
|
}
|
|
11041
|
-
const manifest = {
|
|
11042
|
-
binaryPath,
|
|
11043
|
-
claudeVersion: version,
|
|
11044
|
-
configHash,
|
|
11045
|
-
patchedSize: statSync2(binaryPath).size,
|
|
11046
|
-
patchedSha256: sha256File(binaryPath),
|
|
11047
|
-
backupPath: backup,
|
|
11048
|
-
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11049
|
-
};
|
|
11050
|
-
writePatchManifest(manifest);
|
|
11051
|
-
const modelCount = Object.keys(desired.config).length;
|
|
11052
|
-
const aliasCount = Object.values(desired.config).filter((entry) => entry.alias).length;
|
|
11053
|
-
const windowCount = Object.values(desired.config).filter((entry) => entry.context).length;
|
|
11054
10944
|
return {
|
|
11055
|
-
ok:
|
|
11056
|
-
message: `
|
|
11057
|
-
detailLines
|
|
10945
|
+
ok: false,
|
|
10946
|
+
message: `Patch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
10947
|
+
detailLines
|
|
11058
10948
|
};
|
|
11059
|
-
} finally {
|
|
11060
|
-
rmSync(scriptPath, { force: true });
|
|
11061
10949
|
}
|
|
10950
|
+
if (opts.trace) {
|
|
10951
|
+
process.stderr.write(`${summarizePatchResults(results).join("\n")}
|
|
10952
|
+
`);
|
|
10953
|
+
}
|
|
10954
|
+
const manifest = {
|
|
10955
|
+
binaryPath,
|
|
10956
|
+
claudeVersion: version,
|
|
10957
|
+
configHash,
|
|
10958
|
+
patchedSize: statSync2(binaryPath).size,
|
|
10959
|
+
patchedSha256: sha256File(binaryPath),
|
|
10960
|
+
backupPath: backup,
|
|
10961
|
+
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10962
|
+
};
|
|
10963
|
+
writePatchManifest(manifest);
|
|
10964
|
+
const modelCount = Object.keys(desired.config).length;
|
|
10965
|
+
const aliasCount = Object.values(desired.config).filter((entry) => entry.alias).length;
|
|
10966
|
+
const windowCount = Object.values(desired.config).filter((entry) => entry.context).length;
|
|
10967
|
+
return {
|
|
10968
|
+
ok: true,
|
|
10969
|
+
message: `Patched claude ${version}: ${modelCount} model${modelCount === 1 ? "" : "s"}, ${aliasCount} alias${aliasCount === 1 ? "" : "es"}, ${windowCount} context window${windowCount === 1 ? "" : "s"}.`,
|
|
10970
|
+
detailLines: summarizePatchResults(results)
|
|
10971
|
+
};
|
|
11062
10972
|
}
|
|
11063
10973
|
async function runPatchCommand(opts = {}) {
|
|
11064
10974
|
const resolved = resolveClaudeBinaryForPatch();
|
|
@@ -11609,7 +11519,7 @@ ${pc13.bold("Usage:")}
|
|
|
11609
11519
|
|
|
11610
11520
|
${pc13.bold("Options:")}
|
|
11611
11521
|
--restore Restore the pristine (unpatched) Claude Code binary
|
|
11612
|
-
--trace Show
|
|
11522
|
+
--trace Show per-patch-site results (OK/SKIP/FAIL)
|
|
11613
11523
|
|
|
11614
11524
|
${pc13.bold("Behavior:")}
|
|
11615
11525
|
The patch map is built automatically from your clodex favorites and aliases
|