@auroraflow/code 0.0.6 → 0.0.8
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 +67 -189
- package/bin/aurora-claude.js +4 -0
- package/bin/aurora-codex.js +4 -0
- package/bin/aurora-postinstall.js +24 -0
- package/bin/aurora-sidecar.js +4 -0
- package/bin/aurora.js +2 -382
- package/lib/aurora-api.js +100 -0
- package/lib/commands.js +27 -0
- package/lib/launcher.js +9 -0
- package/lib/pickers.js +137 -0
- package/lib/prompt.js +136 -0
- package/lib/sidecar-client.js +10 -0
- package/lib/sidecar.js +1 -0
- package/lib/state.js +1 -0
- package/package.json +20 -24
- package/packages/cli/package.json +9 -0
- package/packages/cli/src/index.js +135 -0
- package/packages/clients/package.json +9 -0
- package/packages/clients/src/index.js +299 -0
- package/packages/protocol/package.json +9 -0
- package/packages/protocol/src/index.js +170 -0
- package/packages/protocol/src/index.test.js +180 -0
- package/packages/sidecar/package.json +9 -0
- package/packages/sidecar/src/index.js +177 -0
- package/packages/state/package.json +9 -0
- package/packages/state/src/index.js +57 -0
- package/scripts/install_posix_launcher.js +0 -106
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { AURORA_SIDECAR_PORT } from "../../protocol/src/index.js";
|
|
8
|
+
import { CLAUDE_HOME, CODEX_HOME, readSidecarInfo, readState, writeSidecarInfo } from "../../state/src/index.js";
|
|
9
|
+
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const packageRoot = resolve(here, "..", "..", "..");
|
|
12
|
+
const rootSidecarBin = join(packageRoot, "bin", "aurora-sidecar.js");
|
|
13
|
+
|
|
14
|
+
const OFFICIAL_CLIENTS = {
|
|
15
|
+
claude: {
|
|
16
|
+
name: "Claude Code",
|
|
17
|
+
packageName: "@anthropic-ai/claude-code",
|
|
18
|
+
binName: "claude"
|
|
19
|
+
},
|
|
20
|
+
codex: {
|
|
21
|
+
name: "Codex",
|
|
22
|
+
packageName: "@openai/codex",
|
|
23
|
+
binName: "codex"
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function createClientLaunchSpec(client, args = []) {
|
|
28
|
+
const sidecar = await ensureSidecar();
|
|
29
|
+
const state = await readState();
|
|
30
|
+
const model = selectedModelAlias(state);
|
|
31
|
+
const key = selectedKey(state);
|
|
32
|
+
if (client === "claude") {
|
|
33
|
+
const env = {
|
|
34
|
+
...process.env,
|
|
35
|
+
AURORA_HOME: process.env.AURORA_HOME ?? "",
|
|
36
|
+
CLAUDE_CONFIG_DIR: CLAUDE_HOME,
|
|
37
|
+
ANTHROPIC_BASE_URL: sidecar.baseURL,
|
|
38
|
+
ANTHROPIC_AUTH_TOKEN: sidecar.token,
|
|
39
|
+
// Claude Code v2.1.36+ sends attribution header by default.
|
|
40
|
+
// Keep request prefixes stable for DeepSeek prompt-cache reuse.
|
|
41
|
+
CLAUDE_CODE_ATTRIBUTION_HEADER: process.env.CLAUDE_CODE_ATTRIBUTION_HEADER ?? "0",
|
|
42
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
43
|
+
CLAUDE_CODE_DISABLE_TELEMETRY: "1",
|
|
44
|
+
DISABLE_AUTOUPDATER: "1"
|
|
45
|
+
};
|
|
46
|
+
env.ANTHROPIC_MODEL = model;
|
|
47
|
+
return { command: officialClientBin("claude"), args, env };
|
|
48
|
+
}
|
|
49
|
+
await writeCodexRuntimeFiles({ sidecar, model, key });
|
|
50
|
+
const env = {
|
|
51
|
+
...process.env,
|
|
52
|
+
AURORA_HOME: process.env.AURORA_HOME ?? "",
|
|
53
|
+
CODEX_HOME,
|
|
54
|
+
OPENAI_API_KEY: sidecar.token,
|
|
55
|
+
OPENAI_BASE_URL: `${sidecar.baseURL}/v1`
|
|
56
|
+
};
|
|
57
|
+
env.OPENAI_MODEL = model;
|
|
58
|
+
return { command: officialClientBin("codex"), args, env };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function runClient(client, args = []) {
|
|
62
|
+
const spec = await createClientLaunchSpec(client, args);
|
|
63
|
+
await spawnAndExit(spec.command, spec.args, spec.env);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function officialClientStatuses() {
|
|
67
|
+
return Object.entries(OFFICIAL_CLIENTS).map(([id, client]) => {
|
|
68
|
+
const bin = resolveOfficialClientBin(client.binName);
|
|
69
|
+
const packagePath = officialClientPackageJSONPath(client.packageName);
|
|
70
|
+
const version = readOfficialClientVersion(bin) ?? (packagePath ? JSON.parse(readFileSync(packagePath, "utf8")).version : null);
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
name: client.name,
|
|
74
|
+
packageName: client.packageName,
|
|
75
|
+
bin,
|
|
76
|
+
installed: Boolean(bin),
|
|
77
|
+
version
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function updateOfficialClients() {
|
|
83
|
+
await installOfficialClients();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function installOfficialClients() {
|
|
87
|
+
const npm = process.env.npm_execpath || "npm";
|
|
88
|
+
const args = ["install", "-g", "@anthropic-ai/claude-code@latest", "@openai/codex@latest"];
|
|
89
|
+
await spawnAndWait(npm, args, { ...process.env });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isGlobalNpmLifecycle() {
|
|
93
|
+
return process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function officialClientBin(client) {
|
|
97
|
+
const spec = OFFICIAL_CLIENTS[client];
|
|
98
|
+
if (!spec) throw new Error(`unknown official client: ${client}`);
|
|
99
|
+
const bin = resolveOfficialClientBin(spec.binName);
|
|
100
|
+
if (!bin) {
|
|
101
|
+
throw new Error(`${spec.name} is not installed. Run: aurora install-clients`);
|
|
102
|
+
}
|
|
103
|
+
return bin;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolveOfficialClientBin(binName) {
|
|
107
|
+
return findPathCommand(binName) ?? localClientBinPath(binName);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function localClientBinPath(binName) {
|
|
111
|
+
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
112
|
+
const bin = join(packageRoot, "node_modules", ".bin", `${binName}${suffix}`);
|
|
113
|
+
return existsSync(bin) ? bin : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function findPathCommand(binName) {
|
|
117
|
+
const suffixes = process.platform === "win32" ? [".cmd", ".exe", ""] : [""];
|
|
118
|
+
for (const dir of String(process.env.PATH ?? "").split(delimiter)) {
|
|
119
|
+
if (!dir) continue;
|
|
120
|
+
for (const suffix of suffixes) {
|
|
121
|
+
const candidate = join(dir, `${binName}${suffix}`);
|
|
122
|
+
if (existsSync(candidate)) return candidate;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function officialClientPackageJSONPath(packageName) {
|
|
129
|
+
const parts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
|
|
130
|
+
const packagePath = join(packageRoot, "node_modules", ...parts, "package.json");
|
|
131
|
+
return existsSync(packagePath) ? packagePath : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readOfficialClientVersion(bin) {
|
|
135
|
+
if (!bin) return null;
|
|
136
|
+
try {
|
|
137
|
+
const result = spawnSyncCompat(bin, ["--version"]);
|
|
138
|
+
if (result.status !== 0) return null;
|
|
139
|
+
return String(result.stdout || result.stderr || "").trim() || null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function ensureSidecar() {
|
|
146
|
+
try {
|
|
147
|
+
const info = await readSidecarInfo();
|
|
148
|
+
if (info?.port && info?.token && await pingSidecar(info)) return normalizedSidecarInfo(info);
|
|
149
|
+
} catch {
|
|
150
|
+
// Start below.
|
|
151
|
+
}
|
|
152
|
+
const token = `aurora-local-${randomBytes(24).toString("hex")}`;
|
|
153
|
+
const child = spawn(process.execPath, [process.env.AURORA_SIDECAR_BIN || rootSidecarBin, "--token", token], {
|
|
154
|
+
detached: true,
|
|
155
|
+
stdio: "ignore",
|
|
156
|
+
env: process.env
|
|
157
|
+
});
|
|
158
|
+
child.unref();
|
|
159
|
+
const info = { port: AURORA_SIDECAR_PORT, token, baseURL: `http://127.0.0.1:${AURORA_SIDECAR_PORT}` };
|
|
160
|
+
await waitForSidecar(info);
|
|
161
|
+
await writeSidecarInfo(info);
|
|
162
|
+
return info;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function writeCodexRuntimeFiles({ sidecar, model }) {
|
|
166
|
+
await mkdir(CODEX_HOME, { recursive: true });
|
|
167
|
+
const modelCatalogPath = join(CODEX_HOME, "model_catalog.json");
|
|
168
|
+
await writeCodexModelCatalog(sidecar, modelCatalogPath);
|
|
169
|
+
const config = `model = ${tomlString(model)}
|
|
170
|
+
model_provider = "aurora"
|
|
171
|
+
model_catalog_json = ${tomlString(modelCatalogPath)}
|
|
172
|
+
suppress_unstable_features_warning = true
|
|
173
|
+
|
|
174
|
+
[model_providers.aurora]
|
|
175
|
+
name = "Aurora"
|
|
176
|
+
base_url = ${tomlString(`${sidecar.baseURL}/v1`)}
|
|
177
|
+
env_key = "OPENAI_API_KEY"
|
|
178
|
+
wire_api = "responses"
|
|
179
|
+
requires_openai_auth = false
|
|
180
|
+
request_max_retries = 0
|
|
181
|
+
stream_max_retries = 0
|
|
182
|
+
stream_idle_timeout_ms = 300000
|
|
183
|
+
`;
|
|
184
|
+
const auth = {
|
|
185
|
+
OPENAI_API_KEY: sidecar.token,
|
|
186
|
+
tokens: null,
|
|
187
|
+
last_refresh: null
|
|
188
|
+
};
|
|
189
|
+
await writeFile(join(CODEX_HOME, "config.toml"), config, { mode: 0o600 });
|
|
190
|
+
await writeFile(join(CODEX_HOME, "auth.json"), `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function writeCodexModelCatalog(sidecar, modelCatalogPath) {
|
|
194
|
+
const clientVersion = "0.137.0";
|
|
195
|
+
const response = await fetch(
|
|
196
|
+
`${sidecar.baseURL}/v1/models?client_version=${encodeURIComponent(clientVersion)}`,
|
|
197
|
+
{
|
|
198
|
+
headers: { authorization: `Bearer ${sidecar.token}` },
|
|
199
|
+
signal: AbortSignal.timeout(5000)
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`Aurora Codex model catalog request failed with HTTP ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
const payload = await response.json();
|
|
206
|
+
if (!Array.isArray(payload.models) || payload.models.length === 0) {
|
|
207
|
+
throw new Error("Aurora Codex model catalog is empty");
|
|
208
|
+
}
|
|
209
|
+
const catalog = { models: payload.models };
|
|
210
|
+
const cache = {
|
|
211
|
+
fetched_at: new Date().toISOString(),
|
|
212
|
+
client_version: clientVersion,
|
|
213
|
+
models: payload.models
|
|
214
|
+
};
|
|
215
|
+
await writeFile(modelCatalogPath, `${JSON.stringify(catalog, null, 2)}\n`, { mode: 0o600 });
|
|
216
|
+
await writeFile(join(CODEX_HOME, "models_cache.json"), `${JSON.stringify(cache, null, 2)}\n`, { mode: 0o600 });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function waitForSidecar(info) {
|
|
220
|
+
const deadline = Date.now() + 2500;
|
|
221
|
+
while (Date.now() < deadline) {
|
|
222
|
+
if (await pingSidecar(info)) return;
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
224
|
+
}
|
|
225
|
+
throw new Error("Aurora sidecar did not become ready on 127.0.0.1:17878");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function pingSidecar(info) {
|
|
229
|
+
try {
|
|
230
|
+
const normalized = normalizedSidecarInfo(info);
|
|
231
|
+
const response = await fetch(`${normalized.baseURL}/aurora/status`, {
|
|
232
|
+
headers: { authorization: `Bearer ${normalized.token}` },
|
|
233
|
+
signal: AbortSignal.timeout(500)
|
|
234
|
+
});
|
|
235
|
+
return response.ok;
|
|
236
|
+
} catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizedSidecarInfo(info) {
|
|
242
|
+
return {
|
|
243
|
+
port: Number(info.port),
|
|
244
|
+
token: String(info.token),
|
|
245
|
+
baseURL: info.baseURL || `http://127.0.0.1:${Number(info.port)}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function selectedModelAlias(state) {
|
|
250
|
+
const model = state.selectedModel?.alias;
|
|
251
|
+
if (!model) throw new Error("Aurora runtime model is not selected. Select a model in Aurora Desktop > 本机调用.");
|
|
252
|
+
return model;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function selectedKey(state) {
|
|
256
|
+
const key = state.selectedKey?.presentedKey;
|
|
257
|
+
if (!key) throw new Error("Aurora runtime key is not selected. Select a key in Aurora Desktop > 本机调用.");
|
|
258
|
+
return key;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function tomlString(value) {
|
|
262
|
+
return JSON.stringify(String(value));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function spawnAndExit(command, args, env) {
|
|
266
|
+
const child = spawn(command, args, {
|
|
267
|
+
stdio: "inherit",
|
|
268
|
+
env,
|
|
269
|
+
shell: process.platform === "win32" && command.endsWith(".cmd")
|
|
270
|
+
});
|
|
271
|
+
child.on("error", error => {
|
|
272
|
+
console.error(`Failed to start ${command}: ${error.message}`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
});
|
|
275
|
+
child.on("exit", code => process.exit(code ?? 1));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function spawnAndWait(command, args, env) {
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
const child = spawn(command, args, {
|
|
281
|
+
cwd: packageRoot,
|
|
282
|
+
stdio: "inherit",
|
|
283
|
+
env,
|
|
284
|
+
shell: process.platform === "win32"
|
|
285
|
+
});
|
|
286
|
+
child.on("error", reject);
|
|
287
|
+
child.on("exit", code => {
|
|
288
|
+
if (code === 0) resolve();
|
|
289
|
+
else reject(new Error(`${command} exited with code ${code ?? 1}`));
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function spawnSyncCompat(command, args) {
|
|
295
|
+
return spawnSync(command, args, {
|
|
296
|
+
encoding: "utf8",
|
|
297
|
+
shell: process.platform === "win32" && command.endsWith(".cmd")
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export const AURORA_SIDECAR_PORT = 17878;
|
|
2
|
+
export const AURORA_LOCALHOST = "127.0.0.1";
|
|
3
|
+
|
|
4
|
+
export function trimSlash(value) {
|
|
5
|
+
return String(value).replace(/\/+$/, "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function selectedKeyHeaders(state) {
|
|
9
|
+
const key = state.selectedKey?.presentedKey;
|
|
10
|
+
return key ? { authorization: `Bearer ${key}`, "x-api-key": key } : {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function rewriteRuntimeModel(raw, selectedAlias, capabilitiesByAlias = {}) {
|
|
14
|
+
if (!raw.trim()) return raw;
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (selectedAlias) parsed.model = selectedAlias;
|
|
18
|
+
filterHostedWebSearch(parsed, capabilitiesByAlias);
|
|
19
|
+
return JSON.stringify(parsed);
|
|
20
|
+
} catch {
|
|
21
|
+
return raw;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function filterHostedWebSearch(payload, capabilitiesByAlias) {
|
|
26
|
+
const model = String(payload.model ?? "");
|
|
27
|
+
const supportsWebSearch = capabilitiesByAlias[model]?.supports_web_search === true;
|
|
28
|
+
if (supportsWebSearch || !Array.isArray(payload.tools)) return;
|
|
29
|
+
payload.tools = payload.tools.filter(tool => tool?.type !== "web_search");
|
|
30
|
+
if (payload.tools.length === 0) delete payload.tools;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function toClientModelItem(item) {
|
|
34
|
+
const provider = String(item.provider ?? "aurora").replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
35
|
+
const modelID = encodeURIComponent(String(item.model_id ?? item.alias ?? item.id));
|
|
36
|
+
return {
|
|
37
|
+
id: `claude-${provider}/${modelID}`,
|
|
38
|
+
object: "model",
|
|
39
|
+
owned_by: provider,
|
|
40
|
+
display_name: item.display_name || `${provider} / ${item.model_id ?? item.alias ?? item.id}`,
|
|
41
|
+
aurora_alias: item.alias ?? item.id,
|
|
42
|
+
aurora_provider: item.provider,
|
|
43
|
+
aurora_model_id: item.model_id
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function toCodexModelInfo(item, priority = 0) {
|
|
48
|
+
const alias = String(item.alias ?? item.id ?? item.model_id ?? "");
|
|
49
|
+
const displayName = item.display_name || `${item.provider ?? "aurora"} / ${item.model_id ?? alias}`;
|
|
50
|
+
const providerName = providerDisplayName(item.provider);
|
|
51
|
+
const supportsThinking = Boolean(item.supports_thinking);
|
|
52
|
+
const supportsTools = Boolean(item.supports_tools);
|
|
53
|
+
const supportsImages = Boolean(item.supports_images);
|
|
54
|
+
const supportsWebSearch = Boolean(item.supports_web_search);
|
|
55
|
+
const contextWindow = positiveNumber(item.context_window ?? item.context_window_tokens, 256000);
|
|
56
|
+
const maxContextWindow = positiveNumber(item.max_context_window, contextWindow);
|
|
57
|
+
const reasoningLevels = Array.isArray(item.supported_reasoning_levels)
|
|
58
|
+
? item.supported_reasoning_levels
|
|
59
|
+
: defaultReasoningLevels(supportsThinking);
|
|
60
|
+
const modalities = Array.isArray(item.input_modalities)
|
|
61
|
+
? item.input_modalities
|
|
62
|
+
: Array.isArray(item.modalities)
|
|
63
|
+
? item.modalities
|
|
64
|
+
: supportsImages
|
|
65
|
+
? ["text", "image"]
|
|
66
|
+
: ["text"];
|
|
67
|
+
const applyPatchToolType = Object.hasOwn(item, "apply_patch_tool_type")
|
|
68
|
+
? item.apply_patch_tool_type
|
|
69
|
+
: supportsTools
|
|
70
|
+
? "freeform"
|
|
71
|
+
: null;
|
|
72
|
+
return {
|
|
73
|
+
slug: alias,
|
|
74
|
+
display_name: displayName,
|
|
75
|
+
description: item.description || `${providerName} Provider`,
|
|
76
|
+
default_reasoning_level: item.default_reasoning_level ?? (supportsThinking ? "medium" : null),
|
|
77
|
+
supported_reasoning_levels: reasoningLevels,
|
|
78
|
+
shell_type: item.shell_type ?? "shell_command",
|
|
79
|
+
visibility: item.visibility ?? "list",
|
|
80
|
+
supported_in_api: item.supported_in_api ?? true,
|
|
81
|
+
priority: Number(item.priority ?? priority + 1),
|
|
82
|
+
additional_speed_tiers: Array.isArray(item.additional_speed_tiers) ? item.additional_speed_tiers : [],
|
|
83
|
+
service_tiers: Array.isArray(item.service_tiers) ? item.service_tiers : [],
|
|
84
|
+
default_service_tier: item.default_service_tier ?? null,
|
|
85
|
+
availability_nux: item.availability_nux ?? null,
|
|
86
|
+
upgrade: item.upgrade ?? null,
|
|
87
|
+
base_instructions: item.base_instructions || "You are a coding model routed through Aurora sidecar.",
|
|
88
|
+
model_messages: item.model_messages ?? null,
|
|
89
|
+
supports_reasoning_summaries: item.supports_reasoning_summaries ?? supportsThinking,
|
|
90
|
+
default_reasoning_summary: item.default_reasoning_summary ?? "none",
|
|
91
|
+
support_verbosity: item.support_verbosity ?? false,
|
|
92
|
+
default_verbosity: Object.hasOwn(item, "default_verbosity") ? item.default_verbosity : null,
|
|
93
|
+
apply_patch_tool_type: applyPatchToolType,
|
|
94
|
+
web_search_tool_type: Object.hasOwn(item, "web_search_tool_type") && item.web_search_tool_type
|
|
95
|
+
? item.web_search_tool_type
|
|
96
|
+
: supportsWebSearch
|
|
97
|
+
? (supportsImages ? "text_and_image" : "text")
|
|
98
|
+
: "text",
|
|
99
|
+
truncation_policy: item.truncation_policy ?? { mode: "tokens", limit: 10000 },
|
|
100
|
+
supports_parallel_tool_calls: item.supports_parallel_tool_calls ?? supportsTools,
|
|
101
|
+
supports_image_detail_original: item.supports_image_detail_original ?? supportsImages,
|
|
102
|
+
context_window: contextWindow,
|
|
103
|
+
max_context_window: maxContextWindow,
|
|
104
|
+
auto_compact_token_limit: item.auto_compact_token_limit ?? null,
|
|
105
|
+
effective_context_window_percent: positiveNumber(item.effective_context_window_percent, 95),
|
|
106
|
+
experimental_supported_tools: Array.isArray(item.experimental_supported_tools)
|
|
107
|
+
? item.experimental_supported_tools
|
|
108
|
+
: [],
|
|
109
|
+
input_modalities: modalities,
|
|
110
|
+
supports_search_tool: item.supports_search_tool ?? supportsWebSearch,
|
|
111
|
+
auto_review_model_override: item.auto_review_model_override ?? null,
|
|
112
|
+
tool_mode: item.tool_mode ?? null,
|
|
113
|
+
multi_agent_version: item.multi_agent_version ?? null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function positiveNumber(value, defaultValue) {
|
|
118
|
+
const parsed = Number(value);
|
|
119
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function defaultReasoningLevels(supportsThinking) {
|
|
123
|
+
if (!supportsThinking) return [];
|
|
124
|
+
return [
|
|
125
|
+
{ effort: "minimal", description: "Fastest response with shallow reasoning" },
|
|
126
|
+
{ effort: "low", description: "Lower latency and lighter reasoning depth" },
|
|
127
|
+
{ effort: "medium", description: "Balanced reasoning quality and speed" },
|
|
128
|
+
{ effort: "high", description: "Deep reasoning for complex coding tasks" },
|
|
129
|
+
{ effort: "xhigh", description: "Maximum reasoning depth for hardest tasks" }
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function providerDisplayName(provider) {
|
|
134
|
+
const raw = String(provider ?? "Aurora").trim().toLowerCase();
|
|
135
|
+
if (!raw) return "Aurora";
|
|
136
|
+
const known = {
|
|
137
|
+
antigravity: "Antigravity",
|
|
138
|
+
anthropic: "Anthropic",
|
|
139
|
+
claude: "Claude",
|
|
140
|
+
codex: "Codex",
|
|
141
|
+
cursor: "Cursor",
|
|
142
|
+
deepseek: "DeepSeek",
|
|
143
|
+
gemini: "Gemini",
|
|
144
|
+
google: "Google",
|
|
145
|
+
kimi: "Kimi",
|
|
146
|
+
kiro: "Kiro",
|
|
147
|
+
minimax: "MiniMax",
|
|
148
|
+
openai: "OpenAI",
|
|
149
|
+
openrouter: "OpenRouter",
|
|
150
|
+
qwen: "Qwen",
|
|
151
|
+
siliconflow: "SiliconFlow",
|
|
152
|
+
volcengine_ark: "Volcengine Ark",
|
|
153
|
+
windsurf: "Windsurf",
|
|
154
|
+
xai: "xAI",
|
|
155
|
+
zhipu: "Zhipu"
|
|
156
|
+
};
|
|
157
|
+
if (known[raw]) return known[raw];
|
|
158
|
+
return raw
|
|
159
|
+
.split(/[-_\s]+/)
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
162
|
+
.join(" ");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isRuntimePath(pathname) {
|
|
166
|
+
return pathname === "/v1/messages" ||
|
|
167
|
+
pathname === "/v1/responses" ||
|
|
168
|
+
pathname === "/v1/chat/completions" ||
|
|
169
|
+
pathname === "/v1/messages/count_tokens";
|
|
170
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { rewriteRuntimeModel, toCodexModelInfo } from "./index.js";
|
|
5
|
+
|
|
6
|
+
const CODEX_MODEL_INFO_FIELDS = [
|
|
7
|
+
"additional_speed_tiers",
|
|
8
|
+
"apply_patch_tool_type",
|
|
9
|
+
"auto_compact_token_limit",
|
|
10
|
+
"auto_review_model_override",
|
|
11
|
+
"availability_nux",
|
|
12
|
+
"base_instructions",
|
|
13
|
+
"context_window",
|
|
14
|
+
"default_reasoning_level",
|
|
15
|
+
"default_reasoning_summary",
|
|
16
|
+
"default_service_tier",
|
|
17
|
+
"default_verbosity",
|
|
18
|
+
"description",
|
|
19
|
+
"display_name",
|
|
20
|
+
"effective_context_window_percent",
|
|
21
|
+
"experimental_supported_tools",
|
|
22
|
+
"input_modalities",
|
|
23
|
+
"max_context_window",
|
|
24
|
+
"model_messages",
|
|
25
|
+
"multi_agent_version",
|
|
26
|
+
"priority",
|
|
27
|
+
"service_tiers",
|
|
28
|
+
"shell_type",
|
|
29
|
+
"slug",
|
|
30
|
+
"support_verbosity",
|
|
31
|
+
"supported_in_api",
|
|
32
|
+
"supported_reasoning_levels",
|
|
33
|
+
"supports_image_detail_original",
|
|
34
|
+
"supports_parallel_tool_calls",
|
|
35
|
+
"supports_reasoning_summaries",
|
|
36
|
+
"supports_search_tool",
|
|
37
|
+
"tool_mode",
|
|
38
|
+
"truncation_policy",
|
|
39
|
+
"upgrade",
|
|
40
|
+
"visibility",
|
|
41
|
+
"web_search_tool_type"
|
|
42
|
+
].sort();
|
|
43
|
+
|
|
44
|
+
test("projects published reasoning and tool capabilities into Codex metadata", () => {
|
|
45
|
+
const model = toCodexModelInfo({
|
|
46
|
+
alias: "deepseek/deepseek-v4-flash",
|
|
47
|
+
provider: "deepseek",
|
|
48
|
+
model_id: "deepseek-v4-flash",
|
|
49
|
+
context_window_tokens: 1048576,
|
|
50
|
+
supports_tools: true,
|
|
51
|
+
supports_images: false,
|
|
52
|
+
supports_thinking: true,
|
|
53
|
+
supports_web_search: false,
|
|
54
|
+
default_reasoning_level: "high",
|
|
55
|
+
supported_reasoning_levels: [
|
|
56
|
+
{ effort: "minimal", description: "off" },
|
|
57
|
+
{ effort: "high", description: "high" },
|
|
58
|
+
{ effort: "xhigh", description: "max" }
|
|
59
|
+
],
|
|
60
|
+
supports_reasoning_summaries: true,
|
|
61
|
+
shell_type: "shell_command",
|
|
62
|
+
truncation_policy: { mode: "tokens", limit: 10000 },
|
|
63
|
+
supports_parallel_tool_calls: true,
|
|
64
|
+
tool_mode: null,
|
|
65
|
+
multi_agent_version: null
|
|
66
|
+
}, 4);
|
|
67
|
+
|
|
68
|
+
assert.equal(model.slug, "deepseek/deepseek-v4-flash");
|
|
69
|
+
assert.equal(model.priority, 5);
|
|
70
|
+
assert.equal(model.supports_reasoning_summaries, true);
|
|
71
|
+
assert.equal(model.default_reasoning_level, "high");
|
|
72
|
+
assert.deepEqual(model.supported_reasoning_levels.map(level => level.effort), [
|
|
73
|
+
"minimal",
|
|
74
|
+
"high",
|
|
75
|
+
"xhigh"
|
|
76
|
+
]);
|
|
77
|
+
assert.equal(model.shell_type, "shell_command");
|
|
78
|
+
assert.equal(model.supports_parallel_tool_calls, true);
|
|
79
|
+
assert.equal(model.apply_patch_tool_type, "freeform");
|
|
80
|
+
assert.equal(model.supports_search_tool, false);
|
|
81
|
+
assert.equal(model.web_search_tool_type, "text");
|
|
82
|
+
assert.deepEqual(model.input_modalities, ["text"]);
|
|
83
|
+
assert.deepEqual(model.truncation_policy, { mode: "tokens", limit: 10000 });
|
|
84
|
+
assert.equal(model.auto_compact_token_limit, null);
|
|
85
|
+
assert.equal(model.tool_mode, null);
|
|
86
|
+
assert.equal(model.multi_agent_version, null);
|
|
87
|
+
assert.deepEqual(Object.keys(model).sort(), CODEX_MODEL_INFO_FIELDS);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("does not advertise unsupported capabilities", () => {
|
|
91
|
+
const model = toCodexModelInfo({
|
|
92
|
+
alias: "provider/text-only",
|
|
93
|
+
provider: "provider",
|
|
94
|
+
model_id: "text-only",
|
|
95
|
+
supports_tools: false,
|
|
96
|
+
supports_images: false,
|
|
97
|
+
supports_thinking: false,
|
|
98
|
+
supports_web_search: false
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.equal(model.default_reasoning_level, null);
|
|
102
|
+
assert.deepEqual(model.supported_reasoning_levels, []);
|
|
103
|
+
assert.equal(model.supports_reasoning_summaries, false);
|
|
104
|
+
assert.equal(model.apply_patch_tool_type, null);
|
|
105
|
+
assert.equal(model.supports_parallel_tool_calls, false);
|
|
106
|
+
assert.equal(model.supports_image_detail_original, false);
|
|
107
|
+
assert.equal(model.supports_search_tool, false);
|
|
108
|
+
assert.equal(model.web_search_tool_type, "text");
|
|
109
|
+
assert.equal(model.support_verbosity, false);
|
|
110
|
+
assert.equal(model.default_verbosity, null);
|
|
111
|
+
assert.deepEqual(model.input_modalities, ["text"]);
|
|
112
|
+
assert.deepEqual(Object.keys(model).sort(), CODEX_MODEL_INFO_FIELDS);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("preserves explicit Codex deferred tool search capability", () => {
|
|
116
|
+
const model = toCodexModelInfo({
|
|
117
|
+
alias: "cx/gpt-5.5",
|
|
118
|
+
provider: "codex",
|
|
119
|
+
model_id: "gpt-5.5",
|
|
120
|
+
supports_tools: true,
|
|
121
|
+
supports_images: true,
|
|
122
|
+
supports_thinking: true,
|
|
123
|
+
supports_web_search: true,
|
|
124
|
+
supports_search_tool: true,
|
|
125
|
+
web_search_tool_type: "text_and_image"
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
assert.equal(model.supports_search_tool, true);
|
|
129
|
+
assert.equal(model.web_search_tool_type, "text_and_image");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("preserves explicit disabled deferred tool search despite hosted web search", () => {
|
|
133
|
+
const model = toCodexModelInfo({
|
|
134
|
+
alias: "provider/hosted-search-only",
|
|
135
|
+
provider: "provider",
|
|
136
|
+
model_id: "hosted-search-only",
|
|
137
|
+
supports_tools: true,
|
|
138
|
+
supports_images: false,
|
|
139
|
+
supports_thinking: false,
|
|
140
|
+
supports_web_search: true,
|
|
141
|
+
supports_search_tool: false,
|
|
142
|
+
web_search_tool_type: "text"
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
assert.equal(model.supports_search_tool, false);
|
|
146
|
+
assert.equal(model.web_search_tool_type, "text");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("strips hosted web search from runtime requests for unsupported models", () => {
|
|
150
|
+
const raw = JSON.stringify({
|
|
151
|
+
model: "deepseek/deepseek-v4-flash",
|
|
152
|
+
tools: [
|
|
153
|
+
{ type: "web_search" },
|
|
154
|
+
{ type: "function", name: "shell" }
|
|
155
|
+
],
|
|
156
|
+
input: "hi"
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const rewritten = JSON.parse(rewriteRuntimeModel(raw, "", {
|
|
160
|
+
"deepseek/deepseek-v4-flash": { supports_web_search: false }
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
assert.equal(rewritten.model, "deepseek/deepseek-v4-flash");
|
|
164
|
+
assert.deepEqual(rewritten.tools, [{ type: "function", name: "shell" }]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("keeps hosted web search for supported selected models", () => {
|
|
168
|
+
const raw = JSON.stringify({
|
|
169
|
+
model: "gpt-5.5",
|
|
170
|
+
tools: [{ type: "web_search" }],
|
|
171
|
+
input: "hi"
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const rewritten = JSON.parse(rewriteRuntimeModel(raw, "cx/gpt-5.5", {
|
|
175
|
+
"cx/gpt-5.5": { supports_web_search: true }
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
assert.equal(rewritten.model, "cx/gpt-5.5");
|
|
179
|
+
assert.deepEqual(rewritten.tools, [{ type: "web_search" }]);
|
|
180
|
+
});
|