@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 +4 -4
- package/docs/usage.md +10 -2
- package/package.json +1 -1
- package/src/cli.js +84 -20
- package/src/config.js +4 -1
- package/src/copilot.js +17 -1
- package/src/setup-selection.js +71 -0
- package/src/setup.js +29 -15
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
|
|
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
|
|
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
|
|
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
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
|
-
|
|
62
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
190
|
-
const result = setupCodex({ model, authCommand: codexAuthCommand() });
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
199
|
-
const result = setupClaude({ model, apiKeyHelper: claudeApiKeyHelper() });
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|