@jiayunxie/aerial 0.2.3 → 0.2.5
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/docs/usage.md +1 -1
- package/package.json +1 -1
- package/src/cli/{config-command.js → commands/config.js} +4 -4
- package/src/cli/{disable-command.js → commands/disable.js} +4 -4
- package/src/cli/{key-command.js → commands/key.js} +1 -1
- package/src/cli/{login-command.js → commands/login.js} +1 -1
- package/src/cli/{proxy-command.js → commands/proxy.js} +5 -5
- package/src/cli/{service-command.js → commands/service.js} +2 -2
- package/src/cli/{setup-command.js → commands/setup.js} +9 -9
- package/src/cli/{start-command.js → commands/start.js} +3 -3
- package/src/cli/{status-command.js → commands/status.js} +4 -4
- package/src/cli/index.js +9 -9
- package/src/proxy/cache-telemetry.js +4 -3
- package/src/proxy/{effort.js → effort-routing.js} +6 -8
- package/src/proxy/index.js +1 -1
- package/src/proxy/models.js +4 -2
- package/src/proxy/responses-websocket.js +1 -0
- package/src/proxy/server.js +4 -3
- package/src/setup/clients.js +6 -3
- package/src/shared/file-utils.js +1 -1
package/docs/usage.md
CHANGED
|
@@ -105,7 +105,7 @@ If Claude Code was previously pointed at another Anthropic-compatible gateway, s
|
|
|
105
105
|
|
|
106
106
|
For a dry inspection without touching your real config, set `HOME`/`USERPROFILE` to a temporary directory before running this command.
|
|
107
107
|
|
|
108
|
-
To skip the prompts, pass `--model <messages-model-id>` and/or `--effort <low|medium|high|xhigh|max>`.
|
|
108
|
+
To skip the prompts, pass `--model <messages-model-id>` and/or `--effort <low|medium|high|xhigh|max>`. `setup claude --effort <value>` writes Claude Code's native `"effortLevel": "<effort>"` setting into `settings.json` and also updates Aerial-wide `defaultEffort` as a proxy fallback. Precedence for the Anthropic effort applied to a Claude Opus 4.7 request, in order:
|
|
109
109
|
|
|
110
110
|
1. An explicit `output_config.effort` in the request body is preserved verbatim.
|
|
111
111
|
2. A legacy `thinking: { type: "enabled", budget_tokens }` is translated to `thinking: { type: "adaptive" }` with a derived `output_config.effort`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defaultConfig, loadConfig, saveConfig } from "
|
|
2
|
-
import { configPath } from "
|
|
3
|
-
import { assertValidEffort } from "
|
|
4
|
-
import { parseConfigHost, parseConfigPort } from "
|
|
1
|
+
import { defaultConfig, loadConfig, saveConfig } from "../../shared/config.js";
|
|
2
|
+
import { configPath } from "../../shared/paths.js";
|
|
3
|
+
import { assertValidEffort } from "../../shared/effort.js";
|
|
4
|
+
import { parseConfigHost, parseConfigPort } from "../args.js";
|
|
5
5
|
|
|
6
6
|
export function runConfigCli(subcommand, rest) {
|
|
7
7
|
if (subcommand === "reset") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { restoreAllClients } from "
|
|
2
|
-
import { serviceUninstall } from "
|
|
3
|
-
import { printRestoreResults } from "
|
|
4
|
-
import { printServiceUninstallResult } from "./service
|
|
1
|
+
import { restoreAllClients } from "../../setup/index.js";
|
|
2
|
+
import { serviceUninstall } from "../../service/index.js";
|
|
3
|
+
import { printRestoreResults } from "../output.js";
|
|
4
|
+
import { printServiceUninstallResult } from "./service.js";
|
|
5
5
|
|
|
6
6
|
export function runDisableCli() {
|
|
7
7
|
const { ok: restoreOk, results } = restoreAllClients();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { loadConfig } from "
|
|
2
|
-
import { disableUpstreamProxy, enableUpstreamProxy, probeEgress, upstreamProxyState } from "
|
|
3
|
-
import { redactProxyEndpoint, redactProxySource } from "
|
|
4
|
-
import { runProbe } from "
|
|
5
|
-
import { formatEgress } from "
|
|
1
|
+
import { loadConfig } from "../../shared/config.js";
|
|
2
|
+
import { disableUpstreamProxy, enableUpstreamProxy, probeEgress, upstreamProxyState } from "../../upstream/fetch.js";
|
|
3
|
+
import { redactProxyEndpoint, redactProxySource } from "../../upstream/proxy-config.js";
|
|
4
|
+
import { runProbe } from "../probe.js";
|
|
5
|
+
import { formatEgress } from "../output.js";
|
|
6
6
|
|
|
7
7
|
async function proxyRouteSummary() {
|
|
8
8
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { serviceInstall, serviceRestart, serviceStart, serviceStatus, serviceStop, serviceUninstall } from "
|
|
2
|
-
import { printServiceDiagnostics, printServiceWarning } from "
|
|
1
|
+
import { serviceInstall, serviceRestart, serviceStart, serviceStatus, serviceStop, serviceUninstall } from "../../service/index.js";
|
|
2
|
+
import { printServiceDiagnostics, printServiceWarning } from "../output.js";
|
|
3
3
|
|
|
4
4
|
export function printServiceUninstallResult(r, { prefix = "Service uninstall" } = {}) {
|
|
5
5
|
if (r.note === "no service installed") console.log(`${prefix}: no service installed`);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { loadConfig } from "
|
|
2
|
-
import { configPath } from "
|
|
3
|
-
import { restoreAllClients, restoreClient, setupClaude, setupCodex, setupStatus } from "
|
|
4
|
-
import { chooseSetupModel, formatModelChoices } from "
|
|
5
|
-
import { assertValidEffort, chooseSetupEffort, formatEffortSelection } from "
|
|
6
|
-
import { requiredArgValue } from "
|
|
7
|
-
import { claudeApiKeyHelper, codexAuthCommand } from "
|
|
8
|
-
import { printRestoreResults, printSetupCompletionSummary } from "
|
|
1
|
+
import { loadConfig } from "../../shared/config.js";
|
|
2
|
+
import { configPath } from "../../shared/paths.js";
|
|
3
|
+
import { restoreAllClients, restoreClient, setupClaude, setupCodex, setupStatus } from "../../setup/index.js";
|
|
4
|
+
import { chooseSetupModel, formatModelChoices } from "../model-selection.js";
|
|
5
|
+
import { assertValidEffort, chooseSetupEffort, formatEffortSelection } from "../setup-selection.js";
|
|
6
|
+
import { requiredArgValue } from "../args.js";
|
|
7
|
+
import { claudeApiKeyHelper, codexAuthCommand } from "../runtime-auth.js";
|
|
8
|
+
import { printRestoreResults, printSetupCompletionSummary } from "../output.js";
|
|
9
9
|
|
|
10
10
|
async function selectSetupOptions(target, route, args) {
|
|
11
11
|
const explicitEffort = requiredArgValue(args, "--effort");
|
|
@@ -83,7 +83,7 @@ export async function runSetupCli(subcommand, rest) {
|
|
|
83
83
|
aerialDefaultEffort: config.defaultEffort || "missing",
|
|
84
84
|
backup: result.backup,
|
|
85
85
|
auth: "apiKeyHelper local Aerial key",
|
|
86
|
-
notes: ["effort is
|
|
86
|
+
notes: ["effort is written to Claude settings.json effortLevel and mirrored to Aerial defaultEffort for proxy fallback."]
|
|
87
87
|
});
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ensureApiKey, loadConfig } from "
|
|
2
|
-
import { startServer } from "
|
|
3
|
-
import { argValue } from "
|
|
1
|
+
import { ensureApiKey, loadConfig } from "../../shared/config.js";
|
|
2
|
+
import { startServer } from "../../proxy/server.js";
|
|
3
|
+
import { argValue } from "../args.js";
|
|
4
4
|
|
|
5
5
|
export function runStartCli(args) {
|
|
6
6
|
const config = loadConfig();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { setupStatus } from "
|
|
2
|
-
import { serviceStatus } from "
|
|
3
|
-
import { computeAppStatus } from "
|
|
4
|
-
import { printServiceSummary, printSetupSummary } from "
|
|
1
|
+
import { setupStatus } from "../../setup/index.js";
|
|
2
|
+
import { serviceStatus } from "../../service/index.js";
|
|
3
|
+
import { computeAppStatus } from "../app-status.js";
|
|
4
|
+
import { printServiceSummary, printSetupSummary } from "../output.js";
|
|
5
5
|
|
|
6
6
|
export async function appStatus({ json = false } = {}) {
|
|
7
7
|
const setup = setupStatus();
|
package/src/cli/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { printVersion } from "./version.js";
|
|
3
3
|
import { printHelp } from "./help.js";
|
|
4
|
-
import { appStatus } from "./status
|
|
5
|
-
import { runConfigCli } from "./config
|
|
6
|
-
import { runDisableCli } from "./disable
|
|
7
|
-
import { runKeyCli } from "./key
|
|
8
|
-
import { runLoginCli } from "./login
|
|
9
|
-
import { runProxyCli } from "./proxy
|
|
10
|
-
import { runServiceCli } from "./service
|
|
11
|
-
import { runSetupCli } from "./setup
|
|
12
|
-
import { runStartCli } from "./start
|
|
4
|
+
import { appStatus } from "./commands/status.js";
|
|
5
|
+
import { runConfigCli } from "./commands/config.js";
|
|
6
|
+
import { runDisableCli } from "./commands/disable.js";
|
|
7
|
+
import { runKeyCli } from "./commands/key.js";
|
|
8
|
+
import { runLoginCli } from "./commands/login.js";
|
|
9
|
+
import { runProxyCli } from "./commands/proxy.js";
|
|
10
|
+
import { runServiceCli } from "./commands/service.js";
|
|
11
|
+
import { runSetupCli } from "./commands/setup.js";
|
|
12
|
+
import { runStartCli } from "./commands/start.js";
|
|
13
13
|
import { doctor, renderDoctorText } from "./doctor.js";
|
|
14
14
|
import { formatProbeReport, runProbe } from "./probe.js";
|
|
15
15
|
|
|
@@ -2,6 +2,8 @@ import { logEvent } from "../shared/log.js";
|
|
|
2
2
|
import { copyResponseHeaders } from "./headers.js";
|
|
3
3
|
import { countCacheControlBlocks } from "./cache-policy.js";
|
|
4
4
|
|
|
5
|
+
const SSE_FRAME_BOUNDARY = /\r?\n\r?\n/;
|
|
6
|
+
|
|
5
7
|
export function parseJsonBody(body, contentType) {
|
|
6
8
|
if (!body || !contentType.includes("json")) return undefined;
|
|
7
9
|
try {
|
|
@@ -19,7 +21,7 @@ export function cacheRequestFields(payload) {
|
|
|
19
21
|
hasPromptCacheKey: payload.prompt_cache_key !== undefined,
|
|
20
22
|
cacheControlBlocks: countCacheControlBlocks(payload)
|
|
21
23
|
};
|
|
22
|
-
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined && value !== false
|
|
24
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined && value !== false));
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
function responseItems(payload) {
|
|
@@ -103,8 +105,7 @@ export function createSseCacheObserver(route, requestCache) {
|
|
|
103
105
|
|
|
104
106
|
const consume = () => {
|
|
105
107
|
let match;
|
|
106
|
-
|
|
107
|
-
while ((match = boundary.exec(buffer)) !== null) {
|
|
108
|
+
while ((match = SSE_FRAME_BOUNDARY.exec(buffer)) !== null) {
|
|
108
109
|
const frame = buffer.slice(0, match.index);
|
|
109
110
|
buffer = buffer.slice(match.index + match[0].length);
|
|
110
111
|
for (const payload of sseJsonPayloads(frame)) {
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { loadConfig } from "../shared/config.js";
|
|
2
2
|
import { logEvent } from "../shared/log.js";
|
|
3
|
+
import { EFFORT_VALUES, normalizeEffort } from "../shared/effort.js";
|
|
3
4
|
import { canonicalClaudeFamily, findCompatibleModel as findCompatibleModelShared } from "./model-catalog.js";
|
|
4
5
|
import { withDefaultAnthropicCache, withDefaultPromptCache } from "./cache-policy.js";
|
|
5
6
|
|
|
6
|
-
function normalizeProxyEffort(effort) {
|
|
7
|
-
return effort === "max" ? "xhigh" : effort;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
7
|
function openAIEffortRoute(model, effort) {
|
|
11
8
|
if (effort === undefined) return undefined;
|
|
12
|
-
const normalized =
|
|
9
|
+
const normalized = normalizeEffort(effort);
|
|
10
|
+
if (!normalized) return undefined;
|
|
13
11
|
if (/^gpt-5-mini(?:-|$)/.test(model) && normalized === "xhigh") return "high";
|
|
14
|
-
if (normalized !== effort) return normalized;
|
|
12
|
+
if (normalized !== String(effort).trim().toLowerCase()) return normalized;
|
|
15
13
|
return undefined;
|
|
16
14
|
}
|
|
17
15
|
|
|
@@ -47,8 +45,8 @@ function withSupportedAnthropicEffort(payload, models) {
|
|
|
47
45
|
const model = typeof payload?.model === "string" ? payload.model : "";
|
|
48
46
|
const family = canonicalClaudeFamily(model);
|
|
49
47
|
if (!family) return payload;
|
|
50
|
-
const nextEffort =
|
|
51
|
-
if (!
|
|
48
|
+
const nextEffort = normalizeEffort(effort);
|
|
49
|
+
if (!nextEffort || !EFFORT_VALUES.includes(nextEffort)) return payload;
|
|
52
50
|
const routed = findCompatibleModelShared({
|
|
53
51
|
models,
|
|
54
52
|
family,
|
package/src/proxy/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { logEvent } from "../shared/log.js";
|
|
|
4
4
|
import { isResponsesWebSocketOptIn, proxyResponsesWebSocket, shouldUseResponsesWebSocket } from "./responses-websocket.js";
|
|
5
5
|
import { annotateModelsResponse } from "./models.js";
|
|
6
6
|
import { upstreamHeaders } from "./headers.js";
|
|
7
|
-
import { withAnthropicDefaults, withOpenAIDefaults } from "./effort.js";
|
|
7
|
+
import { withAnthropicDefaults, withOpenAIDefaults } from "./effort-routing.js";
|
|
8
8
|
import {
|
|
9
9
|
cacheRequestFields,
|
|
10
10
|
createSseCacheObserver,
|
package/src/proxy/models.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { copyResponseHeaders } from "./headers.js";
|
|
2
|
+
|
|
1
3
|
export function aerialSupportForModel(model) {
|
|
2
4
|
const endpoints = Array.isArray(model.supported_endpoints) ? model.supported_endpoints : [];
|
|
3
5
|
const routes = [];
|
|
@@ -19,9 +21,9 @@ export async function annotateModelsResponse(response) {
|
|
|
19
21
|
const contentType = response.headers.get("content-type") || "";
|
|
20
22
|
if (!response.ok || !contentType.includes("application/json")) return response;
|
|
21
23
|
const payload = await response.json();
|
|
22
|
-
if (!Array.isArray(payload.data)) return Response.json(payload, { status: response.status, headers: response
|
|
24
|
+
if (!Array.isArray(payload.data)) return Response.json(payload, { status: response.status, headers: copyResponseHeaders(response) });
|
|
23
25
|
return Response.json({
|
|
24
26
|
...payload,
|
|
25
27
|
data: payload.data.map((model) => ({ ...model, aerial: aerialSupportForModel(model) }))
|
|
26
|
-
}, { status: response.status });
|
|
28
|
+
}, { status: response.status, headers: copyResponseHeaders(response) });
|
|
27
29
|
}
|
|
@@ -93,6 +93,7 @@ export async function proxyResponsesWebSocket(payload, headers, { initiator = "u
|
|
|
93
93
|
};
|
|
94
94
|
ws.addEventListener("message", async (event) => {
|
|
95
95
|
const message = await normalizeMessageData(event.data);
|
|
96
|
+
if (!message) return;
|
|
96
97
|
controller.enqueue(encoder.encode(toSseFrame(message)));
|
|
97
98
|
if (isTerminalMessage(message)) {
|
|
98
99
|
controller.close();
|
package/src/proxy/server.js
CHANGED
|
@@ -99,7 +99,7 @@ async function readBody(req, maxBytes = MAX_BODY_BYTES) {
|
|
|
99
99
|
|
|
100
100
|
async function handle(fetchRequest, runtime = {}) {
|
|
101
101
|
const url = new URL(fetchRequest.url);
|
|
102
|
-
const config = loadConfig();
|
|
102
|
+
const config = runtime.config || loadConfig();
|
|
103
103
|
if (fetchRequest.method === "GET" && url.pathname === "/health") {
|
|
104
104
|
return Response.json({ ok: true, service: "aerial", host: runtime.host || config.host, port: runtime.port || config.port });
|
|
105
105
|
}
|
|
@@ -157,8 +157,9 @@ export function createServer(runtime = {}) {
|
|
|
157
157
|
try {
|
|
158
158
|
logEvent("request_start", { method: req.method, path: req.url });
|
|
159
159
|
let localAuthValidated = false;
|
|
160
|
+
let config;
|
|
160
161
|
if (!publicRoute(req)) {
|
|
161
|
-
|
|
162
|
+
config = loadConfig();
|
|
162
163
|
if (!validateLocalAuth(nodeHeaderObject(req.headers), config)) {
|
|
163
164
|
const fetchResponse = json(401, { error: { type: "authentication_error", message: "Invalid or missing Aerial API key" } });
|
|
164
165
|
await writeNodeResponse(res, fetchResponse, controller.signal);
|
|
@@ -169,7 +170,7 @@ export function createServer(runtime = {}) {
|
|
|
169
170
|
}
|
|
170
171
|
const body = await readBody(req);
|
|
171
172
|
const fetchRequest = nodeRequestToFetch(req, body, controller.signal);
|
|
172
|
-
const fetchResponse = await handle(fetchRequest, { ...runtime, localAuthValidated });
|
|
173
|
+
const fetchResponse = await handle(fetchRequest, { ...runtime, localAuthValidated, config });
|
|
173
174
|
await writeNodeResponse(res, fetchResponse, controller.signal);
|
|
174
175
|
logEvent("request_end", { method: req.method, path: req.url, status: fetchResponse.status, ms: Date.now() - started });
|
|
175
176
|
} catch (error) {
|
package/src/setup/clients.js
CHANGED
|
@@ -88,6 +88,7 @@ export function setupClaude({ model, effort, apiKeyHelper = DEFAULT_CLAUDE_API_K
|
|
|
88
88
|
env: claudeEnvForAerial(current.env, config)
|
|
89
89
|
};
|
|
90
90
|
if (selectedModel) next.model = selectedModel;
|
|
91
|
+
if (normalizedEffort) next.effortLevel = normalizedEffort;
|
|
91
92
|
atomicWriteFile(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
92
93
|
if (normalizedEffort && config.defaultEffort !== normalizedEffort) {
|
|
93
94
|
saveConfig({ ...config, defaultEffort: normalizedEffort });
|
|
@@ -167,17 +168,19 @@ export function claudeStatus() {
|
|
|
167
168
|
const expectedBaseUrl = `http://${config.host}:${config.port}`;
|
|
168
169
|
const file = claudeSettingsFile();
|
|
169
170
|
const backups = backupPathsFor(file);
|
|
170
|
-
const
|
|
171
|
-
if (!fs.existsSync(file)) return { target: "claude", state: "missing", file, backups, effort };
|
|
171
|
+
const fallbackEffort = typeof config.defaultEffort === "string" && config.defaultEffort.trim() ? config.defaultEffort.trim() : "missing";
|
|
172
|
+
if (!fs.existsSync(file)) return { target: "claude", state: "missing", file, backups, effort: fallbackEffort };
|
|
172
173
|
let doc;
|
|
173
174
|
try {
|
|
174
175
|
doc = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
175
176
|
} catch (err) {
|
|
176
|
-
return { target: "claude", state: "invalid", file, backups, error: err.message, effort };
|
|
177
|
+
return { target: "claude", state: "invalid", file, backups, error: err.message, effort: fallbackEffort };
|
|
177
178
|
}
|
|
178
179
|
const state = claudeStateFromDoc(doc, expectedBaseUrl);
|
|
179
180
|
const model = typeof doc?.model === "string" ? doc.model : undefined;
|
|
180
181
|
const baseUrl = doc?.env?.ANTHROPIC_BASE_URL;
|
|
182
|
+
const settingsEffort = typeof doc?.effortLevel === "string" ? normalizeEffort(doc.effortLevel) : undefined;
|
|
183
|
+
const effort = settingsEffort || fallbackEffort;
|
|
181
184
|
return { target: "claude", state, file, backups, model, baseUrl, effort };
|
|
182
185
|
}
|
|
183
186
|
|
package/src/shared/file-utils.js
CHANGED
|
@@ -22,7 +22,7 @@ export function atomicWriteFile(file, content, { mode } = {}) {
|
|
|
22
22
|
throw err;
|
|
23
23
|
}
|
|
24
24
|
if (process.platform !== "win32" && mode !== undefined) {
|
|
25
|
-
fs.chmodSync(file, mode);
|
|
25
|
+
try { fs.chmodSync(file, mode); } catch {}
|
|
26
26
|
}
|
|
27
27
|
} catch (err) {
|
|
28
28
|
try { fs.unlinkSync(tmp); } catch {}
|