codex-model-router 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/bin/codex-model-router.js +2 -2
- package/lib/agent-reasoning-cli.js +160 -0
- package/lib/manifest.js +70 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ The normal installation uses free mode:
|
|
|
10
10
|
|
|
11
11
|
- The user-selected primary model handles conversation, clarification, follow-ups, final replies, and trivial work.
|
|
12
12
|
- Terra/high investigates, produces implementation-ready plans, verifies results, debugs, and replans.
|
|
13
|
-
- Luna/
|
|
13
|
+
- Luna/xhigh performs most clear, bounded, repetitive, or independently verifiable implementation work.
|
|
14
14
|
- Sol/medium is read-only and used only when Terra still cannot resolve core logic after focused investigation and one materially revised plan, or when the user explicitly requests Sol.
|
|
15
15
|
- Existing built-in and custom agents remain available and take precedence when they are a better match.
|
|
16
16
|
|
|
@@ -49,6 +49,17 @@ codex-model-router install --set-default
|
|
|
49
49
|
|
|
50
50
|
Running a later plain `install` returns package-managed default settings to free mode. User-modified model settings are preserved.
|
|
51
51
|
|
|
52
|
+
## Agent reasoning
|
|
53
|
+
|
|
54
|
+
The managed agent defaults are Terra/high, Luna/xhigh, and Sol/medium. Override all agents or individual agents during installation:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
codex-model-router install --agent-reasoning high
|
|
58
|
+
codex-model-router install --terra-reasoning xhigh --luna-reasoning low --sol-reasoning max
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Supported values are `none`, `low`, `medium`, `high`, `xhigh`, and `max`. Per-agent options override `--agent-reasoning`. Later installs preserve unchanged package-managed reasoning choices; manually edited agent files remain protected.
|
|
62
|
+
|
|
52
63
|
## Install
|
|
53
64
|
|
|
54
65
|
Run from npm:
|
|
@@ -69,7 +80,7 @@ Installing the CLI globally only makes the command available system-wide. Use `i
|
|
|
69
80
|
Install a specific GitHub release:
|
|
70
81
|
|
|
71
82
|
```sh
|
|
72
|
-
npm install -g https://github.com/Honguan/codex-model-router/archive/refs/tags/v2.
|
|
83
|
+
npm install -g https://github.com/Honguan/codex-model-router/archive/refs/tags/v2.1.0.tar.gz
|
|
73
84
|
codex-model-router install
|
|
74
85
|
```
|
|
75
86
|
|
|
@@ -119,6 +130,8 @@ Terra verifies separately whether implementation matches the plan and whether th
|
|
|
119
130
|
|
|
120
131
|
```text
|
|
121
132
|
codex-model-router install [--global] [--set-default] [--dry-run]
|
|
133
|
+
[--agent-reasoning <effort>]
|
|
134
|
+
[--terra-reasoning <effort>] [--luna-reasoning <effort>] [--sol-reasoning <effort>]
|
|
122
135
|
codex-model-router uninstall [--global] [--dry-run]
|
|
123
136
|
codex-model-router doctor [--global]
|
|
124
137
|
codex-model-router --version
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { runCli } from "../lib/agent-reasoning-cli.js";
|
|
3
3
|
|
|
4
|
-
process.exitCode = await
|
|
4
|
+
process.exitCode = await runCli(process.argv.slice(2), { output: console.log });
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
AGENT_NAMES,
|
|
7
|
+
configureAgentReasoning,
|
|
8
|
+
DEFAULT_AGENT_REASONING,
|
|
9
|
+
REASONING_EFFORTS,
|
|
10
|
+
VERSION
|
|
11
|
+
} from "./manifest.js";
|
|
12
|
+
import { run as runCore } from "./router.js";
|
|
13
|
+
|
|
14
|
+
const OPTION_TO_AGENT = Object.freeze({
|
|
15
|
+
"--terra-reasoning": "terra",
|
|
16
|
+
"--luna-reasoning": "luna",
|
|
17
|
+
"--sol-reasoning": "sol"
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function usage() {
|
|
21
|
+
return `codex-model-router ${VERSION}
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
codex-model-router install [--global] [--set-default] [--dry-run]
|
|
25
|
+
[--agent-reasoning <effort>]
|
|
26
|
+
[--terra-reasoning <effort>]
|
|
27
|
+
[--luna-reasoning <effort>]
|
|
28
|
+
[--sol-reasoning <effort>]
|
|
29
|
+
codex-model-router uninstall [--global] [--dry-run]
|
|
30
|
+
codex-model-router doctor [--global]
|
|
31
|
+
codex-model-router --version
|
|
32
|
+
|
|
33
|
+
Reasoning efforts: ${REASONING_EFFORTS.join(", ")}
|
|
34
|
+
Defaults: Terra=${DEFAULT_AGENT_REASONING.terra}, Luna=${DEFAULT_AGENT_REASONING.luna}, Sol=${DEFAULT_AGENT_REASONING.sol}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function optionValue(argv, index, option) {
|
|
38
|
+
const token = argv[index];
|
|
39
|
+
if (token.startsWith(`${option}=`)) return { value: token.slice(option.length + 1), consumed: 1 };
|
|
40
|
+
const value = argv[index + 1];
|
|
41
|
+
if (!value || value.startsWith("--")) return { error: `missing value for ${option}` };
|
|
42
|
+
return { value, consumed: 2 };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseReasoningOptions(argv) {
|
|
46
|
+
const command = argv[0];
|
|
47
|
+
const cleaned = command ? [command] : [];
|
|
48
|
+
const overrides = {};
|
|
49
|
+
let all;
|
|
50
|
+
|
|
51
|
+
for (let index = 1; index < argv.length;) {
|
|
52
|
+
const token = argv[index];
|
|
53
|
+
const option = token.split("=", 1)[0];
|
|
54
|
+
const agent = OPTION_TO_AGENT[option];
|
|
55
|
+
if (option !== "--agent-reasoning" && !agent) {
|
|
56
|
+
cleaned.push(token);
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (command !== "install") return { error: `${option} is supported only for install` };
|
|
61
|
+
const parsed = optionValue(argv, index, option);
|
|
62
|
+
if (parsed.error) return parsed;
|
|
63
|
+
const reasoning = parsed.value.toLowerCase();
|
|
64
|
+
if (!REASONING_EFFORTS.includes(reasoning)) {
|
|
65
|
+
return { error: `unsupported reasoning effort: ${parsed.value}` };
|
|
66
|
+
}
|
|
67
|
+
if (agent) overrides[agent] = reasoning;
|
|
68
|
+
else all = reasoning;
|
|
69
|
+
index += parsed.consumed;
|
|
70
|
+
}
|
|
71
|
+
return { cleaned, overrides, all };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function digest(content) {
|
|
75
|
+
return createHash("sha256").update(content).digest("hex");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readRegularText(path) {
|
|
79
|
+
try {
|
|
80
|
+
const info = await lstat(path);
|
|
81
|
+
if (!info.isFile() || info.isSymbolicLink()) return null;
|
|
82
|
+
return await readFile(path, "utf8");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error?.code === "ENOENT") return null;
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function activeReasoning(content) {
|
|
90
|
+
const match = content.match(/^\s*model_reasoning_effort\s*=\s*"([^"\r\n]+)"\s*(?:#.*)?$/m);
|
|
91
|
+
return match && REASONING_EFFORTS.includes(match[1]) ? match[1] : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function versionAtLeast(version, minimum) {
|
|
95
|
+
const parse = (value) => String(value || "0").split(".").slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
96
|
+
const left = parse(version);
|
|
97
|
+
const right = parse(minimum);
|
|
98
|
+
for (let index = 0; index < 3; index += 1) {
|
|
99
|
+
if (left[index] !== right[index]) return left[index] > right[index];
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function installedReasoning(argv, options) {
|
|
105
|
+
const global = argv.includes("--global");
|
|
106
|
+
const home = resolve(options.home ?? homedir());
|
|
107
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
108
|
+
const env = options.env ?? process.env;
|
|
109
|
+
const codexHome = global
|
|
110
|
+
? resolve(env.CODEX_HOME || join(home, ".codex"))
|
|
111
|
+
: join(cwd, ".codex");
|
|
112
|
+
const stateText = await readRegularText(join(codexHome, "model-router-state.json"));
|
|
113
|
+
if (!stateText) return { reasoning: {}, content: {}, packageVersion: null };
|
|
114
|
+
|
|
115
|
+
let state;
|
|
116
|
+
try {
|
|
117
|
+
state = JSON.parse(stateText);
|
|
118
|
+
} catch {
|
|
119
|
+
return { reasoning: {}, content: {}, packageVersion: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const physicalCodexHome = await realpath(codexHome);
|
|
123
|
+
const reasoning = {};
|
|
124
|
+
const content = {};
|
|
125
|
+
for (const name of AGENT_NAMES) {
|
|
126
|
+
const path = join(physicalCodexHome, "agents", `${name}.toml`);
|
|
127
|
+
const tracked = state.files?.[name];
|
|
128
|
+
const current = await readRegularText(path);
|
|
129
|
+
if (!tracked || !current || resolve(tracked.path) !== resolve(path) || digest(current) !== tracked.hash) continue;
|
|
130
|
+
const effort = activeReasoning(current);
|
|
131
|
+
if (!effort) continue;
|
|
132
|
+
reasoning[name] = effort;
|
|
133
|
+
content[name] = current;
|
|
134
|
+
}
|
|
135
|
+
return { reasoning, content, packageVersion: state.packageVersion };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function runCli(argv, options = {}) {
|
|
139
|
+
const output = options.output ?? console.log;
|
|
140
|
+
if (!argv.length || argv.includes("--help") || argv.includes("-h")) {
|
|
141
|
+
output(usage());
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const parsed = parseReasoningOptions(argv);
|
|
146
|
+
if (parsed.error) {
|
|
147
|
+
output(`${parsed.error}\n\n${usage()}`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const installed = await installedReasoning(parsed.cleaned, options);
|
|
152
|
+
const selected = { ...DEFAULT_AGENT_REASONING };
|
|
153
|
+
if (versionAtLeast(installed.packageVersion, "2.1.0")) Object.assign(selected, installed.reasoning);
|
|
154
|
+
if (parsed.all) {
|
|
155
|
+
for (const name of AGENT_NAMES) selected[name] = parsed.all;
|
|
156
|
+
}
|
|
157
|
+
Object.assign(selected, parsed.overrides);
|
|
158
|
+
configureAgentReasoning(selected, installed.content);
|
|
159
|
+
return runCore(parsed.cleaned, options);
|
|
160
|
+
}
|
package/lib/manifest.js
CHANGED
|
@@ -87,7 +87,7 @@ Return only the artifact required by the current stage.
|
|
|
87
87
|
const luna = `name = "luna"
|
|
88
88
|
description = "Implements clear, bounded work from an approved plan."
|
|
89
89
|
model = "gpt-5.6-luna"
|
|
90
|
-
model_reasoning_effort = "
|
|
90
|
+
model_reasoning_effort = "xhigh"
|
|
91
91
|
sandbox_mode = "workspace-write"
|
|
92
92
|
developer_instructions = """
|
|
93
93
|
Implement the current approved plan with the smallest coherent change.
|
|
@@ -155,45 +155,91 @@ Omit irrelevant fields and vague instructions. Preserve verified work, revise on
|
|
|
155
155
|
Verification answers separately: did implementation match the plan, and did the plan plus implementation satisfy the request? Use PASS, IMPLEMENTATION_FIX, PLAN_REVISION, EVIDENCE_GAP, or REQUIREMENT_CLARIFICATION.
|
|
156
156
|
`;
|
|
157
157
|
|
|
158
|
-
export const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
export const REASONING_EFFORTS = Object.freeze([
|
|
159
|
+
"none",
|
|
160
|
+
"low",
|
|
161
|
+
"medium",
|
|
162
|
+
"high",
|
|
163
|
+
"xhigh",
|
|
164
|
+
"max"
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
export const DEFAULT_AGENT_REASONING = Object.freeze({
|
|
168
|
+
terra: "high",
|
|
169
|
+
luna: "xhigh",
|
|
170
|
+
sol: "medium"
|
|
164
171
|
});
|
|
165
172
|
|
|
166
|
-
export const MANAGED_FILE_NAMES = Object.freeze(["terra", "luna", "sol", "skill", "planning"]);
|
|
167
173
|
export const AGENT_NAMES = Object.freeze(["terra", "luna", "sol"]);
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
175
|
+
const AGENT_BASE_TEMPLATES = Object.freeze({ terra, luna, sol });
|
|
176
|
+
|
|
177
|
+
function withReasoning(content, reasoning) {
|
|
178
|
+
const pattern = /^model_reasoning_effort = "[^"]+"$/m;
|
|
179
|
+
if (!pattern.test(content)) throw new Error("agent template is missing model_reasoning_effort");
|
|
180
|
+
return content.replace(pattern, `model_reasoning_effort = "${reasoning}"`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const TEMPLATES = {
|
|
184
|
+
terra: { relative: Object.freeze(["agents", "terra.toml"]), content: terra },
|
|
185
|
+
luna: { relative: Object.freeze(["agents", "luna.toml"]), content: luna },
|
|
186
|
+
sol: { relative: Object.freeze(["agents", "sol.toml"]), content: sol },
|
|
187
|
+
skill: { relative: Object.freeze(["model-router", "SKILL.md"]), content: skill },
|
|
188
|
+
planning: { relative: Object.freeze(["implementation-planning", "SKILL.md"]), content: planning }
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const MANAGED_FILE_NAMES = Object.freeze(["terra", "luna", "sol", "skill", "planning"]);
|
|
176
192
|
|
|
177
|
-
export const
|
|
178
|
-
terra:
|
|
193
|
+
export const LEGACY_TEMPLATES = {
|
|
194
|
+
terra: [],
|
|
195
|
+
luna: [legacyLunaV12, withReasoning(luna, "max")],
|
|
196
|
+
sol: [legacySolWorkspaceWrite, legacySolReadOnly],
|
|
197
|
+
skill: [legacySkillWorkspaceWrite, legacySkillReadOnly],
|
|
198
|
+
planning: []
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const AGENT_EXPECTATIONS = {
|
|
202
|
+
terra: {
|
|
179
203
|
name: "terra",
|
|
180
204
|
model: "gpt-5.6-terra",
|
|
181
205
|
model_reasoning_effort: "high",
|
|
182
206
|
sandbox_mode: "read-only"
|
|
183
|
-
}
|
|
184
|
-
luna:
|
|
207
|
+
},
|
|
208
|
+
luna: {
|
|
185
209
|
name: "luna",
|
|
186
210
|
model: "gpt-5.6-luna",
|
|
187
|
-
model_reasoning_effort: "
|
|
211
|
+
model_reasoning_effort: "xhigh",
|
|
188
212
|
sandbox_mode: "workspace-write"
|
|
189
|
-
}
|
|
190
|
-
sol:
|
|
213
|
+
},
|
|
214
|
+
sol: {
|
|
191
215
|
name: "sol",
|
|
192
216
|
model: "gpt-5.6-sol",
|
|
193
217
|
model_reasoning_effort: "medium",
|
|
194
218
|
sandbox_mode: "read-only"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export function configureAgentReasoning(overrides = {}, migrateFrom = {}) {
|
|
223
|
+
const selected = { ...DEFAULT_AGENT_REASONING, ...overrides };
|
|
224
|
+
for (const [name, reasoning] of Object.entries(selected)) {
|
|
225
|
+
if (!AGENT_NAMES.includes(name)) throw new Error(`unknown agent: ${name}`);
|
|
226
|
+
if (!REASONING_EFFORTS.includes(reasoning)) {
|
|
227
|
+
throw new Error(`unsupported reasoning effort for ${name}: ${reasoning}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const name of AGENT_NAMES) {
|
|
232
|
+
const previous = migrateFrom[name];
|
|
233
|
+
if (typeof previous === "string" && !LEGACY_TEMPLATES[name].includes(previous)) {
|
|
234
|
+
LEGACY_TEMPLATES[name].push(previous);
|
|
235
|
+
}
|
|
236
|
+
TEMPLATES[name].content = withReasoning(AGENT_BASE_TEMPLATES[name], selected[name]);
|
|
237
|
+
AGENT_EXPECTATIONS[name].model_reasoning_effort = selected[name];
|
|
238
|
+
}
|
|
239
|
+
return Object.freeze({ ...selected });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
configureAgentReasoning();
|
|
197
243
|
|
|
198
244
|
export const SKILL_EXPECTATIONS = Object.freeze({
|
|
199
245
|
skill: Object.freeze({
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-model-router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Safely install adaptive free-mode Codex routing for Terra, Luna, and Sol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"codex-model-router": "./bin/codex-model-router.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"check": "node --check bin/codex-model-router.js && node --check lib/manifest.js && node --check lib/router.js && node --check lib/router-core.js && node --check lib/toml.js && node --check scripts/release-metadata.js && node --check scripts/verify-published.js",
|
|
10
|
+
"check": "node --check bin/codex-model-router.js && node --check lib/agent-reasoning-cli.js && node --check lib/manifest.js && node --check lib/router.js && node --check lib/router-core.js && node --check lib/toml.js && node --check scripts/release-metadata.js && node --check scripts/verify-published.js",
|
|
11
11
|
"test": "node --test",
|
|
12
12
|
"test:package": "node scripts/package-smoke.js"
|
|
13
13
|
},
|