codex-copilot-dx 0.9.11 → 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 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-5.5` Responses model by default, independently of the interactive Fast/Standard selection, and logs both model IDs when the mapping is used. The review target can be changed interactively without reinstalling or restarting CCDX.
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
- The selection is saved in `~/.config/codex-copilot-dx/config.json`, or under `XDG_CONFIG_HOME` when set. Choosing `gpt-5.5` clears the override and restores the package default. A running adapter reads the setting on the next Auto-review request.
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
 
@@ -352,7 +354,7 @@ Recovery first tries compatible local credentials and starts Device Flow only wh
352
354
  | `CCDX_STREAM_IDLE_TIMEOUT_MS` | `120000` | Maximum idle time between upstream streaming body chunks |
353
355
  | `CCDX_UPSTREAM_RETRIES` | `2` | Retries for safe requests and clearly pre-connect POST failures; capped at `5` |
354
356
  | `CCDX_UPSTREAM_RETRY_DELAY_MS` | `300` | Initial upstream retry backoff in milliseconds; capped at `5000` |
355
- | `CCDX_AUTO_REVIEW_MODEL` | saved selection or `gpt-5.5` | Copilot Responses model used for Codex Auto-review; overrides the interactive selection |
357
+ | `CCDX_AUTO_REVIEW_MODEL` | saved selection or `gpt-6.1-sol` | Copilot Responses model used for Codex Auto-review; overrides the interactive selection |
356
358
  | `CCDX_LOG_PATH` | unset | Mirror terminal logs to a file; set to `1` for `~/.local/share/codex-copilot-dx/debug.log` |
357
359
  | `CCDX_LOG_LEVEL` | `info` | Set to `debug` for upstream attempts, status codes, retry causes, and timings |
358
360
  | `CCDX_LOG_MAX_BYTES` | `16777216` | Rotate the debug log at this size and retain one `.1` backup; set to `0` to disable rotation |
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: () => autoReviewModelPreference().model,
343
+ autoReviewModelResolver: () => autoReviewPreference(),
344
344
  codexClient: profileRuntime.codexClient,
345
345
  codexModelCatalog,
346
346
  codexModelRegistry: profileModels.codexRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "Use Codex App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
@@ -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
- autoReviewModelPreference,
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, source: fallback ? "running adapter cache" : "running adapter" };
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 = autoReviewModelPreference({ env, home });
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
  }
@@ -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\nInteractively selects an advertised Responses model for Codex Auto-review.`,
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 Responses model
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
- export const DEFAULT_CODEX_AUTO_REVIEW_MODEL = "gpt-5.5";
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 options = model === CODEX_AUTO_REVIEW_MODEL && typeof autoReviewModelResolver === "function"
94
- ? { autoReviewModel: autoReviewModelResolver() }
95
- : {};
96
- return resolveOpenAIModel(model, openAIModelEnv, options);
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) {
@@ -272,7 +275,12 @@ export function createResponsesHandler(options) {
272
275
  delete prepared.body.service_tier;
273
276
  }
274
277
  stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
275
- if (requestedModel === CODEX_AUTO_REVIEW_MODEL) delete prepared.body.service_tier;
278
+ if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
279
+ delete prepared.body.service_tier;
280
+ if (resolvedModel.reasoningEffort || prepared.body.reasoning?.effort === undefined) {
281
+ prepared.body.reasoning = { ...prepared.body.reasoning, effort: resolvedModel.reasoningEffort || "low" };
282
+ }
283
+ }
276
284
  if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
277
285
  markResponseModel(upstreamModel);
278
286
  const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
@@ -537,7 +545,12 @@ export function createResponsesCompactHandler(options) {
537
545
  delete prepared.body.service_tier;
538
546
  }
539
547
  stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
540
- if (requestedModel === CODEX_AUTO_REVIEW_MODEL) delete prepared.body.service_tier;
548
+ if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
549
+ delete prepared.body.service_tier;
550
+ if (resolvedModel.reasoningEffort || prepared.body.reasoning?.effort === undefined) {
551
+ prepared.body.reasoning = { ...prepared.body.reasoning, effort: resolvedModel.reasoningEffort || "low" };
552
+ }
553
+ }
541
554
  if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
542
555
  const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
543
556
  console.log(status("info", `responses compact model=${requestedModel}${upstreamLog} stream=false`));
@@ -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 environmentModel = String(env.CCDX_AUTO_REVIEW_MODEL || "").trim();
101
- if (environmentModel) return { model: environmentModel, source: "environment" };
110
+ const { model, source } = autoReviewPreference({ env, home });
111
+ return { model, source };
112
+ }
102
113
 
103
- const savedModel = savedAutoReviewModel({ env, home });
104
- if (savedModel) return { model: savedModel, source: "settings" };
105
- return { model: DEFAULT_CODEX_AUTO_REVIEW_MODEL, source: "default" };
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 };