@auroraflow/code 0.0.7 → 0.0.9
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 +1 -0
- package/bin/aurora-claude.js +0 -0
- package/bin/aurora-codex.js +0 -0
- package/bin/aurora.js +0 -0
- package/package.json +7 -2
- package/packages/cli/src/index.js +23 -18
- package/packages/clients/src/index.js +32 -0
- package/packages/protocol/src/index.js +130 -3
- package/packages/protocol/src/index.test.js +180 -0
- package/packages/sidecar/src/index.js +79 -11
package/README.md
CHANGED
package/bin/aurora-claude.js
CHANGED
|
File without changes
|
package/bin/aurora-codex.js
CHANGED
|
File without changes
|
package/bin/aurora.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auroraflow/code",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Aurora launcher and sidecar for official Codex and Claude Code clients.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/auroraflowintl/aurora-code.git"
|
|
9
|
+
},
|
|
6
10
|
"bin": {
|
|
7
11
|
"aurora": "bin/aurora.js",
|
|
8
12
|
"aurora-claude": "bin/aurora-claude.js",
|
|
@@ -11,7 +15,8 @@
|
|
|
11
15
|
},
|
|
12
16
|
"scripts": {
|
|
13
17
|
"postinstall": "node bin/aurora-postinstall.js",
|
|
14
|
-
"check": "find bin lib packages -name '*.js' -print0 | xargs -0 -n1 node --check"
|
|
18
|
+
"check": "find bin lib packages -name '*.js' -print0 | xargs -0 -n1 node --check",
|
|
19
|
+
"test": "node --test packages/protocol/src/index.test.js"
|
|
15
20
|
},
|
|
16
21
|
"engines": {
|
|
17
22
|
"node": ">=24"
|
|
@@ -61,35 +61,40 @@ async function startInteractive(args) {
|
|
|
61
61
|
async function init() {
|
|
62
62
|
const existing = await readState();
|
|
63
63
|
const account = await readAccount();
|
|
64
|
+
const controlPlaneURL = account.controlPlaneURL ?? existing.controlPlaneURL ?? "https://auroramos.com/api/v1";
|
|
65
|
+
const gatewayURL = account.gatewayURL ?? existing.gatewayURL ?? "https://auroramos.com";
|
|
66
|
+
console.log(`Welcome to Aurora Code.
|
|
67
|
+
Sign in with your Aurora account to connect the local Claude/Codex launcher.
|
|
68
|
+
API key and model selection are managed in Aurora Desktop > 本机调用.
|
|
69
|
+
`);
|
|
64
70
|
const answers = await promptFields([
|
|
65
|
-
{ name: "controlPlaneURL", label: "Aurora control-plane URL", defaultValue: account.controlPlaneURL ?? existing.controlPlaneURL ?? "https://auroramos.com/api/v1" },
|
|
66
|
-
{ name: "gatewayURL", label: "Aurora gateway URL", defaultValue: account.gatewayURL ?? existing.gatewayURL ?? "https://auroramos.com" },
|
|
67
71
|
{ name: "email", label: "Aurora email", defaultValue: account.user?.email ?? "" },
|
|
68
|
-
{ name: "password", label: "Aurora password", defaultValue: "" }
|
|
69
|
-
{ name: "presentedKey", label: "Initial Aurora API key", defaultValue: existing.selectedKey?.presentedKey ?? "" },
|
|
70
|
-
{ name: "model", label: "Initial Aurora model alias", defaultValue: existing.selectedModel?.alias ?? "" }
|
|
72
|
+
{ name: "password", label: "Aurora password", defaultValue: "" }
|
|
71
73
|
]);
|
|
72
|
-
const login = await loginAurora(
|
|
74
|
+
const login = await loginAurora(controlPlaneURL, answers.email, answers.password);
|
|
73
75
|
const sessionToken = login.session?.token?.trim();
|
|
74
76
|
if (!sessionToken) throw new Error("Aurora login did not return session.token");
|
|
77
|
+
const user = login.user ? {
|
|
78
|
+
id: login.user.id,
|
|
79
|
+
email: login.user.email,
|
|
80
|
+
name: login.user.display_name || login.user.email
|
|
81
|
+
} : null;
|
|
75
82
|
await writeAccount({
|
|
76
|
-
controlPlaneURL
|
|
77
|
-
gatewayURL
|
|
83
|
+
controlPlaneURL,
|
|
84
|
+
gatewayURL,
|
|
78
85
|
sessionToken,
|
|
79
|
-
user
|
|
80
|
-
id: login.user.id,
|
|
81
|
-
email: login.user.email,
|
|
82
|
-
name: login.user.display_name || login.user.email
|
|
83
|
-
} : null
|
|
86
|
+
user
|
|
84
87
|
});
|
|
85
88
|
await writeState({
|
|
86
89
|
...existing,
|
|
87
|
-
controlPlaneURL
|
|
88
|
-
gatewayURL
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
controlPlaneURL,
|
|
91
|
+
gatewayURL,
|
|
92
|
+
account: {
|
|
93
|
+
sessionToken,
|
|
94
|
+
user
|
|
95
|
+
}
|
|
91
96
|
});
|
|
92
|
-
console.log("Aurora
|
|
97
|
+
console.log("Aurora account initialized. Select API key and model in Aurora Desktop > 本机调用.");
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
async function loginAurora(controlPlaneURL, email, password) {
|
|
@@ -36,6 +36,9 @@ export async function createClientLaunchSpec(client, args = []) {
|
|
|
36
36
|
CLAUDE_CONFIG_DIR: CLAUDE_HOME,
|
|
37
37
|
ANTHROPIC_BASE_URL: sidecar.baseURL,
|
|
38
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",
|
|
39
42
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
40
43
|
CLAUDE_CODE_DISABLE_TELEMETRY: "1",
|
|
41
44
|
DISABLE_AUTOUPDATER: "1"
|
|
@@ -161,8 +164,11 @@ async function ensureSidecar() {
|
|
|
161
164
|
|
|
162
165
|
async function writeCodexRuntimeFiles({ sidecar, model }) {
|
|
163
166
|
await mkdir(CODEX_HOME, { recursive: true });
|
|
167
|
+
const modelCatalogPath = join(CODEX_HOME, "model_catalog.json");
|
|
168
|
+
await writeCodexModelCatalog(sidecar, modelCatalogPath);
|
|
164
169
|
const config = `model = ${tomlString(model)}
|
|
165
170
|
model_provider = "aurora"
|
|
171
|
+
model_catalog_json = ${tomlString(modelCatalogPath)}
|
|
166
172
|
suppress_unstable_features_warning = true
|
|
167
173
|
|
|
168
174
|
[model_providers.aurora]
|
|
@@ -184,6 +190,32 @@ stream_idle_timeout_ms = 300000
|
|
|
184
190
|
await writeFile(join(CODEX_HOME, "auth.json"), `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600 });
|
|
185
191
|
}
|
|
186
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
|
+
|
|
187
219
|
async function waitForSidecar(info) {
|
|
188
220
|
const deadline = Date.now() + 2500;
|
|
189
221
|
while (Date.now() < deadline) {
|
|
@@ -10,17 +10,26 @@ export function selectedKeyHeaders(state) {
|
|
|
10
10
|
return key ? { authorization: `Bearer ${key}`, "x-api-key": key } : {};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function rewriteRuntimeModel(raw, selectedAlias) {
|
|
14
|
-
if (!
|
|
13
|
+
export function rewriteRuntimeModel(raw, selectedAlias, capabilitiesByAlias = {}) {
|
|
14
|
+
if (!raw.trim()) return raw;
|
|
15
15
|
try {
|
|
16
16
|
const parsed = JSON.parse(raw);
|
|
17
|
-
parsed.model = selectedAlias;
|
|
17
|
+
if (selectedAlias) parsed.model = selectedAlias;
|
|
18
|
+
filterHostedWebSearch(parsed, capabilitiesByAlias);
|
|
18
19
|
return JSON.stringify(parsed);
|
|
19
20
|
} catch {
|
|
20
21
|
return raw;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
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
|
+
|
|
24
33
|
export function toClientModelItem(item) {
|
|
25
34
|
const provider = String(item.provider ?? "aurora").replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
26
35
|
const modelID = encodeURIComponent(String(item.model_id ?? item.alias ?? item.id));
|
|
@@ -35,6 +44,124 @@ export function toClientModelItem(item) {
|
|
|
35
44
|
};
|
|
36
45
|
}
|
|
37
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
|
+
|
|
38
165
|
export function isRuntimePath(pathname) {
|
|
39
166
|
return pathname === "/v1/messages" ||
|
|
40
167
|
pathname === "/v1/responses" ||
|
|
@@ -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
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { AURORA_LOCALHOST, AURORA_SIDECAR_PORT, isRuntimePath, rewriteRuntimeModel, selectedKeyHeaders, toClientModelItem, toCodexModelInfo, trimSlash } from "../../protocol/src/index.js";
|
|
5
|
+
import { CODEX_HOME, readState, writeSidecarInfo } from "../../state/src/index.js";
|
|
6
|
+
|
|
7
|
+
const CODEX_MODEL_CAPABILITIES_PATH = join(CODEX_HOME, "model_capabilities.json");
|
|
4
8
|
|
|
5
9
|
export async function startSidecar(options = {}) {
|
|
6
10
|
const token = options.token || readArg("--token") || process.env.AURORA_SIDECAR_TOKEN || "aurora-local-dev";
|
|
@@ -33,7 +37,7 @@ async function handle(request, response, token) {
|
|
|
33
37
|
return;
|
|
34
38
|
}
|
|
35
39
|
if (request.method === "GET" && url.pathname === "/v1/models") {
|
|
36
|
-
await proxyModels(response);
|
|
40
|
+
await proxyModels(response, url);
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
if (request.method === "POST" && isRuntimePath(url.pathname)) {
|
|
@@ -43,37 +47,78 @@ async function handle(request, response, token) {
|
|
|
43
47
|
writeJSON(response, 404, { error: { type: "not_found", message: "unknown sidecar route" } });
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
async function proxyModels(response) {
|
|
50
|
+
async function proxyModels(response, incomingURL) {
|
|
47
51
|
const state = await readState();
|
|
48
52
|
const gatewayURL = trimSlash(state.gatewayURL ?? "https://auroramos.com");
|
|
49
|
-
const
|
|
50
|
-
|
|
53
|
+
const clientVersion = incomingURL.searchParams.get("client_version");
|
|
54
|
+
const upstreamURL = clientVersion
|
|
55
|
+
? `${gatewayURL}/v1/aurora-cli/models?client_version=${encodeURIComponent(clientVersion)}`
|
|
56
|
+
: `${gatewayURL}/v1/aurora-cli/models`;
|
|
57
|
+
const upstream = await fetch(upstreamURL, {
|
|
58
|
+
headers: {
|
|
59
|
+
...selectedKeyHeaders(state),
|
|
60
|
+
"accept-encoding": "identity"
|
|
61
|
+
}
|
|
51
62
|
});
|
|
52
63
|
const payload = await upstream.json().catch(() => ({ object: "list", data: [] }));
|
|
53
|
-
const
|
|
64
|
+
const sourceModels = Array.isArray(payload.data) ? payload.data : [];
|
|
65
|
+
if (clientVersion) {
|
|
66
|
+
await writeCodexModelCapabilities(sourceModels);
|
|
67
|
+
const models = sourceModels.map((item, index) => toCodexModelInfo(item, index));
|
|
68
|
+
writeJSON(response, upstream.ok ? 200 : upstream.status, { models });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const data = sourceModels.map(toClientModelItem);
|
|
54
72
|
writeJSON(response, upstream.ok ? 200 : upstream.status, { object: "list", data });
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
async function proxyRuntime(request, response, path) {
|
|
58
76
|
const state = await readState();
|
|
59
77
|
const body = await readBody(request);
|
|
78
|
+
const capabilitiesByAlias = await readCodexModelCapabilities();
|
|
60
79
|
const gatewayURL = trimSlash(state.gatewayURL ?? "https://auroramos.com");
|
|
61
80
|
const upstream = await fetch(`${gatewayURL}${path}`, {
|
|
62
81
|
method: request.method,
|
|
63
82
|
headers: {
|
|
64
83
|
...copyForwardHeaders(request.headers),
|
|
65
84
|
...selectedKeyHeaders(state),
|
|
66
|
-
"content-type": request.headers["content-type"] ?? "application/json"
|
|
85
|
+
"content-type": request.headers["content-type"] ?? "application/json",
|
|
86
|
+
"accept-encoding": "identity"
|
|
67
87
|
},
|
|
68
|
-
body: rewriteRuntimeModel(body, state.selectedModel?.alias)
|
|
88
|
+
body: rewriteRuntimeModel(body, state.selectedModel?.alias, capabilitiesByAlias)
|
|
69
89
|
});
|
|
70
|
-
response.writeHead(upstream.status,
|
|
90
|
+
response.writeHead(upstream.status, responseHeaders(upstream.headers));
|
|
71
91
|
if (upstream.body) {
|
|
72
92
|
for await (const chunk of upstream.body) response.write(chunk);
|
|
73
93
|
}
|
|
74
94
|
response.end();
|
|
75
95
|
}
|
|
76
96
|
|
|
97
|
+
async function writeCodexModelCapabilities(sourceModels) {
|
|
98
|
+
const models = {};
|
|
99
|
+
for (const item of sourceModels) {
|
|
100
|
+
const alias = String(item.alias ?? item.id ?? item.model_id ?? "");
|
|
101
|
+
if (!alias) continue;
|
|
102
|
+
models[alias] = {
|
|
103
|
+
supports_web_search: item.supports_web_search === true,
|
|
104
|
+
web_search_tool_type: item.web_search_tool_type ?? null
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
await writeFile(CODEX_MODEL_CAPABILITIES_PATH, `${JSON.stringify({
|
|
108
|
+
generated_at: new Date().toISOString(),
|
|
109
|
+
models
|
|
110
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function readCodexModelCapabilities() {
|
|
114
|
+
try {
|
|
115
|
+
const payload = JSON.parse(await readFile(CODEX_MODEL_CAPABILITIES_PATH, "utf8"));
|
|
116
|
+
return payload && typeof payload.models === "object" && payload.models !== null ? payload.models : {};
|
|
117
|
+
} catch {
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
77
122
|
function isAuthorized(request, token) {
|
|
78
123
|
const auth = String(request.headers.authorization ?? "");
|
|
79
124
|
const xKey = String(request.headers["x-api-key"] ?? "");
|
|
@@ -83,12 +128,35 @@ function isAuthorized(request, token) {
|
|
|
83
128
|
function copyForwardHeaders(headers) {
|
|
84
129
|
const out = {};
|
|
85
130
|
for (const [key, value] of Object.entries(headers)) {
|
|
86
|
-
if (["host", "authorization", "x-api-key", "content-length"].includes(key.toLowerCase())) continue;
|
|
131
|
+
if (["host", "authorization", "x-api-key", "content-length", "accept-encoding"].includes(key.toLowerCase())) continue;
|
|
87
132
|
if (value !== undefined) out[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
88
133
|
}
|
|
89
134
|
return out;
|
|
90
135
|
}
|
|
91
136
|
|
|
137
|
+
function responseHeaders(headers) {
|
|
138
|
+
const out = {};
|
|
139
|
+
for (const [key, value] of headers.entries()) {
|
|
140
|
+
const normalized = key.toLowerCase();
|
|
141
|
+
if ([
|
|
142
|
+
"connection",
|
|
143
|
+
"content-encoding",
|
|
144
|
+
"content-length",
|
|
145
|
+
"keep-alive",
|
|
146
|
+
"proxy-authenticate",
|
|
147
|
+
"proxy-authorization",
|
|
148
|
+
"te",
|
|
149
|
+
"trailer",
|
|
150
|
+
"transfer-encoding",
|
|
151
|
+
"upgrade"
|
|
152
|
+
].includes(normalized)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
out[key] = value;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
92
160
|
function readBody(request) {
|
|
93
161
|
return new Promise((resolve, reject) => {
|
|
94
162
|
const chunks = [];
|