codex-copilot-dx 0.9.12 → 0.9.13
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 +4 -2
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/auto-review-model.mjs +47 -8
- package/src/cli-options.mjs +2 -2
- package/src/models.mjs +10 -0
- package/src/responses-handler.mjs +11 -8
- package/src/user-settings.mjs +32 -7
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The adapter handles response history, encrypted reasoning state, function/custom
|
|
|
20
20
|
|
|
21
21
|
Codex can select Standard or Fast without changing CCDX configuration. When the client requests a model with `service_tier: "priority"`, CCDX uses its exact `-fast` model only when the live Copilot catalog advertises that variant as enabled, selectable, OpenAI-owned, and Responses-only. Standard mode continues to use the base model; CCDX does not invent Fast availability when the upstream catalog does not provide it. This retains the established `gpt-5.6-sol` / `gpt-5.6-sol-fast` mapping and applies the same exact-match rule to future models.
|
|
22
22
|
|
|
23
|
-
Codex Auto-review uses the hidden `codex-auto-review` model ID. CCDX maps it to Copilot's `gpt-6.1-sol` Responses model by default, independently of the interactive Fast/Standard selection, and logs both model IDs when the mapping is used.
|
|
23
|
+
Codex Auto-review uses the hidden `codex-auto-review` model ID. CCDX maps it to Copilot's `gpt-6.1-sol` Responses model by default, independently of the interactive Fast/Standard selection, and logs both model IDs when the mapping is used. Without a saved reasoning override, an omitted Auto-review effort defaults to `low`; explicit client efforts are preserved. A saved override changes only review effort, preserving other reasoning fields. The review target and effort can be changed interactively without reinstalling or restarting a current CCDX adapter.
|
|
24
24
|
|
|
25
25
|
### GPT-6 model catalog
|
|
26
26
|
|
|
@@ -117,7 +117,9 @@ ccdx auto-review-model
|
|
|
117
117
|
|
|
118
118
|
The selector first queries the running adapter, including its last-known-good model list when live refresh is unavailable, then falls back to a local model cache no older than seven days. It offers model IDs that advertise a Responses endpoint and are not explicitly hidden from the model picker. This selection does not itself verify policy or inference access.
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
Enter a model number, then a reasoning-effort number. The second menu reuses the same model-directory lookup and lists only advertised efforts. GPT-6.1 Sol excludes `none` and `minimal`, even if upstream metadata incorrectly lists them. Enter keeps the current valid effort or selects `low` for a new selection when advertised; `0` follows the client (or `low` if the request omits effort). If effort metadata is absent, only `0` is offered. Enter `q` at either step to cancel without saving either choice.
|
|
121
|
+
|
|
122
|
+
Both choices are saved together in `~/.config/codex-copilot-dx/config.json`, or under `XDG_CONFIG_HOME` when set. Choosing `gpt-6.1-sol` clears the model override and restores the package default. A selected effort is saved as `auto_review_reasoning: { "model": "gpt-6.1-sol", "effort": "medium" }` and applies only when its model matches the effective review target. Existing saved models, including `gpt-5.5`, and environment model overrides retain their precedence; changing the model via an environment variable does not transfer another model's effort. Choose `0` to clear the effort override; its `null` marker remembers client-following so Enter preserves that choice next time. Users who do not configure effort retain their existing behavior. Adapters running 0.9.13 or newer read both settings on the next Auto-review request; older adapters must first be upgraded and restarted. No client patch, foreground-model change, Fast mapping, permission or approval-policy change is involved.
|
|
121
123
|
|
|
122
124
|
### Runtime caches
|
|
123
125
|
|
package/bin/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { cliCommandName, cliHelp, parseAdapterAddressOptions, parseAdapterProbeO
|
|
|
16
16
|
import { formatAdapterStatus, readAdapterStatus } from "../src/cli-status.mjs";
|
|
17
17
|
import { closeHttpServer } from "../src/shutdown.mjs";
|
|
18
18
|
import { runAutoReviewModelCommand } from "../src/auto-review-model.mjs";
|
|
19
|
-
import { autoReviewModelPreference, responseHistoryLimitPreference, terminalAnimationPreference } from "../src/user-settings.mjs";
|
|
19
|
+
import { autoReviewModelPreference, autoReviewPreference, responseHistoryLimitPreference, terminalAnimationPreference } from "../src/user-settings.mjs";
|
|
20
20
|
import { fetchLiveCopilotModels, formatLiveCopilotModels } from "../src/cli-models.mjs";
|
|
21
21
|
import { runAuthCommand } from "../src/cli-auth.mjs";
|
|
22
22
|
import { createProfileRuntime } from "../src/profile-runtime.mjs";
|
|
@@ -340,7 +340,7 @@ try {
|
|
|
340
340
|
const terminalAnimationTheme = terminalAnimationPreference().theme;
|
|
341
341
|
const codexModelCatalog = createCodexModelCatalog();
|
|
342
342
|
activeServer = await startAdapter(ADAPTER_PORT, ADAPTER_HOST, {
|
|
343
|
-
autoReviewModelResolver: () =>
|
|
343
|
+
autoReviewModelResolver: () => autoReviewPreference(),
|
|
344
344
|
codexClient: profileRuntime.codexClient,
|
|
345
345
|
codexModelCatalog,
|
|
346
346
|
codexModelRegistry: profileModels.codexRegistry,
|
package/package.json
CHANGED
|
@@ -3,12 +3,13 @@ import { createInterface } from "node:readline/promises";
|
|
|
3
3
|
import { loadModelCache } from "./model-cache.mjs";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_CODEX_AUTO_REVIEW_MODEL,
|
|
6
|
+
autoReviewReasoningEfforts,
|
|
6
7
|
responsesModelIdsFromCopilotModels,
|
|
7
8
|
} from "./models.mjs";
|
|
8
9
|
import { parseAdapterProbeOptions } from "./cli-options.mjs";
|
|
9
10
|
import { adapterBaseUrl } from "./running-adapter.mjs";
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
+
autoReviewPreference,
|
|
12
13
|
readUserSettings,
|
|
13
14
|
writeAutoReviewModel,
|
|
14
15
|
} from "./user-settings.mjs";
|
|
@@ -28,6 +29,13 @@ function sortedModelIds(modelIds, currentModel) {
|
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function catalogReasoningEfforts(models, modelIds) {
|
|
33
|
+
const data = Array.isArray(models) ? models : models?.data;
|
|
34
|
+
return Object.fromEntries(modelIds.map((id) => [id,
|
|
35
|
+
autoReviewReasoningEfforts(data?.find((model) => String(model?.id || "").trim() === id)),
|
|
36
|
+
]));
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export async function loadAutoReviewModelCatalog({
|
|
32
40
|
env = process.env,
|
|
33
41
|
fetchImpl = fetch,
|
|
@@ -52,7 +60,8 @@ export async function loadAutoReviewModelCatalog({
|
|
|
52
60
|
const modelIds = responsesModelIdsFromCopilotModels(models);
|
|
53
61
|
if (modelIds.length) {
|
|
54
62
|
const fallback = response.headers.get("X-CCDX-Model-Source") === "last-known-good";
|
|
55
|
-
return { modelIds,
|
|
63
|
+
return { modelIds, reasoningEfforts: catalogReasoningEfforts(models, modelIds),
|
|
64
|
+
source: fallback ? "running adapter cache" : "running adapter" };
|
|
56
65
|
}
|
|
57
66
|
liveError = "adapter returned no selectable Responses models";
|
|
58
67
|
}
|
|
@@ -66,7 +75,7 @@ export async function loadAutoReviewModelCatalog({
|
|
|
66
75
|
|
|
67
76
|
const cached = loadModelCacheFn({ home });
|
|
68
77
|
const modelIds = responsesModelIdsFromCopilotModels(cached);
|
|
69
|
-
if (modelIds.length) return { modelIds, source: "local model cache" };
|
|
78
|
+
if (modelIds.length) return { modelIds, reasoningEfforts: catalogReasoningEfforts(cached, modelIds), source: "local model cache" };
|
|
70
79
|
throw new Error(`No selectable Responses models are available (${liveError}). Start ccdx once to refresh the model list.`);
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -85,13 +94,14 @@ export async function runAutoReviewModelCommand({
|
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
readUserSettings({ env, home, strict: true });
|
|
88
|
-
const current =
|
|
97
|
+
const current = autoReviewPreference({ env, home });
|
|
89
98
|
const catalog = await loadCatalog({ env, fetchImpl, home });
|
|
90
99
|
const modelIds = sortedModelIds(catalog.modelIds, current.model);
|
|
91
100
|
if (!modelIds.length) throw new Error("No selectable Responses models are available");
|
|
92
101
|
|
|
93
102
|
output.write(`${commandName} auto-review-model\n`);
|
|
94
103
|
output.write(`Current: ${current.model} (${current.source})\n`);
|
|
104
|
+
output.write(`Reasoning: ${current.reasoningEffort || "follow client (low when omitted)"}\n`);
|
|
95
105
|
output.write(`Responses models from ${catalog.source}:\n`);
|
|
96
106
|
modelIds.forEach((model, index) => {
|
|
97
107
|
const markers = [];
|
|
@@ -111,6 +121,7 @@ export async function runAutoReviewModelCommand({
|
|
|
111
121
|
});
|
|
112
122
|
|
|
113
123
|
let selectedModel;
|
|
124
|
+
let selectedEffort;
|
|
114
125
|
try {
|
|
115
126
|
while (!selectedModel) {
|
|
116
127
|
const answer = String(await ask(`Select a model [${defaultIndex + 1}], or q to cancel: `) || "").trim();
|
|
@@ -125,17 +136,45 @@ export async function runAutoReviewModelCommand({
|
|
|
125
136
|
output.write(`Enter a number from 1 to ${modelIds.length}, or q to cancel.\n`);
|
|
126
137
|
}
|
|
127
138
|
}
|
|
139
|
+
const efforts = catalog.reasoningEfforts?.[selectedModel] || [];
|
|
140
|
+
const choices = [null, ...efforts];
|
|
141
|
+
const currentEffort = selectedModel === current.model ? current.reasoningEffort : undefined;
|
|
142
|
+
const effortIndex = selectedModel === current.model && current.reasoningEffort === null
|
|
143
|
+
? 0 : currentEffort ? efforts.indexOf(currentEffort) + 1 : efforts.indexOf("low") + 1;
|
|
144
|
+
output.write(`Reasoning for ${selectedModel}:\n`);
|
|
145
|
+
output.write(" 0. Follow client (low when omitted)\n");
|
|
146
|
+
efforts.forEach((effort, index) => {
|
|
147
|
+
const markers = [];
|
|
148
|
+
if (effort === currentEffort) markers.push("current");
|
|
149
|
+
if (effort === "low") markers.push("default");
|
|
150
|
+
output.write(` ${index + 1}. ${effort}${markers.length ? ` [${markers.join(", ")}]` : ""}\n`);
|
|
151
|
+
});
|
|
152
|
+
if (!efforts.length) output.write("No reasoning efforts are advertised; no override will be set.\n");
|
|
153
|
+
while (selectedEffort === undefined) {
|
|
154
|
+
const answer = String(await ask(`Select reasoning [${effortIndex}], or q to cancel: `) || "").trim();
|
|
155
|
+
if (["q", "quit"].includes(answer.toLowerCase())) {
|
|
156
|
+
output.write("No changes made.\n");
|
|
157
|
+
return { changed: false, cancelled: true, model: current.model };
|
|
158
|
+
}
|
|
159
|
+
const selectedIndex = answer === "" ? effortIndex : Number(answer);
|
|
160
|
+
if (Number.isInteger(selectedIndex) && selectedIndex >= 0 && selectedIndex < choices.length) {
|
|
161
|
+
selectedEffort = choices[selectedIndex];
|
|
162
|
+
} else {
|
|
163
|
+
output.write(`Enter a number from 0 to ${efforts.length}, or q to cancel.\n`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
128
166
|
} finally {
|
|
129
167
|
readline?.close();
|
|
130
168
|
}
|
|
131
169
|
|
|
132
170
|
const savedModel = selectedModel === DEFAULT_CODEX_AUTO_REVIEW_MODEL ? "" : selectedModel;
|
|
133
|
-
const result = writeAutoReviewModel(savedModel, { env, home });
|
|
171
|
+
const result = writeAutoReviewModel(savedModel, { env, home, reasoningEffort: selectedEffort });
|
|
134
172
|
output.write(`${result.changed ? "Saved" : "Kept"} Auto Review model: ${selectedModel}\n`);
|
|
173
|
+
output.write(`Reasoning: ${selectedEffort || "follow client (low when omitted)"}\n`);
|
|
135
174
|
if (String(env.CCDX_AUTO_REVIEW_MODEL || "").trim()) {
|
|
136
|
-
output.write(`CCDX_AUTO_REVIEW_MODEL=${current.model} remains the effective override until it is unset.\n`);
|
|
175
|
+
output.write(`CCDX_AUTO_REVIEW_MODEL=${current.model} remains the effective override until it is unset. Reasoning is bound to ${selectedModel}.\n`);
|
|
137
176
|
} else {
|
|
138
|
-
output.write("The running adapter will use this model on the next Auto Review request.\n");
|
|
177
|
+
output.write("The running adapter will use this model on the next Auto Review request. Reasoning selection requires an adapter running 0.9.13 or newer.\n");
|
|
139
178
|
}
|
|
140
|
-
return { ...result, cancelled: false, model: selectedModel };
|
|
179
|
+
return { ...result, cancelled: false, model: selectedModel, reasoningEffort: selectedEffort };
|
|
141
180
|
}
|
package/src/cli-options.mjs
CHANGED
|
@@ -355,7 +355,7 @@ function topicHelp(name, topic) {
|
|
|
355
355
|
"enable-image": `Usage:\n ${name} enable-image\n\nInteractively configures an HTTPS image API endpoint (base URLs are also accepted) and API key, detects its supported image model and protocol, installs CCDX image guidance, and checks the local image tool for Codex App without generating an image. A blank key preserves the saved key only for the same origin; a new origin requires explicit key entry. Concurrent unrelated config edits are preserved.`,
|
|
356
356
|
"disable-image": `Usage:\n ${name} disable-image\n\nDisables the local CCDX image tool and removes its stored API credential and unmodified CCDX image guidance.`,
|
|
357
357
|
"image-status": `Usage:\n ${name} image-status\n\nShows provider configuration and probes local image-tool readiness without generating an image or exposing its API key.`,
|
|
358
|
-
"auto-review-model": `Usage:\n ${name} auto-review-model\n\
|
|
358
|
+
"auto-review-model": `Usage:\n ${name} auto-review-model\n\nSelect a Responses model, then an advertised reasoning effort. Enter keeps the default/current choice; q cancels both steps without saving. Effort 0 follows the client (low when omitted). Settings apply only to Auto-review on the next request; no client patch or restart is required for adapters running 0.9.13 or newer.`,
|
|
359
359
|
update: `Usage:\n ${name} update [npm|github]\n\nUpdates the global package from the configured npm registry or GitHub main. With no source, an interactive terminal prompts for one.`,
|
|
360
360
|
version: `Usage:\n ${name} --version`,
|
|
361
361
|
};
|
|
@@ -398,7 +398,7 @@ Commands:
|
|
|
398
398
|
enable-image Configure and enable the optional image provider
|
|
399
399
|
disable-image Disable the image provider and remove its credential
|
|
400
400
|
image-status Show the optional image provider status
|
|
401
|
-
auto-review-model Select the Codex Auto-review
|
|
401
|
+
auto-review-model Select the Codex Auto-review model and reasoning effort
|
|
402
402
|
update Update the global package from npm or GitHub
|
|
403
403
|
|
|
404
404
|
Run ${name} <command> --help for command details.`;
|
package/src/models.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export const CODEX_AUTO_REVIEW_MODEL = "codex-auto-review";
|
|
2
2
|
export const DEFAULT_CODEX_AUTO_REVIEW_MODEL = "gpt-6.1-sol";
|
|
3
3
|
export const CODEX_GPT6_MODEL = "gpt-6-astra";
|
|
4
|
+
export const AUTO_REVIEW_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max", "minimal", "none"];
|
|
5
|
+
|
|
6
|
+
export function autoReviewReasoningEfforts(model) {
|
|
7
|
+
const advertised = model?.capabilities?.supports?.reasoning_effort;
|
|
8
|
+
const supported = new Set(Array.isArray(advertised) ? advertised : []);
|
|
9
|
+
const id = String(model?.id || "").trim();
|
|
10
|
+
const gpt61Sol = id === "gpt-6.1-sol" || id === "gpt-6.1-sol-fast";
|
|
11
|
+
return AUTO_REVIEW_REASONING_EFFORTS.filter((effort) => supported.has(effort)
|
|
12
|
+
&& !(gpt61Sol && ["none", "minimal"].includes(effort)));
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
function hasOpenAIEndpoint(model) {
|
|
6
16
|
const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
@@ -90,10 +90,13 @@ function unsupportedCustomToolsError(model) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function resolveRequestModel(model, openAIModelEnv, autoReviewModelResolver) {
|
|
93
|
-
const
|
|
94
|
-
?
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const preference = model === CODEX_AUTO_REVIEW_MODEL && typeof autoReviewModelResolver === "function"
|
|
94
|
+
? autoReviewModelResolver() : undefined;
|
|
95
|
+
const resolved = resolveOpenAIModel(model, openAIModelEnv, {
|
|
96
|
+
autoReviewModel: typeof preference === "string" ? preference : preference?.model,
|
|
97
|
+
});
|
|
98
|
+
return { ...resolved, reasoningEffort: preference?.model === resolved.upstreamModel
|
|
99
|
+
? preference.reasoningEffort : undefined };
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
function stripUnsupportedGpt6ServiceTier(body, requestedModel, upstreamModel, priorityTierModel) {
|
|
@@ -274,8 +277,8 @@ export function createResponsesHandler(options) {
|
|
|
274
277
|
stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
|
|
275
278
|
if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
|
|
276
279
|
delete prepared.body.service_tier;
|
|
277
|
-
if (prepared.body.reasoning?.effort === undefined) {
|
|
278
|
-
prepared.body.reasoning = { ...prepared.body.reasoning, effort: "low" };
|
|
280
|
+
if (resolvedModel.reasoningEffort || prepared.body.reasoning?.effort === undefined) {
|
|
281
|
+
prepared.body.reasoning = { ...prepared.body.reasoning, effort: resolvedModel.reasoningEffort || "low" };
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
|
@@ -544,8 +547,8 @@ export function createResponsesCompactHandler(options) {
|
|
|
544
547
|
stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
|
|
545
548
|
if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
|
|
546
549
|
delete prepared.body.service_tier;
|
|
547
|
-
if (prepared.body.reasoning?.effort === undefined) {
|
|
548
|
-
prepared.body.reasoning = { ...prepared.body.reasoning, effort: "low" };
|
|
550
|
+
if (resolvedModel.reasoningEffort || prepared.body.reasoning?.effort === undefined) {
|
|
551
|
+
prepared.body.reasoning = { ...prepared.body.reasoning, effort: resolvedModel.reasoningEffort || "low" };
|
|
549
552
|
}
|
|
550
553
|
}
|
|
551
554
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
package/src/user-settings.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { atomicWriteFileIfChangedSync } from "./atomic-file.mjs";
|
|
5
|
-
import { DEFAULT_CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
|
|
5
|
+
import { AUTO_REVIEW_REASONING_EFFORTS, DEFAULT_CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
|
|
6
6
|
import { loadRuntimeConfig, RUNTIME_DEFAULTS } from "./runtime-config.mjs";
|
|
7
7
|
import {
|
|
8
8
|
MIB,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "./terminal-animation.mjs";
|
|
18
18
|
|
|
19
19
|
const AUTO_REVIEW_MODEL_KEY = "auto_review_model";
|
|
20
|
+
const AUTO_REVIEW_REASONING_KEY = "auto_review_reasoning";
|
|
20
21
|
const TERMINAL_ANIMATION_KEY = "terminal_animation";
|
|
21
22
|
const RESPONSE_HISTORY_MAX_MIB_KEY = "response_history_max_mib";
|
|
22
23
|
const DECODED_BODY_LIMIT_MIB_KEY = "decoded_body_limit_mib";
|
|
@@ -47,6 +48,15 @@ export function readUserSettings({ env = process.env, home = os.homedir(), stric
|
|
|
47
48
|
throw invalidSettings(filePath, `${AUTO_REVIEW_MODEL_KEY} must be a non-empty string`);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
if (Object.hasOwn(parsed, AUTO_REVIEW_REASONING_KEY)) {
|
|
52
|
+
const value = parsed[AUTO_REVIEW_REASONING_KEY];
|
|
53
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
54
|
+
|| typeof value.model !== "string" || !value.model.trim()
|
|
55
|
+
|| !(value.effort === null || AUTO_REVIEW_REASONING_EFFORTS.includes(value.effort))) {
|
|
56
|
+
if (strict) throw invalidSettings(filePath, `${AUTO_REVIEW_REASONING_KEY} must contain a model and valid effort`);
|
|
57
|
+
delete parsed[AUTO_REVIEW_REASONING_KEY];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
if (Object.hasOwn(parsed, TERMINAL_ANIMATION_KEY)) {
|
|
51
61
|
const value = parsed[TERMINAL_ANIMATION_KEY];
|
|
52
62
|
// Accept the retired value on read so upgrades can fall back and select a replacement.
|
|
@@ -97,12 +107,18 @@ export function savedAutoReviewModel(options = {}) {
|
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
export function autoReviewModelPreference({ env = process.env, home = os.homedir() } = {}) {
|
|
100
|
-
const
|
|
101
|
-
|
|
110
|
+
const { model, source } = autoReviewPreference({ env, home });
|
|
111
|
+
return { model, source };
|
|
112
|
+
}
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
export function autoReviewPreference({ env = process.env, home = os.homedir() } = {}) {
|
|
115
|
+
const settings = readUserSettings({ env, home });
|
|
116
|
+
const environmentModel = String(env.CCDX_AUTO_REVIEW_MODEL || "").trim();
|
|
117
|
+
const savedModel = String(settings[AUTO_REVIEW_MODEL_KEY] || "").trim();
|
|
118
|
+
const model = environmentModel || savedModel || DEFAULT_CODEX_AUTO_REVIEW_MODEL;
|
|
119
|
+
const source = environmentModel ? "environment" : savedModel ? "settings" : "default";
|
|
120
|
+
const reasoning = settings[AUTO_REVIEW_REASONING_KEY];
|
|
121
|
+
return { model, source, reasoningEffort: reasoning?.model === model ? reasoning.effort : undefined };
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
export function savedTerminalAnimationTheme(options = {}) {
|
|
@@ -183,13 +199,22 @@ export function writeResponseHistoryLimitMib(mib, { env = process.env, home = os
|
|
|
183
199
|
return { changed, filePath, ...responseHistoryLimitPreference({ env, home }) };
|
|
184
200
|
}
|
|
185
201
|
|
|
186
|
-
export function writeAutoReviewModel(model, { env = process.env, home = os.homedir() } = {}) {
|
|
202
|
+
export function writeAutoReviewModel(model, { env = process.env, home = os.homedir(), reasoningEffort } = {}) {
|
|
203
|
+
if (reasoningEffort !== undefined && reasoningEffort !== null
|
|
204
|
+
&& !AUTO_REVIEW_REASONING_EFFORTS.includes(reasoningEffort)) {
|
|
205
|
+
throw new Error("Auto-review reasoning effort is invalid");
|
|
206
|
+
}
|
|
187
207
|
const filePath = userSettingsPath({ env, home });
|
|
188
208
|
const settings = readUserSettings({ env, home, strict: true });
|
|
189
209
|
const value = String(model || "").trim();
|
|
190
210
|
const next = { ...settings };
|
|
191
211
|
if (value) next[AUTO_REVIEW_MODEL_KEY] = value;
|
|
192
212
|
else delete next[AUTO_REVIEW_MODEL_KEY];
|
|
213
|
+
const selectedModel = value || DEFAULT_CODEX_AUTO_REVIEW_MODEL;
|
|
214
|
+
if (reasoningEffort !== undefined) next[AUTO_REVIEW_REASONING_KEY] = { model: selectedModel, effort: reasoningEffort };
|
|
215
|
+
else if (next[AUTO_REVIEW_REASONING_KEY]?.model !== selectedModel) {
|
|
216
|
+
delete next[AUTO_REVIEW_REASONING_KEY];
|
|
217
|
+
}
|
|
193
218
|
|
|
194
219
|
const changed = atomicWriteFileIfChangedSync(filePath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
195
220
|
return { changed, filePath, model: value || DEFAULT_CODEX_AUTO_REVIEW_MODEL };
|