@jiayunxie/aerial 0.1.9 → 0.1.10

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
@@ -73,18 +73,18 @@ You normally do not need to create, copy, or export an API key yourself.
73
73
 
74
74
  ## Choosing A Model
75
75
 
76
- The easiest path is to let setup show the compatible models:
76
+ The easiest path is to let setup show the compatible models and ask for reasoning effort:
77
77
 
78
78
  ```bash
79
79
  aerial setup codex
80
80
  aerial setup claude
81
81
  ```
82
82
 
83
- To pin a model without the prompt:
83
+ To skip the prompts:
84
84
 
85
85
  ```bash
86
- aerial setup codex --model <responses-model-id>
87
- aerial setup claude --model <messages-model-id>
86
+ aerial setup codex --model <responses-model-id> --effort <low|medium|high|xhigh|max>
87
+ aerial setup claude --model <messages-model-id> --effort <low|medium|high|xhigh|max>
88
88
  ```
89
89
 
90
90
  To inspect the full model matrix:
package/docs/usage.md CHANGED
@@ -71,7 +71,7 @@ The local key is generated and stored by Aerial automatically. Users do not need
71
71
 
72
72
  For a dry inspection without touching your real config, set `HOME`/`USERPROFILE` to a temporary directory before running this command.
73
73
 
74
- To skip the prompt, pass `--model <responses-model-id>`.
74
+ To skip the prompts, pass `--model <responses-model-id>` and/or `--effort <low|medium|high|xhigh|max>` (`max` is an alias for `xhigh`). The chosen effort is written into the `[profiles.aerial]` block as `model_reasoning_effort = "<effort>"` and is also persisted as Aerial-wide `defaultEffort` in `~/.aerial/config.json`. Under non-TTY (CI/pipes) the wizard does not prompt and falls back to the default effort `medium`.
75
75
 
76
76
  ## 6. Configure Claude Code
77
77
 
@@ -85,7 +85,15 @@ If Claude Code was previously pointed at another Anthropic-compatible gateway, s
85
85
 
86
86
  For a dry inspection without touching your real config, set `HOME`/`USERPROFILE` to a temporary directory before running this command.
87
87
 
88
- To skip the prompt, pass `--model <messages-model-id>`.
88
+ To skip the prompts, pass `--model <messages-model-id>` and/or `--effort <low|medium|high|xhigh|max>`. Claude `settings.json` does not store an effort value; instead, `setup claude --effort <value>` updates Aerial-wide `defaultEffort` and the local proxy injects it into outgoing `/v1/messages` payloads. Precedence for the Anthropic effort applied to a Claude Opus 4.7 request, in order:
89
+
90
+ 1. An explicit `output_config.effort` in the request body is preserved verbatim.
91
+ 2. A legacy `thinking: { type: "enabled", budget_tokens }` is translated to `thinking: { type: "adaptive" }` with a derived `output_config.effort`.
92
+ 3. `thinking: { type: "adaptive" }` without an explicit effort is preserved as-is; Aerial does not inject a default.
93
+ 4. Otherwise Aerial injects Aerial `defaultEffort` from `~/.aerial/config.json` as `output_config.effort`.
94
+ 5. If no Aerial default is set, Aerial falls back to `medium`.
95
+
96
+ Non-Opus-4.7 Claude requests are not modified.
89
97
 
90
98
  ## 7. Verify
91
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiayunxie/aerial",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Local-only GitHub Copilot proxy for Codex CLI and Claude Code.",
5
5
  "type": "module",
6
6
  "private": false,
package/src/cli.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { startDeviceFlow, pollDeviceFlow, readGitHubToken, gitHubTokenSource } from "./auth.js";
4
4
  import { ensureApiKey, loadConfig, saveConfig } from "./config.js";
5
+ import { configPath } from "./paths.js";
5
6
  import { startServer } from "./server.js";
6
7
  import { setupClaude, setupCodex, setupStatus, restoreClient, restoreAllClients } from "./setup.js";
