@bitkyc08/opencodex 1.9.4 → 2.0.0
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.ko.md +95 -71
- package/README.md +93 -46
- package/README.zh-CN.md +101 -70
- package/gui/dist/assets/index-DRr-3yL3.css +1 -0
- package/gui/dist/assets/index-LGqpEmI5.js +9 -0
- package/gui/dist/index.html +13 -3
- package/package.json +1 -3
- package/src/adapters/openai-chat.ts +34 -20
- package/src/bridge.ts +13 -5
- package/src/cli.ts +18 -11
- package/src/codex-catalog.ts +147 -31
- package/src/codex-inject.ts +46 -13
- package/src/config.ts +2 -1
- package/src/oauth/index.ts +28 -12
- package/src/oauth/key-providers.ts +27 -0
- package/src/providers/derive.ts +35 -0
- package/src/providers/registry.ts +130 -7
- package/src/reasoning-effort.ts +102 -0
- package/src/responses/parser.ts +1 -1
- package/src/server.ts +19 -2
- package/src/service.ts +26 -2
- package/src/star-prompt.ts +5 -4
- package/src/types.ts +22 -0
- package/src/ws-bridge.ts +5 -2
- package/gui/dist/assets/index-C1wlp1SM.css +0 -1
- package/gui/dist/assets/index-CDhJ0DI7.js +0 -9
- package/scripts/postinstall.mjs +0 -57
package/src/codex-inject.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { atomicWriteFile, websocketsEnabled } from "./config";
|
|
3
3
|
import { restoreCodexCatalog } from "./codex-catalog";
|
|
4
|
-
import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH, DEFAULT_CATALOG_PATH, parseTomlString, readRootTomlString, tomlString } from "./codex-paths";
|
|
4
|
+
import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH, DEFAULT_CATALOG_PATH, parseTomlString, readRootTomlString, resolveCodexConfigPath, tomlString } from "./codex-paths";
|
|
5
5
|
import type { OcxConfig } from "./types";
|
|
6
6
|
|
|
7
7
|
const OCX_SECTION_MARKER = "# Auto-injected by opencodex";
|
|
8
8
|
|
|
9
|
+
export interface InjectCodexOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Absolute or CODEX_HOME-relative catalog path to advertise to Codex. Pass `null` only when the
|
|
12
|
+
* opencodex catalog could not be materialized; Codex will then keep its native catalog instead of
|
|
13
|
+
* failing on a missing model_catalog_json file.
|
|
14
|
+
*/
|
|
15
|
+
catalogPath?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
/**
|
|
10
19
|
* The `[model_providers.opencodex]` TABLE only. A table is position-independent in TOML, so it is
|
|
11
20
|
* safe to append at EOF. The bare root key `model_provider = "opencodex"` is NOT included here —
|
|
@@ -83,10 +92,20 @@ function readRootModelCatalogPath(content: string): string | null {
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
function setRootModelCatalogPath(content: string, catalogPath: string): string {
|
|
86
|
-
if (readRootModelCatalogPath(content)) return content;
|
|
87
95
|
const lines = content.split("\n");
|
|
88
96
|
const firstTable = lines.findIndex(l => /^\s*\[/.test(l));
|
|
89
97
|
const key = `model_catalog_json = ${tomlString(catalogPath)}`;
|
|
98
|
+
const rootEnd = firstTable === -1 ? lines.length : firstTable;
|
|
99
|
+
for (let i = 0; i < rootEnd; i++) {
|
|
100
|
+
const m = lines[i].match(/^\s*model_catalog_json\s*=\s*("(?:\\.|[^"])*"|'[^']*')\s*$/);
|
|
101
|
+
if (!m) continue;
|
|
102
|
+
const existing = parseTomlString(m[1]);
|
|
103
|
+
if (isOpencodexCatalogPath(existing)) {
|
|
104
|
+
lines[i] = key;
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
return content;
|
|
108
|
+
}
|
|
90
109
|
if (firstTable === -1) {
|
|
91
110
|
return content.replace(/\n+$/, "") + "\n" + key + "\n";
|
|
92
111
|
}
|
|
@@ -157,20 +176,30 @@ function stripOpencodexCatalogPath(content: string): string {
|
|
|
157
176
|
.join("\n");
|
|
158
177
|
}
|
|
159
178
|
|
|
160
|
-
function buildProfileFile(port: number, catalogPath
|
|
161
|
-
|
|
179
|
+
export function buildProfileFile(port: number, catalogPath?: string | null): string {
|
|
180
|
+
const lines = [
|
|
162
181
|
"# OpenCodex proxy profile — use with: codex --profile opencodex",
|
|
163
182
|
`# Routes all model requests through the opencodex proxy at localhost:${port}`,
|
|
164
183
|
'model_provider = "opencodex"',
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
];
|
|
185
|
+
if (catalogPath) lines.push(`model_catalog_json = ${tomlString(catalogPath)}`);
|
|
186
|
+
lines.push("", "[features]", "fast_mode = true", "");
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function chooseCatalogPathForInjection(content: string, requested?: string | null): string | null {
|
|
191
|
+
if (requested !== undefined) return requested;
|
|
192
|
+
|
|
193
|
+
const existing = readRootModelCatalogPath(content);
|
|
194
|
+
if (existing) {
|
|
195
|
+
const resolved = resolveCodexConfigPath(existing);
|
|
196
|
+
if (!isOpencodexCatalogPath(resolved) || existsSync(resolved)) return existing;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return existsSync(DEFAULT_CATALOG_PATH) ? DEFAULT_CATALOG_PATH : null;
|
|
171
200
|
}
|
|
172
201
|
|
|
173
|
-
export async function injectCodexConfig(port: number, config?: OcxConfig): Promise<{ success: boolean; message: string }> {
|
|
202
|
+
export async function injectCodexConfig(port: number, config?: OcxConfig, options: InjectCodexOptions = {}): Promise<{ success: boolean; message: string }> {
|
|
174
203
|
if (!existsSync(CODEX_CONFIG_PATH)) {
|
|
175
204
|
return { success: false, message: `Codex config not found at ${CODEX_CONFIG_PATH}. Is Codex installed?` };
|
|
176
205
|
}
|
|
@@ -189,8 +218,8 @@ export async function injectCodexConfig(port: number, config?: OcxConfig): Promi
|
|
|
189
218
|
content = normalizeServiceTier(content);
|
|
190
219
|
content = ensureFastModeFeature(content);
|
|
191
220
|
|
|
192
|
-
const catalogPath =
|
|
193
|
-
content = setRootModelCatalogPath(content, catalogPath);
|
|
221
|
+
const catalogPath = chooseCatalogPathForInjection(content, options.catalogPath);
|
|
222
|
+
content = catalogPath ? setRootModelCatalogPath(content, catalogPath) : stripOpencodexCatalogPath(content);
|
|
194
223
|
|
|
195
224
|
// 1) Root key BEFORE the first table header (must be a global, not nested under a table).
|
|
196
225
|
content = setRootModelProvider(content);
|
|
@@ -200,9 +229,13 @@ export async function injectCodexConfig(port: number, config?: OcxConfig): Promi
|
|
|
200
229
|
writeFileSync(CODEX_CONFIG_PATH, content, "utf-8");
|
|
201
230
|
writeFileSync(CODEX_PROFILE_PATH, buildProfileFile(port, catalogPath), "utf-8");
|
|
202
231
|
|
|
232
|
+
const catalogMessage = catalogPath
|
|
233
|
+
? ` Codex model catalog: ${catalogPath}\n`
|
|
234
|
+
: ` Codex model catalog not injected because no opencodex catalog file exists yet.\n`;
|
|
203
235
|
return {
|
|
204
236
|
success: true,
|
|
205
237
|
message: `Injected opencodex as default provider into Codex config.\n` +
|
|
238
|
+
catalogMessage +
|
|
206
239
|
` All models now route through opencodex proxy (like OpenRouter).\n` +
|
|
207
240
|
` OpenAI models (gpt-5.5, etc.) are passed through to OpenAI.\n` +
|
|
208
241
|
` Custom models route to their configured providers.\n` +
|
package/src/config.ts
CHANGED
|
@@ -3,12 +3,13 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { OcxConfig } from "./types";
|
|
5
5
|
|
|
6
|
+
let _atomicSeq = 0;
|
|
6
7
|
/**
|
|
7
8
|
* Write a file atomically (temp + rename) so concurrent writers — e.g. `ocx stop` and the
|
|
8
9
|
* proxy's own shutdown handler both restoring Codex — can never leave a half-written file.
|
|
9
10
|
*/
|
|
10
11
|
export function atomicWriteFile(path: string, content: string): void {
|
|
11
|
-
const tmp = `${path}.ocx.tmp`;
|
|
12
|
+
const tmp = `${path}.ocx.${process.pid}.${++_atomicSeq}.tmp`;
|
|
12
13
|
writeFileSync(tmp, content, "utf-8");
|
|
13
14
|
renameSync(tmp, path);
|
|
14
15
|
}
|
package/src/oauth/index.ts
CHANGED
|
@@ -121,24 +121,40 @@ export function buildModelsRequest(prov: OcxProviderConfig, apiKey: string | und
|
|
|
121
121
|
* Only touches providers that are registry-managed AND still `authMode: "oauth"`, and only the
|
|
122
122
|
* preset fields (never apiKey/baseUrl/user toggles). Persists + returns true when anything changed.
|
|
123
123
|
*/
|
|
124
|
+
function cloneProviderField(value: unknown): unknown {
|
|
125
|
+
if (Array.isArray(value)) return [...value];
|
|
126
|
+
if (value && typeof value === "object") return JSON.parse(JSON.stringify(value));
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
|
|
131
|
+
"models",
|
|
132
|
+
"noReasoningModels",
|
|
133
|
+
"noVisionModels",
|
|
134
|
+
"reasoningEfforts",
|
|
135
|
+
"modelReasoningEfforts",
|
|
136
|
+
"reasoningEffortMap",
|
|
137
|
+
"modelReasoningEffortMap",
|
|
138
|
+
"noTemperatureModels",
|
|
139
|
+
"noTopPModels",
|
|
140
|
+
"noPenaltyModels",
|
|
141
|
+
"autoToolChoiceOnlyModels",
|
|
142
|
+
"preserveReasoningContentModels",
|
|
143
|
+
];
|
|
144
|
+
|
|
124
145
|
export function reconcileOAuthProviders(config: OcxConfig): boolean {
|
|
125
146
|
let changed = false;
|
|
126
147
|
for (const [name, prov] of Object.entries(config.providers)) {
|
|
127
148
|
const def = OAUTH_PROVIDERS[name];
|
|
128
149
|
if (!def || prov.authMode !== "oauth") continue;
|
|
129
150
|
const preset = def.providerConfig;
|
|
130
|
-
|
|
131
|
-
prov
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
changed = true;
|
|
138
|
-
}
|
|
139
|
-
if (JSON.stringify(prov.noVisionModels) !== JSON.stringify(preset.noVisionModels)) {
|
|
140
|
-
if (preset.noVisionModels) prov.noVisionModels = [...preset.noVisionModels];
|
|
141
|
-
else delete prov.noVisionModels;
|
|
151
|
+
for (const field of OAUTH_RECONCILE_FIELDS) {
|
|
152
|
+
if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue;
|
|
153
|
+
if (preset[field] !== undefined) {
|
|
154
|
+
prov[field] = cloneProviderField(preset[field]) as never;
|
|
155
|
+
} else {
|
|
156
|
+
delete prov[field];
|
|
157
|
+
}
|
|
142
158
|
changed = true;
|
|
143
159
|
}
|
|
144
160
|
// Heal a defaultModel that no longer exists in the refreshed list (e.g. a deprecated snapshot).
|
|
@@ -20,8 +20,17 @@ export interface KeyLoginProvider {
|
|
|
20
20
|
* accept a reasoning param. Copied into the created provider config by `enrichProviderFromCatalog`,
|
|
21
21
|
* so the classification actually gates the sidecars (matching is tolerant of an Ollama ":size" tag).
|
|
22
22
|
*/
|
|
23
|
+
reasoningEfforts?: string[];
|
|
24
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
25
|
+
reasoningEffortMap?: Record<string, string>;
|
|
26
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
23
27
|
noVisionModels?: string[];
|
|
24
28
|
noReasoningModels?: string[];
|
|
29
|
+
noTemperatureModels?: string[];
|
|
30
|
+
noTopPModels?: string[];
|
|
31
|
+
noPenaltyModels?: string[];
|
|
32
|
+
autoToolChoiceOnlyModels?: string[];
|
|
33
|
+
preserveReasoningContentModels?: string[];
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export const KEY_LOGIN_PROVIDERS: Record<string, KeyLoginProvider> = deriveKeyLoginMap();
|
|
@@ -37,8 +46,26 @@ export function enrichProviderFromCatalog(name: string, prov: OcxProviderConfig)
|
|
|
37
46
|
if (!e) return;
|
|
38
47
|
if (!prov.models && e.models) prov.models = [...e.models];
|
|
39
48
|
if (!prov.defaultModel && e.defaultModel) prov.defaultModel = e.defaultModel;
|
|
49
|
+
if (!prov.reasoningEfforts && e.reasoningEfforts) prov.reasoningEfforts = [...e.reasoningEfforts];
|
|
50
|
+
if (!prov.modelReasoningEfforts && e.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(e.modelReasoningEfforts);
|
|
51
|
+
if (!prov.reasoningEffortMap && e.reasoningEffortMap) prov.reasoningEffortMap = { ...e.reasoningEffortMap };
|
|
52
|
+
if (!prov.modelReasoningEffortMap && e.modelReasoningEffortMap) prov.modelReasoningEffortMap = cloneNestedRecord(e.modelReasoningEffortMap);
|
|
40
53
|
if (!prov.noVisionModels && e.noVisionModels) prov.noVisionModels = [...e.noVisionModels];
|
|
41
54
|
if (!prov.noReasoningModels && e.noReasoningModels) prov.noReasoningModels = [...e.noReasoningModels];
|
|
55
|
+
if (!prov.noTemperatureModels && e.noTemperatureModels) prov.noTemperatureModels = [...e.noTemperatureModels];
|
|
56
|
+
if (!prov.noTopPModels && e.noTopPModels) prov.noTopPModels = [...e.noTopPModels];
|
|
57
|
+
if (!prov.noPenaltyModels && e.noPenaltyModels) prov.noPenaltyModels = [...e.noPenaltyModels];
|
|
58
|
+
if (!prov.autoToolChoiceOnlyModels && e.autoToolChoiceOnlyModels) prov.autoToolChoiceOnlyModels = [...e.autoToolChoiceOnlyModels];
|
|
59
|
+
if (!prov.preserveReasoningContentModels && e.preserveReasoningContentModels) prov.preserveReasoningContentModels = [...e.preserveReasoningContentModels];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function cloneRecordOfArrays(input: Record<string, string[]>): Record<string, string[]> {
|
|
64
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, [...value]]));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cloneNestedRecord(input: Record<string, Record<string, string>>): Record<string, Record<string, string>> {
|
|
68
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, { ...value }]));
|
|
42
69
|
}
|
|
43
70
|
|
|
44
71
|
export function isKeyLoginProvider(name: string): boolean {
|
package/src/providers/derive.ts
CHANGED
|
@@ -8,8 +8,17 @@ export interface DerivedKeyLoginProvider {
|
|
|
8
8
|
dashboardUrl: string;
|
|
9
9
|
models?: string[];
|
|
10
10
|
defaultModel?: string;
|
|
11
|
+
reasoningEfforts?: string[];
|
|
12
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
13
|
+
reasoningEffortMap?: Record<string, string>;
|
|
14
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
11
15
|
noVisionModels?: string[];
|
|
12
16
|
noReasoningModels?: string[];
|
|
17
|
+
noTemperatureModels?: string[];
|
|
18
|
+
noTopPModels?: string[];
|
|
19
|
+
noPenaltyModels?: string[];
|
|
20
|
+
autoToolChoiceOnlyModels?: string[];
|
|
21
|
+
preserveReasoningContentModels?: string[];
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
export interface DerivedInitProvider {
|
|
@@ -38,6 +47,14 @@ export function listRegistryEntries(): readonly ProviderRegistryEntry[] {
|
|
|
38
47
|
return PROVIDER_REGISTRY;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
function cloneRecordOfArrays(input: Record<string, string[]>): Record<string, string[]> {
|
|
51
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, [...value]]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cloneNestedRecord(input: Record<string, Record<string, string>>): Record<string, Record<string, string>> {
|
|
55
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, { ...value }]));
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderConfig {
|
|
42
59
|
return {
|
|
43
60
|
adapter: entry.adapter,
|
|
@@ -45,8 +62,17 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
45
62
|
authMode: entry.authKind === "local" ? undefined : entry.authKind,
|
|
46
63
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
47
64
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
65
|
+
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
66
|
+
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
67
|
+
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
68
|
+
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
48
69
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
49
70
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
71
|
+
...(entry.noTemperatureModels ? { noTemperatureModels: [...entry.noTemperatureModels] } : {}),
|
|
72
|
+
...(entry.noTopPModels ? { noTopPModels: [...entry.noTopPModels] } : {}),
|
|
73
|
+
...(entry.noPenaltyModels ? { noPenaltyModels: [...entry.noPenaltyModels] } : {}),
|
|
74
|
+
...(entry.autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels: [...entry.autoToolChoiceOnlyModels] } : {}),
|
|
75
|
+
...(entry.preserveReasoningContentModels ? { preserveReasoningContentModels: [...entry.preserveReasoningContentModels] } : {}),
|
|
50
76
|
};
|
|
51
77
|
}
|
|
52
78
|
|
|
@@ -62,8 +88,17 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
|
62
88
|
dashboardUrl: entry.dashboardUrl,
|
|
63
89
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
64
90
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
91
|
+
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
92
|
+
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
93
|
+
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
94
|
+
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
65
95
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
66
96
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
97
|
+
...(entry.noTemperatureModels ? { noTemperatureModels: [...entry.noTemperatureModels] } : {}),
|
|
98
|
+
...(entry.noTopPModels ? { noTopPModels: [...entry.noTopPModels] } : {}),
|
|
99
|
+
...(entry.noPenaltyModels ? { noPenaltyModels: [...entry.noPenaltyModels] } : {}),
|
|
100
|
+
...(entry.autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels: [...entry.autoToolChoiceOnlyModels] } : {}),
|
|
101
|
+
...(entry.preserveReasoningContentModels ? { preserveReasoningContentModels: [...entry.preserveReasoningContentModels] } : {}),
|
|
67
102
|
};
|
|
68
103
|
}
|
|
69
104
|
return out;
|
|
@@ -14,8 +14,17 @@ export interface ProviderRegistryEntry {
|
|
|
14
14
|
dashboardUrl?: string;
|
|
15
15
|
defaultModel?: string;
|
|
16
16
|
models?: string[];
|
|
17
|
+
reasoningEfforts?: string[];
|
|
18
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
19
|
+
reasoningEffortMap?: Record<string, string>;
|
|
20
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
17
21
|
noVisionModels?: string[];
|
|
18
22
|
noReasoningModels?: string[];
|
|
23
|
+
noTemperatureModels?: string[];
|
|
24
|
+
noTopPModels?: string[];
|
|
25
|
+
noPenaltyModels?: string[];
|
|
26
|
+
autoToolChoiceOnlyModels?: string[];
|
|
27
|
+
preserveReasoningContentModels?: string[];
|
|
19
28
|
oauthId?: string;
|
|
20
29
|
jawcodeBundle?: string;
|
|
21
30
|
extraMetadataAliases?: string[];
|
|
@@ -24,9 +33,32 @@ export interface ProviderRegistryEntry {
|
|
|
24
33
|
|
|
25
34
|
export type ProviderConfigSeed = Pick<
|
|
26
35
|
OcxProviderConfig,
|
|
27
|
-
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models"
|
|
36
|
+
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models"
|
|
37
|
+
| "reasoningEfforts" | "modelReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
38
|
+
| "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
|
|
39
|
+
| "autoToolChoiceOnlyModels" | "preserveReasoningContentModels"
|
|
28
40
|
>;
|
|
29
41
|
|
|
42
|
+
|
|
43
|
+
const ZAI_GLM_52_MODELS = ["glm-5.2", "glm-5.2[1m]"];
|
|
44
|
+
const ZAI_GLM_52_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
45
|
+
const ZAI_GLM_52_REASONING_MAP: Record<string, string> = {
|
|
46
|
+
none: "none",
|
|
47
|
+
minimal: "none",
|
|
48
|
+
low: "high",
|
|
49
|
+
medium: "high",
|
|
50
|
+
high: "high",
|
|
51
|
+
xhigh: "max",
|
|
52
|
+
max: "max",
|
|
53
|
+
};
|
|
54
|
+
const KIMI_THINKING_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-k2-0905-preview"];
|
|
55
|
+
const KIMI_LOCKED_PARAMETER_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
|
|
56
|
+
const NEURALWATT_REASONING_HISTORY_MODELS = [
|
|
57
|
+
"glm-5.2",
|
|
58
|
+
"moonshotai/Kimi-K2.5", "kimi-k2.6", "kimi-k2.7-code",
|
|
59
|
+
"qwen3.5-397b", "qwen3.6-35b",
|
|
60
|
+
];
|
|
61
|
+
|
|
30
62
|
export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
31
63
|
{
|
|
32
64
|
id: "openai",
|
|
@@ -75,11 +107,72 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
75
107
|
oauthId: "kimi",
|
|
76
108
|
jawcodeBundle: "moonshot",
|
|
77
109
|
note: "Log in with your Kimi account",
|
|
78
|
-
models: ["kimi-k2.6", "kimi-k2.5"],
|
|
79
|
-
defaultModel: "kimi-k2.
|
|
110
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
|
|
111
|
+
defaultModel: "kimi-k2.7-code",
|
|
112
|
+
// Kimi thinking is controlled by Kimi's `thinking` extension, not OpenAI `reasoning_effort`.
|
|
113
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
114
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
115
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
116
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
117
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
118
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
119
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
80
120
|
},
|
|
81
121
|
{ id: "openai-apikey", label: "OpenAI (API key)", adapter: "openai-responses", baseUrl: "https://api.openai.com/v1", authKind: "key", featured: true, dashboardUrl: "https://platform.openai.com/api-keys", defaultModel: "gpt-5.5" },
|
|
82
|
-
{
|
|
122
|
+
{
|
|
123
|
+
id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1",
|
|
124
|
+
authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.7-code",
|
|
125
|
+
jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…",
|
|
126
|
+
modelReasoningEfforts: {
|
|
127
|
+
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
128
|
+
"kimi-k2.7-code": [],
|
|
129
|
+
"kimi-k2.7-code-highspeed": [],
|
|
130
|
+
},
|
|
131
|
+
modelReasoningEffortMap: { "glm-5.2": ZAI_GLM_52_REASONING_MAP },
|
|
132
|
+
noReasoningModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
133
|
+
noTemperatureModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
134
|
+
noTopPModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
135
|
+
noPenaltyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
136
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
137
|
+
preserveReasoningContentModels: ["glm-5.2", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "neuralwatt",
|
|
141
|
+
label: "Neuralwatt Cloud",
|
|
142
|
+
adapter: "openai-chat",
|
|
143
|
+
baseUrl: "https://api.neuralwatt.com/v1",
|
|
144
|
+
authKind: "key",
|
|
145
|
+
dashboardUrl: "https://portal.neuralwatt.com",
|
|
146
|
+
defaultModel: "glm-5.2",
|
|
147
|
+
models: [
|
|
148
|
+
"glm-5.2", "glm-5.2-fast",
|
|
149
|
+
"moonshotai/Kimi-K2.5", "kimi-k2.5-fast", "kimi-k2.6", "kimi-k2.6-fast",
|
|
150
|
+
"kimi-k2.7-code",
|
|
151
|
+
"qwen3.5-397b", "qwen3.5-397b-fast", "qwen3.6-35b", "qwen3.6-35b-fast",
|
|
152
|
+
],
|
|
153
|
+
// Neuralwatt's /v1/models metadata is authoritative; these static hints are the offline fallback.
|
|
154
|
+
modelReasoningEfforts: {
|
|
155
|
+
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
156
|
+
"glm-5.2-fast": [],
|
|
157
|
+
"moonshotai/Kimi-K2.5": [],
|
|
158
|
+
"kimi-k2.5-fast": [],
|
|
159
|
+
"kimi-k2.6": [],
|
|
160
|
+
"kimi-k2.6-fast": [],
|
|
161
|
+
"kimi-k2.7-code": [],
|
|
162
|
+
"qwen3.5-397b": ["low", "medium", "high"],
|
|
163
|
+
"qwen3.5-397b-fast": [],
|
|
164
|
+
"qwen3.6-35b": ["low", "medium", "high"],
|
|
165
|
+
"qwen3.6-35b-fast": [],
|
|
166
|
+
},
|
|
167
|
+
modelReasoningEffortMap: { "glm-5.2": ZAI_GLM_52_REASONING_MAP },
|
|
168
|
+
noReasoningModels: ["glm-5.2-fast", "kimi-k2.5-fast", "kimi-k2.6-fast", "qwen3.5-397b-fast", "qwen3.6-35b-fast"],
|
|
169
|
+
noVisionModels: ["glm-5.2", "glm-5.2-fast", "qwen3.5-397b", "qwen3.5-397b-fast"],
|
|
170
|
+
noTemperatureModels: ["kimi-k2.7-code"],
|
|
171
|
+
noTopPModels: ["kimi-k2.7-code"],
|
|
172
|
+
noPenaltyModels: ["kimi-k2.7-code"],
|
|
173
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code"],
|
|
174
|
+
preserveReasoningContentModels: NEURALWATT_REASONING_HISTORY_MODELS,
|
|
175
|
+
},
|
|
83
176
|
{ id: "openrouter", label: "OpenRouter", adapter: "openai-chat", baseUrl: "https://openrouter.ai/api/v1", authKind: "key", featured: true, dashboardUrl: "https://openrouter.ai/keys", jawcodeBundle: "openrouter" },
|
|
84
177
|
{ id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
|
|
85
178
|
{ id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true, dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro", jawcodeBundle: "google", extraMetadataAliases: ["gemini"] },
|
|
@@ -92,11 +185,30 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
92
185
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
93
186
|
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
94
187
|
{ id: "firepass", label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
95
|
-
{
|
|
188
|
+
{
|
|
189
|
+
id: "moonshot", label: "Moonshot (Kimi API)", baseUrl: "https://api.moonshot.ai/v1", adapter: "openai-chat", authKind: "key",
|
|
190
|
+
dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2.7-code", jawcodeBundle: "moonshot",
|
|
191
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-k2-0905-preview"],
|
|
192
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
193
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
194
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
195
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
196
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
197
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
198
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
199
|
+
},
|
|
96
200
|
{ id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
|
|
97
201
|
{ id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com" },
|
|
98
202
|
{ id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
|
|
99
|
-
{
|
|
203
|
+
{
|
|
204
|
+
id: "zai", label: "Z.AI (GLM Coding)", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
|
|
205
|
+
dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-5.2",
|
|
206
|
+
models: ["glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
|
|
207
|
+
noVisionModels: ZAI_GLM_52_MODELS,
|
|
208
|
+
modelReasoningEfforts: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_EFFORTS])),
|
|
209
|
+
modelReasoningEffortMap: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_MAP])),
|
|
210
|
+
preserveReasoningContentModels: ZAI_GLM_52_MODELS,
|
|
211
|
+
},
|
|
100
212
|
{ id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
|
|
101
213
|
{ id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
|
|
102
214
|
{ id: "qwen-portal", label: "Qwen Portal", baseUrl: "https://portal.qwen.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://portal.qwen.ai" },
|
|
@@ -125,7 +237,18 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
125
237
|
{ id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
|
|
126
238
|
{ id: "minimax", label: "MiniMax", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
|
|
127
239
|
{ id: "minimax-cn", label: "MiniMax (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
|
|
128
|
-
{
|
|
240
|
+
{
|
|
241
|
+
id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
|
|
242
|
+
dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
|
|
243
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
|
|
244
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
245
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
246
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
247
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
248
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
249
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
250
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
251
|
+
},
|
|
129
252
|
{ id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth" },
|
|
130
253
|
{ id: "vercel-ai-gateway", label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://vercel.com/dashboard" },
|
|
131
254
|
{ id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "./types";
|
|
2
|
+
import { modelInList } from "./types";
|
|
3
|
+
|
|
4
|
+
export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] = [
|
|
5
|
+
{ effort: "low", description: "Fast responses with lighter reasoning" },
|
|
6
|
+
{ effort: "medium", description: "Balances speed and reasoning depth" },
|
|
7
|
+
{ effort: "high", description: "Greater reasoning depth for complex problems" },
|
|
8
|
+
{ effort: "xhigh", description: "Extended reasoning for the hardest problems" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const CODEX_REASONING_ORDER = CODEX_REASONING_LEVELS.map(l => l.effort);
|
|
12
|
+
const CODEX_REASONING_SET = new Set(CODEX_REASONING_ORDER);
|
|
13
|
+
|
|
14
|
+
export function modelRecordValue<T>(record: Record<string, T> | undefined, modelId: string): T | undefined {
|
|
15
|
+
if (!record) return undefined;
|
|
16
|
+
if (Object.prototype.hasOwnProperty.call(record, modelId)) return record[modelId];
|
|
17
|
+
const colon = modelId.indexOf(":");
|
|
18
|
+
if (colon > 0) {
|
|
19
|
+
const family = modelId.slice(0, colon);
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(record, family)) return record[family];
|
|
21
|
+
}
|
|
22
|
+
const folded = modelId.toLowerCase();
|
|
23
|
+
for (const [key, value] of Object.entries(record)) {
|
|
24
|
+
if (key.toLowerCase() === folded) return value;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function sanitizeCodexReasoningEfforts(efforts: readonly string[] | undefined): string[] | undefined {
|
|
30
|
+
if (efforts === undefined) return undefined;
|
|
31
|
+
const seen = new Set<string>();
|
|
32
|
+
const out: string[] = [];
|
|
33
|
+
for (const effort of efforts) {
|
|
34
|
+
if (!CODEX_REASONING_SET.has(effort) || seen.has(effort)) continue;
|
|
35
|
+
seen.add(effort);
|
|
36
|
+
out.push(effort);
|
|
37
|
+
}
|
|
38
|
+
return out.sort((a, b) => CODEX_REASONING_ORDER.indexOf(a) - CODEX_REASONING_ORDER.indexOf(b));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Provider/model configured reasoning levels for the Codex catalog. `undefined` means “no override”,
|
|
43
|
+
* while an empty array means “intentionally expose no effort control for this model”.
|
|
44
|
+
*/
|
|
45
|
+
export function configuredReasoningEfforts(provider: OcxProviderConfig, modelId: string): string[] | undefined {
|
|
46
|
+
if (modelInList(provider.noReasoningModels, modelId)) return [];
|
|
47
|
+
const modelEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId);
|
|
48
|
+
if (modelEfforts !== undefined) return sanitizeCodexReasoningEfforts(modelEfforts) ?? [];
|
|
49
|
+
if (provider.reasoningEfforts !== undefined) return sanitizeCodexReasoningEfforts(provider.reasoningEfforts) ?? [];
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requestToCodexEffort(requested: string): string | undefined {
|
|
54
|
+
if (requested === "none") return undefined;
|
|
55
|
+
if (requested === "minimal") return "low";
|
|
56
|
+
if (requested === "max") return "xhigh";
|
|
57
|
+
return CODEX_REASONING_SET.has(requested) ? requested : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function clampToSupportedCodexEffort(requested: string, supported: readonly string[]): string | undefined {
|
|
61
|
+
if (supported.length === 0) return undefined;
|
|
62
|
+
const codex = requestToCodexEffort(requested);
|
|
63
|
+
if (!codex) return undefined;
|
|
64
|
+
if (supported.includes(codex)) return codex;
|
|
65
|
+
|
|
66
|
+
const requestedRank = CODEX_REASONING_ORDER.indexOf(codex);
|
|
67
|
+
let best = supported[0];
|
|
68
|
+
let bestRank = CODEX_REASONING_ORDER.indexOf(best);
|
|
69
|
+
for (const effort of supported) {
|
|
70
|
+
const rank = CODEX_REASONING_ORDER.indexOf(effort);
|
|
71
|
+
if (rank <= requestedRank && rank >= bestRank) {
|
|
72
|
+
best = effort;
|
|
73
|
+
bestRank = rank;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If every supported tier is above the requested tier, choose the lowest supported tier.
|
|
77
|
+
return best;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function reasoningEffortMapFor(provider: OcxProviderConfig, modelId: string): Record<string, string> | undefined {
|
|
81
|
+
return modelRecordValue(provider.modelReasoningEffortMap, modelId) ?? provider.reasoningEffortMap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Translate Codex's reasoning label into the provider's real wire value. The Codex catalog must only
|
|
86
|
+
* advertise labels Codex itself accepts (`low`/`medium`/`high`/`xhigh`), but some upstreams use
|
|
87
|
+
* different values (`max`) or a smaller subset (`low`/`medium`/`high`).
|
|
88
|
+
*/
|
|
89
|
+
export function mapReasoningEffort(provider: OcxProviderConfig, modelId: string, requested: string | undefined): string | undefined {
|
|
90
|
+
if (!requested) return undefined;
|
|
91
|
+
if (modelInList(provider.noReasoningModels, modelId)) return undefined;
|
|
92
|
+
|
|
93
|
+
const wireMap = reasoningEffortMapFor(provider, modelId);
|
|
94
|
+
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, requested)) return wireMap[requested];
|
|
95
|
+
|
|
96
|
+
const supported = configuredReasoningEfforts(provider, modelId);
|
|
97
|
+
const codexEffort = supported !== undefined ? clampToSupportedCodexEffort(requested, supported) : requestToCodexEffort(requested);
|
|
98
|
+
if (!codexEffort) return undefined;
|
|
99
|
+
|
|
100
|
+
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, codexEffort)) return wireMap[codexEffort];
|
|
101
|
+
return codexEffort;
|
|
102
|
+
}
|
package/src/responses/parser.ts
CHANGED
|
@@ -188,7 +188,7 @@ function findToolNameById(messages: OcxMessage[], callId: string): string {
|
|
|
188
188
|
return "";
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
const REASONING_EFFORTS = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
191
|
+
const REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
192
192
|
|
|
193
193
|
export function parseRequest(body: unknown): OcxParsedRequest {
|
|
194
194
|
const parsed = responsesRequestSchema.safeParse(body);
|
package/src/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
3
|
import { createAnthropicAdapter } from "./adapters/anthropic";
|
|
4
4
|
import { createAzureAdapter } from "./adapters/azure";
|
|
@@ -32,7 +32,15 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "./oauth/key-pr
|
|
|
32
32
|
import { deriveProviderPresets } from "./providers/derive";
|
|
33
33
|
import type { OcxConfig, OcxProviderConfig } from "./types";
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// Single source of truth = package.json (../ from src/), so /healthz + the GUI badge match the
|
|
36
|
+
// installed npm version instead of a stale hardcode.
|
|
37
|
+
const VERSION = (() => {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version as string;
|
|
40
|
+
} catch {
|
|
41
|
+
return "0.0.0";
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
36
44
|
|
|
37
45
|
const MIME_TYPES: Record<string, string> = {
|
|
38
46
|
".html": "text/html", ".js": "application/javascript", ".css": "text/css",
|
|
@@ -522,6 +530,15 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
522
530
|
return jsonResponse({ success: true });
|
|
523
531
|
}
|
|
524
532
|
|
|
533
|
+
if (url.pathname === "/api/stop" && req.method === "POST") {
|
|
534
|
+
const { restoreNativeCodex } = await import("./codex-inject");
|
|
535
|
+
const { stopServiceIfInstalled } = await import("./service");
|
|
536
|
+
stopServiceIfInstalled();
|
|
537
|
+
restoreNativeCodex();
|
|
538
|
+
setTimeout(() => process.exit(0), 200);
|
|
539
|
+
return jsonResponse({ success: true, message: "Proxy stopping, native Codex restored." });
|
|
540
|
+
}
|
|
541
|
+
|
|
525
542
|
return null;
|
|
526
543
|
}
|
|
527
544
|
|