@bitkyc08/opencodex 1.9.4 → 1.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.ts +7 -2
- package/src/codex-inject.ts +46 -13
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { restoreNativeCodex } from "./codex-inject";
|
|
4
5
|
import { loadConfig, readPid, removePid, writePid } from "./config";
|
|
5
6
|
import { serviceCommand } from "./service";
|
|
@@ -35,18 +36,22 @@ Examples:
|
|
|
35
36
|
async function syncModelsToCodex(port?: number) {
|
|
36
37
|
const config = loadConfig();
|
|
37
38
|
const p = port ?? config.port ?? 10100;
|
|
38
|
-
|
|
39
|
-
const result = await injectCodexConfig(p, config);
|
|
39
|
+
let catalogPath: string | null | undefined;
|
|
40
40
|
try {
|
|
41
41
|
const { invalidateCodexModelsCache, syncCatalogModels } = await import("./codex-catalog");
|
|
42
42
|
const cat = await syncCatalogModels(config);
|
|
43
|
+
catalogPath = existsSync(cat.path) ? cat.path : null;
|
|
43
44
|
if (cat.added > 0) {
|
|
44
45
|
invalidateCodexModelsCache();
|
|
45
46
|
console.log(` + ${cat.added} models appended to Codex catalog (${cat.path})`);
|
|
47
|
+
} else if (catalogPath === null) {
|
|
48
|
+
console.error("catalog sync skipped: no Codex catalog source found; keeping Codex's native catalog.");
|
|
46
49
|
}
|
|
47
50
|
} catch (e) {
|
|
48
51
|
console.error("catalog sync skipped:", e instanceof Error ? e.message : String(e));
|
|
49
52
|
}
|
|
53
|
+
const { injectCodexConfig } = await import("./codex-inject");
|
|
54
|
+
const result = await injectCodexConfig(p, config, { catalogPath });
|
|
50
55
|
console.log(result.message);
|
|
51
56
|
return result;
|
|
52
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` +
|