7
8
  import { serviceInstall, serviceStart, serviceStop, serviceRestart, serviceUninstall, serviceStatus } from "./service.js";
8
9
  import { doctor, renderDoctorText } from "./doctor.js";
9
10
  import { runProbe, formatProbeReport } from "./probe.js";
10
11
  import { chooseSetupModel, formatModelChoices } from "./model-selection.js";
12
+ import { chooseSetupEffort, formatEffortSelection, assertValidEffort } from "./setup-selection.js";
11
13
  import { printVersion } from "./version.js";
12
14
  import { computeAppStatus } from "./app-status.js";
13
15
 
@@ -36,8 +38,8 @@ function printHelp() {
36
38
  Usage:
37
39
  aerial --version
38
40
  aerial login
39
- aerial setup codex [--model <id>]
40
- aerial setup claude [--model <id>]
41
+ aerial setup codex [--model <id>] [--effort <low|medium|high|xhigh|max>]
42
+ aerial setup claude [--model <id>] [--effort <low|medium|high|xhigh|max>]
41
43
  aerial service install
42
44
  aerial status [--json]
43
45
 
@@ -58,8 +60,20 @@ function argValue(args, name) {
58
60
  return index >= 0 ? args[index + 1] : undefined;
59
61
  }
60
62
 
61
- async function selectSetupModel(target, route, args) {
62
- const selected = await chooseSetupModel({ target, route, explicitModel: argValue(args, "--model") });
63
+ function requiredArgValue(args, name) {
64
+ const index = args.indexOf(name);
65
+ if (index < 0) return undefined;
66
+ const value = args[index + 1];
67
+ if (value === undefined || value.startsWith("--")) {
68
+ throw new Error(`${name} requires a value.`);
69
+ }
70
+ return value;
71
+ }
72
+
73
+ async function selectSetupOptions(target, route, args) {
74
+ const explicitEffort = requiredArgValue(args, "--effort");
75
+ if (explicitEffort !== undefined) assertValidEffort(explicitEffort);
76
+ const selected = await chooseSetupModel({ target, route, explicitModel: requiredArgValue(args, "--model") });
63
77
  if (!selected.displayed) {
64
78
  for (const line of formatModelChoices({ target, route, choices: selected.choices, selectedModel: selected.model, source: selected.source, recommended: selected.recommended })) {
65
79
  console.log(line);
@@ -67,14 +81,38 @@ async function selectSetupModel(target, route, args) {
67
81
  } else {
68
82
  console.log(`Selected ${target} model: ${selected.model}`);
69
83
  }
70
- return selected.model;
84
+ const effortChoice = await chooseSetupEffort({ target, explicitEffort });
85
+ console.log(formatEffortSelection({ target, effort: effortChoice.effort, source: effortChoice.source }));
86
+ return {
87
+ model: selected.model,
88
+ effort: effortChoice.effort,
89
+ modelSource: selected.source,
90
+ effortSource: effortChoice.source,
91
+ modelDisplayed: Boolean(selected.displayed),
92
+ effortDisplayed: Boolean(effortChoice.displayed)
93
+ };
94
+ }
95
+
96
+ function printSetupCompletionSummary({ heading, cli, model, effort, proxy, configFile, aerialConfigFile, aerialDefaultEffort, backup, auth, notes = [] }) {
97
+ console.log(heading);
98
+ console.log(` cli: ${cli}`);
99
+ console.log(` model: ${model}`);
100
+ console.log(` effort: ${effort}`);
101
+ console.log(` proxy: ${proxy}`);
102
+ console.log(` client config: ${configFile}`);
103
+ console.log(` aerial config: ${aerialConfigFile}`);
104
+ console.log(` aerial defaultEffort: ${aerialDefaultEffort}`);
105
+ console.log(` backup: ${backup || "none"}`);
106
+ console.log(` auth: ${auth}`);
107
+ for (const note of notes) console.log(` note: ${note}`);
71
108
  }
72
109
 
73
110
  function printSetupSummary(status) {
74
111
  console.log("clients:");
75
112
  for (const client of Object.values(status.clients)) {
76
113
  const model = client.model ? ` model=${client.model}` : "";
77
- console.log(` ${client.target}: ${client.state}${model}`);
114
+ const effort = ` effort=${client.effort || "missing"}`;
115
+ console.log(` ${client.target}: ${client.state}${model}${effort}`);
78
116
  }
79
117
  console.log(`api key: ${status.auth.api_key.exists ? "present" : "missing"}`);
80
118
  const ghSource = status.auth.github_token.source;
@@ -186,21 +224,41 @@ async function main() {
186
224
 
187
225
  if (command === "setup") {
188
226
  if (subcommand === "codex") {
189
- const model = await selectSetupModel("Codex", "responses", rest);
190
- const result = setupCodex({ model, authCommand: codexAuthCommand() });
191
- console.log(`Updated Codex config: ${result.file}`);
192
- console.log(`Configured Codex model: ${result.model}`);
193
- if (result.backup) console.log(`Backup: ${result.backup}`);
194
- console.log("Configured Codex to read the local Aerial key automatically.");
227
+ const options = await selectSetupOptions("Codex", "responses", rest);
228
+ const result = setupCodex({ model: options.model, effort: options.effort, authCommand: codexAuthCommand() });
229
+ const config = loadConfig();
230
+ printSetupCompletionSummary({
231
+ heading: "Configured Codex",
232
+ cli: "Codex",
233
+ model: result.model,
234
+ effort: result.effort || "missing",
235
+ proxy: `http://${config.host}:${config.port}/v1`,
236
+ configFile: result.file,
237
+ aerialConfigFile: configPath(),
238
+ aerialDefaultEffort: config.defaultEffort || "missing",
239
+ backup: result.backup,
240
+ auth: "command-backed local Aerial key",
241
+ notes: ["restart Codex if it was already running so it reloads the profile."]
242
+ });
195
243
  return;
196
244
  }
197
245
  if (subcommand === "claude") {
198
- const model = await selectSetupModel("Claude Code", "messages", rest);
199
- const result = setupClaude({ model, apiKeyHelper: claudeApiKeyHelper() });
200
- console.log(`Updated Claude settings: ${result.file}`);
201
- if (result.model) console.log(`Configured Claude default model: ${result.model}`);
202
- console.log("Configured Claude Code to read the local Aerial key automatically.");
203
- if (result.backup) console.log(`Backup: ${result.backup}`);
246
+ const options = await selectSetupOptions("Claude Code", "messages", rest);
247
+ const result = setupClaude({ model: options.model, effort: options.effort, apiKeyHelper: claudeApiKeyHelper() });
248
+ const config = loadConfig();
249
+ printSetupCompletionSummary({
250
+ heading: "Configured Claude Code",
251
+ cli: "Claude Code",
252
+ model: result.model || "preserved",
253
+ effort: result.effort || config.defaultEffort || "missing",
254
+ proxy: `http://${config.host}:${config.port}`,
255
+ configFile: result.file,
256
+ aerialConfigFile: configPath(),
257
+ aerialDefaultEffort: config.defaultEffort || "missing",
258
+ backup: result.backup,
259
+ auth: "apiKeyHelper local Aerial key",
260
+ notes: ["effort is applied via Aerial defaultEffort and proxy fallback; Claude settings.json does not store an effort value."]
261
+ });
204
262
  return;
205
263
  }
206
264
  if (subcommand === "all") {
@@ -222,7 +280,8 @@ async function main() {
222
280
  console.log(`GitHub token: ${ghSourceText}`);
223
281
  for (const cs of Object.values(status.clients)) {
224
282
  const head = `${cs.target.padEnd(7)} state=${cs.state}`;
225
- console.log(`${head} file=${cs.file}`);
283
+ const effortText = ` effort=${cs.effort || "missing"}`;
284
+ console.log(`${head}${effortText} file=${cs.file}`);
226
285
  if (cs.backups.length) console.log(` backups=${cs.backups.length}`);
227
286
  if (cs.error) console.log(` error=${cs.error}`);
228
287
  }
@@ -469,9 +528,14 @@ async function main() {
469
528
  if (subcommand === "set") {
470
529
  const [key, value] = rest;
471
530
  if (!key || value === undefined) throw new Error("Usage: aerial config set <key> <value>");
472
- if (!["host", "port", "defaultModel", "logLevel", "promptCacheRetention", "promptCacheKey"].includes(key)) throw new Error(`Unsupported config key: ${key}`);
531
+ if (!["host", "port", "defaultModel", "defaultEffort", "logLevel", "promptCacheRetention", "promptCacheKey"].includes(key)) throw new Error(`Unsupported config key: ${key}`);
473
532
  if (key === "promptCacheRetention" && !["in_memory", "24h", "off"].includes(value)) throw new Error("promptCacheRetention must be one of: in_memory, 24h, off");
474
533
  if (key === "promptCacheKey" && !value.trim()) throw new Error("promptCacheKey must be auto, off, or a non-empty string");
534
+ if (key === "defaultEffort") {
535
+ config.defaultEffort = assertValidEffort(value);
536
+ saveConfig(config);
537
+ return;
538
+ }
475
539
  config[key] = key === "port" ? Number(value) : value;
476
540
  saveConfig(config);
477
541
  return;
package/src/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import { CONFIG_VERSION, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_VERSIONS } from "./constants.js";
3
3
  import { apiKeyPath, configPath, readJsonIfExists, writeJsonPrivate, writePrivateFile } from "./paths.js";
4
4
  import { hashApiKey, randomApiKey, verifyApiKey } from "./crypto.js";
5
+ import { DEFAULT_EFFORT, normalizeEffort } from "./setup-selection.js";
5
6
 
6
7
  export function defaultConfig() {
7
8
  return {
@@ -10,6 +11,7 @@ export function defaultConfig() {
10
11
  port: DEFAULT_PORT,
11
12
  apiKeyHash: undefined,
12
13
  defaultModel: undefined,
14
+ defaultEffort: DEFAULT_EFFORT,
13
15
  logLevel: "info",
14
16
  promptCacheRetention: "in_memory",
15
17
  promptCacheKey: "auto",
@@ -24,7 +26,8 @@ export function loadConfig() {
24
26
  const defaults = defaultConfig();
25
27
  const promptCacheRetention = envRetention === undefined ? (loaded.promptCacheRetention ?? defaults.promptCacheRetention) : envRetention;
26
28
  const promptCacheKey = envCacheKey === undefined ? (loaded.promptCacheKey ?? defaults.promptCacheKey) : envCacheKey;
27
- return { ...defaults, ...loaded, promptCacheRetention, promptCacheKey, versions: { ...DEFAULT_VERSIONS, ...(loaded.versions || {}) } };
29
+ const defaultEffort = normalizeEffort(loaded.defaultEffort) || DEFAULT_EFFORT;
30
+ return { ...defaults, ...loaded, defaultEffort, promptCacheRetention, promptCacheKey, versions: { ...DEFAULT_VERSIONS, ...(loaded.versions || {}) } };
28
31
  }
29
32
 
30
33
  export function saveConfig(config) {
package/src/copilot.js CHANGED
@@ -251,8 +251,24 @@ async function fetchModelsCatalogForCopilot() {
251
251
  });
252
252
  }
253
253
 
254
+ function withDefaultAnthropicEffort(payload) {
255
+ if (payload?.output_config?.effort !== undefined) return payload;
256
+ if (payload?.thinking?.type === "adaptive") return payload;
257
+ const model = typeof payload?.model === "string" ? payload.model : "";
258
+ if (!canonicalClaudeFamily(model)) return payload;
259
+ const config = loadConfig();
260
+ const effort = config.defaultEffort || "medium";
261
+ const outputConfig = payload?.output_config && typeof payload.output_config === "object" && !Array.isArray(payload.output_config)
262
+ ? payload.output_config
263
+ : {};
264
+ logEvent("anthropic_default_effort", { model, effort });
265
+ return { ...payload, output_config: { ...outputConfig, effort } };
266
+ }
267
+
254
268
  async function withAnthropicDefaults(payload) {
255
- const next = withSupportedAnthropicThinking(withDefaultAnthropicCache(payload));
269
+ const cached = withDefaultAnthropicCache(payload);
270
+ const thinkingApplied = withSupportedAnthropicThinking(cached);
271
+ const next = withDefaultAnthropicEffort(thinkingApplied);
256
272
  const models = shouldLoadAnthropicCatalog(next)
257
273
  ? await fetchModelsCatalogForCopilot().catch(() => undefined)
258
274
  : undefined;
@@ -0,0 +1,71 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+
4
+ export const EFFORT_VALUES = Object.freeze(["low", "medium", "high", "xhigh"]);
5
+ export const DEFAULT_EFFORT = "medium";
6
+
7
+ export function normalizeEffort(value) {
8
+ if (value === undefined || value === null) return undefined;
9
+ const trimmed = String(value).trim().toLowerCase();
10
+ if (!trimmed) return undefined;
11
+ if (trimmed === "max") return "xhigh";
12
+ if (EFFORT_VALUES.includes(trimmed)) return trimmed;
13
+ return undefined;
14
+ }
15
+
16
+ export function assertValidEffort(raw) {
17
+ const normalized = normalizeEffort(raw);
18
+ if (!normalized) {
19
+ throw new Error(`Invalid --effort ${JSON.stringify(raw)}. Allowed: ${EFFORT_VALUES.join(", ")} (or alias 'max' for xhigh).`);
20
+ }
21
+ return normalized;
22
+ }
23
+
24
+ function parseChoice(value, max, defaultIndex) {
25
+ const trimmed = String(value || "").trim();
26
+ if (!trimmed) return defaultIndex;
27
+ if (!/^\d+$/.test(trimmed)) return undefined;
28
+ const n = Number(trimmed);
29
+ return n >= 1 && n <= max ? n - 1 : undefined;
30
+ }
31
+
32
+ export async function chooseSetupEffort({
33
+ target,
34
+ explicitEffort,
35
+ prompt,
36
+ input: inputStream = input,
37
+ output: outputStream = output
38
+ } = {}) {
39
+ if (explicitEffort !== undefined) {
40
+ return { effort: assertValidEffort(explicitEffort), source: "explicit", displayed: false };
41
+ }
42
+ const shouldPrompt = prompt === undefined ? Boolean(inputStream.isTTY) : prompt;
43
+ if (!shouldPrompt) {
44
+ return { effort: DEFAULT_EFFORT, source: "default_non_tty", displayed: false };
45
+ }
46
+ const defaultIndex = EFFORT_VALUES.indexOf(DEFAULT_EFFORT);
47
+ const rl = createInterface({ input: inputStream, output: outputStream });
48
+ try {
49
+ outputStream.write(`Choose ${target} reasoning effort:\n`);
50
+ for (const [index, value] of EFFORT_VALUES.entries()) {
51
+ const marker = index === defaultIndex ? " (default)" : "";
52
+ outputStream.write(` ${index + 1}. ${value}${marker}\n`);
53
+ }
54
+ while (true) {
55
+ const answer = await rl.question(`Choose effort [1-${EFFORT_VALUES.length}, default ${defaultIndex + 1} = ${DEFAULT_EFFORT}]: `);
56
+ const selectedIndex = parseChoice(answer, EFFORT_VALUES.length, defaultIndex);
57
+ if (selectedIndex !== undefined) {
58
+ return { effort: EFFORT_VALUES[selectedIndex], source: "prompt", displayed: true };
59
+ }
60
+ outputStream.write(`Enter a number from 1 to ${EFFORT_VALUES.length}, or press Enter for ${defaultIndex + 1}.\n`);
61
+ }
62
+ } finally {
63
+ rl.close();
64
+ }
65
+ }
66
+
67
+ export function formatEffortSelection({ target, effort, source }) {
68
+ if (source === "explicit") return `Selected ${target} effort: ${effort}`;
69
+ if (source === "prompt") return `Selected ${target} effort: ${effort}`;
70
+ return `No interactive terminal detected; selected ${target} effort: ${effort}. Pass --effort <low|medium|high|xhigh|max> to choose a different effort.`;
71
+ }
package/src/setup.js CHANGED
@@ -2,10 +2,11 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { parse as parseToml } from "smol-toml";
5
- import { ensureApiKey, loadConfig } from "./config.js";
5
+ import { ensureApiKey, loadConfig, saveConfig } from "./config.js";
6
6
  import { apiKeyPath, githubTokenPath } from "./paths.js";
7
7
  import { gitHubTokenSource } from "./auth.js";
8
8
  import { logEvent } from "./log.js";
9
+ import { assertValidEffort, normalizeEffort } from "./setup-selection.js";
9
10
 
10
11
  const BACKUP_PREFIX = ".aerial-backup-";
11
12
  const PRE_RESTORE_PREFIX = ".aerial-pre-restore-";
@@ -75,7 +76,8 @@ function claudeEnvForAerial(currentEnv, config) {
75
76
  };
76
77
  }
77
78
 
78
- export function setupCodex({ model, authCommand = DEFAULT_CODEX_AUTH } = {}) {
79
+ export function setupCodex({ model, effort, authCommand = DEFAULT_CODEX_AUTH } = {}) {
80
+ const normalizedEffort = effort === undefined ? undefined : assertValidEffort(effort);
79
81
  ensureApiKey();
80
82
  const config = loadConfig();
81
83
  const selectedModel = model || config.defaultModel;
@@ -99,13 +101,19 @@ export function setupCodex({ model, authCommand = DEFAULT_CODEX_AUTH } = {}) {
99
101
  timeout_ms: authCommand.timeout_ms || DEFAULT_CODEX_AUTH.timeout_ms,
100
102
  refresh_interval_ms: authCommand.refresh_interval_ms ?? DEFAULT_CODEX_AUTH.refresh_interval_ms
101
103
  });
102
- content = upsertTomlSection(content, "profiles.aerial", { model_provider: "aerial", model: selectedModel });
104
+ const profileValues = { model_provider: "aerial", model: selectedModel };
105
+ if (normalizedEffort) profileValues.model_reasoning_effort = normalizedEffort;
106
+ content = upsertTomlSection(content, "profiles.aerial", profileValues);
103
107
  fs.writeFileSync(file, content, "utf8");
104
- logEvent("setup_write", { target: "codex", file, backup, auth: "command" });
105
- return { file, backup, model: selectedModel, auth: { type: "command", command: authCommand.command, args: authCommand.args || [] } };
108
+ if (normalizedEffort && config.defaultEffort !== normalizedEffort) {
109
+ saveConfig({ ...config, defaultEffort: normalizedEffort });
110
+ }
111
+ logEvent("setup_write", { target: "codex", file, backup, auth: "command", effort: normalizedEffort });
112
+ return { file, backup, model: selectedModel, effort: normalizedEffort, auth: { type: "command", command: authCommand.command, args: authCommand.args || [] } };
106
113
  }
107
114
 
108
- export function setupClaude({ model, apiKeyHelper = DEFAULT_CLAUDE_API_KEY_HELPER } = {}) {
115
+ export function setupClaude({ model, effort, apiKeyHelper = DEFAULT_CLAUDE_API_KEY_HELPER } = {}) {
116
+ const normalizedEffort = effort === undefined ? undefined : assertValidEffort(effort);
109
117
  ensureApiKey();
110
118
  const config = loadConfig();
111
119
  const selectedModel = model || config.defaultModel;
@@ -121,8 +129,11 @@ export function setupClaude({ model, apiKeyHelper = DEFAULT_CLAUDE_API_KEY_HELPE
121
129
  };
122
130
  if (selectedModel) next.model = selectedModel;
123
131
  fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`, "utf8");
124
- logEvent("setup_write", { target: "claude", file, backup, model: selectedModel });
125
- return { file, backup, model: selectedModel, apiKeyHelper };
132
+ if (normalizedEffort && config.defaultEffort !== normalizedEffort) {
133
+ saveConfig({ ...config, defaultEffort: normalizedEffort });
134
+ }
135
+ logEvent("setup_write", { target: "claude", file, backup, model: selectedModel, effort: normalizedEffort });
136
+ return { file, backup, model: selectedModel, effort: normalizedEffort, apiKeyHelper };
126
137
  }
127
138
 
128
139
  function codexConfigFile() {
@@ -182,25 +193,27 @@ export function codexStatus() {
182
193
  const expectedBaseUrl = `http://${config.host}:${config.port}/v1`;
183
194
  const file = codexConfigFile();
184
195
  const backups = backupPathsFor(file);
185
- if (!fs.existsSync(file)) return { target: "codex", state: "missing", file, backups };
196
+ if (!fs.existsSync(file)) return { target: "codex", state: "missing", file, backups, effort: "missing" };
186
197
  let content;
187
198
  try {
188
199
  content = fs.readFileSync(file, "utf8");
189
200
  } catch (err) {
190
- return { target: "codex", state: "invalid", file, backups, error: err.message };
201
+ return { target: "codex", state: "invalid", file, backups, error: err.message, effort: "missing" };
191
202
  }
192
203
  let doc;
193
204
  try {
194
205
  doc = parseToml(content);
195
206
  } catch (err) {
196
- return { target: "codex", state: "invalid", file, backups, error: err.message };
207
+ return { target: "codex", state: "invalid", file, backups, error: err.message, effort: "missing" };
197
208
  }
198
209
  const state = codexStateFromDoc(doc, expectedBaseUrl);
199
210
  const model = typeof doc?.model === "string" ? doc.model : undefined;
200
211
  const baseUrl = typeof doc?.model_providers?.aerial?.base_url === "string"
201
212
  ? doc.model_providers.aerial.base_url
202
213
  : undefined;
203
- return { target: "codex", state, file, backups, model, baseUrl };
214
+ const profileEffort = doc?.profiles?.aerial?.model_reasoning_effort;
215
+ const effort = typeof profileEffort === "string" ? (normalizeEffort(profileEffort) || "missing") : "missing";
216
+ return { target: "codex", state, file, backups, model, baseUrl, effort };
204
217
  }
205
218
 
206
219
  function claudeStateFromDoc(doc, expectedBaseUrl) {
@@ -220,17 +233,18 @@ export function claudeStatus() {
220
233
  const expectedBaseUrl = `http://${config.host}:${config.port}`;
221
234
  const file = claudeSettingsFile();
222
235
  const backups = backupPathsFor(file);
223
- if (!fs.existsSync(file)) return { target: "claude", state: "missing", file, backups };
236
+ const effort = typeof config.defaultEffort === "string" && config.defaultEffort.trim() ? config.defaultEffort.trim() : "missing";
237
+ if (!fs.existsSync(file)) return { target: "claude", state: "missing", file, backups, effort };
224
238
  let doc;
225
239
  try {
226
240
  doc = JSON.parse(fs.readFileSync(file, "utf8"));
227
241
  } catch (err) {
228
- return { target: "claude", state: "invalid", file, backups, error: err.message };
242
+ return { target: "claude", state: "invalid", file, backups, error: err.message, effort };
229
243
  }
230
244
  const state = claudeStateFromDoc(doc, expectedBaseUrl);
231
245
  const model = typeof doc?.model === "string" ? doc.model : undefined;
232
246
  const baseUrl = doc?.env?.ANTHROPIC_BASE_URL;
233
- return { target: "claude", state, file, backups, model, baseUrl };
247
+ return { target: "claude", state, file, backups, model, baseUrl, effort };
234
248
  }
235
249
 
236
250
  export function setupStatus() {