@koreanhole/opencodex 2.99.0 → 3.1.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/AGENTS_INSTALL.md +15 -7
- package/README.md +91 -208
- package/bin/ocx.mjs +1 -1
- package/bin/package-main.mjs +11 -1
- package/package.json +8 -6
- package/src/adapter-runtime/config.ts +381 -0
- package/src/adapter-runtime/gateway.ts +121 -0
- package/src/adapter-runtime/scoped-server.ts +20 -0
- package/src/adapters/cli-agent/cline/adapter.ts +107 -90
- package/src/adapters/cli-agent/cline/json.ts +652 -0
- package/src/adapters/cli-agent/cline/settings.ts +64 -5
- package/src/adapters/cli-agent/cline/stream.ts +201 -1
- package/src/adapters/cli-agent/gemini/acp.ts +6 -1
- package/src/adapters/cli-agent/gemini/adapter.ts +1 -1
- package/src/adapters/cli-agent/gemini/live-models.ts +1 -1
- package/src/adapters/cli-agent/types.ts +1 -0
- package/src/cli/entry.ts +230 -0
- package/src/cli/index.ts +7 -5
- package/src/server/index.ts +28 -17
- package/src/server/responses.ts +42 -1
- package/src/upstream/cli.ts +12 -0
- package/src/upstream/package.ts +75 -0
package/src/server/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
assertSafeListener,
|
|
6
6
|
defaultConfig,
|
|
7
7
|
normalizeConfig,
|
|
8
|
+
type ProviderId,
|
|
8
9
|
type RuntimeConfig,
|
|
9
10
|
} from "../config";
|
|
10
11
|
import type { IncomingMeta } from "../types";
|
|
@@ -29,6 +30,7 @@ export interface ServerOptions {
|
|
|
29
30
|
clineDeps?: ClineTurnDeps;
|
|
30
31
|
geminiDeps?: GeminiTurnDeps;
|
|
31
32
|
geminiPackageRoot?: string;
|
|
33
|
+
providerScope?: ProviderId;
|
|
32
34
|
onWebSocketRequest?: (observation: WebSocketRequestObservation) => void;
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -92,23 +94,32 @@ async function discoverModels(
|
|
|
92
94
|
clineDeps: ClineTurnDeps | undefined,
|
|
93
95
|
geminiDeps: GeminiTurnDeps | undefined,
|
|
94
96
|
geminiPackageRoot: string | undefined,
|
|
97
|
+
providerScope?: ProviderId,
|
|
95
98
|
): Promise<ProviderModels> {
|
|
96
99
|
const [cline, gemini] = await Promise.all([
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
!providerScope || providerScope === "cline-cli"
|
|
101
|
+
? discoverClineLiveModels({ env, ...(clineDeps ? { deps: clineDeps } : {}) })
|
|
102
|
+
: undefined,
|
|
103
|
+
!providerScope || providerScope === "gemini-cli"
|
|
104
|
+
? discoverGeminiLiveModels({ env, ...(geminiDeps ? { deps: geminiDeps } : {}), ...(geminiPackageRoot ? { packageRoot: geminiPackageRoot } : {}) })
|
|
105
|
+
: undefined,
|
|
99
106
|
]);
|
|
100
|
-
if (!cline.ok && cline.reason !== "not_found" && env.OPENCODEX_CLINE_ACP?.trim() !== "0") {
|
|
107
|
+
if (cline && !cline.ok && cline.reason !== "not_found" && env.OPENCODEX_CLINE_ACP?.trim() !== "0") {
|
|
101
108
|
console.warn(`[opencodex] Cline model discovery degraded [${cline.reason}]: ${cline.detail}`);
|
|
102
109
|
}
|
|
103
|
-
if (!gemini.ok && gemini.reason !== "not_found" && env.OPENCODEX_GEMINI_ACP?.trim() !== "0") {
|
|
110
|
+
if (gemini && !gemini.ok && gemini.reason !== "not_found" && env.OPENCODEX_GEMINI_ACP?.trim() !== "0") {
|
|
104
111
|
console.warn(`[opencodex] Gemini model discovery degraded [${gemini.reason}]: ${gemini.detail}`);
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
113
|
+
const models: ProviderModels = {};
|
|
114
|
+
if (cline) {
|
|
115
|
+
models["cline-cli"] = cline.ok ? ["default", ...cline.models.filter(id => id !== "default")] : ["default"];
|
|
116
|
+
}
|
|
117
|
+
if (gemini) {
|
|
118
|
+
models["gemini-cli"] = gemini.ok
|
|
109
119
|
? ["default", ...gemini.modelInfo.filter(model => model.id !== "default")]
|
|
110
|
-
: ["default"]
|
|
111
|
-
}
|
|
120
|
+
: ["default"];
|
|
121
|
+
}
|
|
122
|
+
return models;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
export function startServer(config: RuntimeConfig | number, options: ServerOptions = {}): RunningServer {
|
|
@@ -138,8 +149,8 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
138
149
|
}
|
|
139
150
|
try {
|
|
140
151
|
if (req.method === "GET" && url.pathname === "/v1/models") {
|
|
141
|
-
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot);
|
|
142
|
-
return Response.json({ object: "list", data: modelList(known) });
|
|
152
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot, options.providerScope);
|
|
153
|
+
return Response.json({ object: "list", data: modelList(known, options.providerScope) });
|
|
143
154
|
}
|
|
144
155
|
if (req.method === "GET" && url.pathname === "/v1/model-catalog") {
|
|
145
156
|
const discovered = await discoverCodexCatalogModels({
|
|
@@ -152,16 +163,16 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
152
163
|
}
|
|
153
164
|
if (req.method === "POST" && url.pathname === "/v1/responses") {
|
|
154
165
|
const body = await boundedJson(req);
|
|
155
|
-
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot);
|
|
166
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot, options.providerScope);
|
|
156
167
|
const signal = AbortSignal.any([req.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
|
|
157
168
|
const incoming: IncomingMeta = { headers: req.headers, abortSignal: signal };
|
|
158
|
-
return await handleResponses(body, incoming, known, turnDeps, config.defaultProvider);
|
|
169
|
+
return await handleResponses(body, incoming, known, turnDeps, options.providerScope ?? config.defaultProvider);
|
|
159
170
|
}
|
|
160
171
|
if (req.method === "POST" && url.pathname === "/v1/messages") {
|
|
161
172
|
const body = await boundedJson(req);
|
|
162
|
-
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot);
|
|
173
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot, options.providerScope);
|
|
163
174
|
const signal = AbortSignal.any([req.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
|
|
164
|
-
return await handleMessages(body, { headers: req.headers, abortSignal: signal }, known, turnDeps, config.defaultProvider);
|
|
175
|
+
return await handleMessages(body, { headers: req.headers, abortSignal: signal }, known, turnDeps, options.providerScope ?? config.defaultProvider);
|
|
165
176
|
}
|
|
166
177
|
if (req.method === "POST" && url.pathname === "/v1/messages/count_tokens") {
|
|
167
178
|
return handleCountTokens(await boundedJson(req));
|
|
@@ -173,9 +184,9 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
173
184
|
};
|
|
174
185
|
|
|
175
186
|
const websocketContext = {
|
|
176
|
-
knownModels: () => discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot),
|
|
187
|
+
knownModels: () => discoverModels(env, options.clineDeps, options.geminiDeps, options.geminiPackageRoot, options.providerScope),
|
|
177
188
|
deps: turnDeps,
|
|
178
|
-
defaultProvider: config.defaultProvider,
|
|
189
|
+
defaultProvider: options.providerScope ?? config.defaultProvider,
|
|
179
190
|
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
180
191
|
...(options.onWebSocketRequest ? { onRequest: options.onWebSocketRequest } : {}),
|
|
181
192
|
};
|
package/src/server/responses.ts
CHANGED
|
@@ -379,7 +379,10 @@ const PROVIDER_LABELS: Record<ProviderId, string> = {
|
|
|
379
379
|
[GEMINI_PROVIDER_ID]: "Gemini CLI",
|
|
380
380
|
};
|
|
381
381
|
|
|
382
|
-
export function modelList(
|
|
382
|
+
export function modelList(
|
|
383
|
+
known: string[] | ProviderModels,
|
|
384
|
+
scope?: ProviderId,
|
|
385
|
+
): Array<Record<string, unknown>> {
|
|
383
386
|
if (Array.isArray(known)) {
|
|
384
387
|
const ids = ["default", ...known.filter(id => id !== "default")];
|
|
385
388
|
return [...new Set(ids)].map(id => ({
|
|
@@ -390,6 +393,44 @@ export function modelList(known: string[] | ProviderModels): Array<Record<string
|
|
|
390
393
|
display_name: id === "default" ? "Cline default" : id,
|
|
391
394
|
}));
|
|
392
395
|
}
|
|
396
|
+
if (scope) {
|
|
397
|
+
const entries = ["default", ...(known[scope] ?? []).filter(entry => modelIdOf(entry) !== "default")];
|
|
398
|
+
const rows: Array<Record<string, unknown>> = [];
|
|
399
|
+
const seen = new Set<string>();
|
|
400
|
+
for (const entry of entries) {
|
|
401
|
+
const id = modelIdOf(entry);
|
|
402
|
+
if (seen.has(id)) continue;
|
|
403
|
+
seen.add(id);
|
|
404
|
+
const info = typeof entry === "string" ? { id: entry } as OcxProviderModel : entry;
|
|
405
|
+
const reasoning = info.supportedReasoningLevels ?? [];
|
|
406
|
+
rows.push({
|
|
407
|
+
id: encodeRoutedModelId(id),
|
|
408
|
+
object: "model",
|
|
409
|
+
created: 0,
|
|
410
|
+
owned_by: scope,
|
|
411
|
+
display_name: info.name ?? (id === "default" ? PROVIDER_LABELS[scope] + " default" : id),
|
|
412
|
+
...(info.description ? { description: info.description } : {}),
|
|
413
|
+
...(info.contextWindowTokens !== undefined
|
|
414
|
+
? {
|
|
415
|
+
context_window: info.contextWindowTokens,
|
|
416
|
+
max_context_window: info.maxContextWindowTokens ?? info.contextWindowTokens,
|
|
417
|
+
}
|
|
418
|
+
: {}),
|
|
419
|
+
...(info.inputModalities ? { input_modalities: [...info.inputModalities] } : {}),
|
|
420
|
+
...(info.loadSession !== undefined ? { load_session: info.loadSession } : {}),
|
|
421
|
+
...(reasoning.length > 0
|
|
422
|
+
? {
|
|
423
|
+
supported_reasoning_levels: reasoning.map(level => ({
|
|
424
|
+
effort: level.effort,
|
|
425
|
+
description: level.description,
|
|
426
|
+
})),
|
|
427
|
+
default_reasoning_level: info.defaultReasoningLevel ?? reasoning[0]!.effort,
|
|
428
|
+
}
|
|
429
|
+
: { supported_reasoning_levels: [] }),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return rows;
|
|
433
|
+
}
|
|
393
434
|
const rows: Array<Record<string, unknown>> = [{
|
|
394
435
|
id: "default",
|
|
395
436
|
object: "model",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { resolveUpstreamPackage, type UpstreamPackage } from "./package";
|
|
3
|
+
|
|
4
|
+
export type UpstreamModuleImporter = (url: string) => Promise<unknown>;
|
|
5
|
+
|
|
6
|
+
/** Load the immutable upstream CLI into this process so service ownership stays ours. */
|
|
7
|
+
export async function loadUpstreamCli(
|
|
8
|
+
pkg: UpstreamPackage = resolveUpstreamPackage(),
|
|
9
|
+
importer: UpstreamModuleImporter = url => import(url),
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await importer(pathToFileURL(pkg.cliEntry).href);
|
|
12
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const UPSTREAM_PACKAGE_NAME = "@bitkyc08/opencodex";
|
|
6
|
+
export const UPSTREAM_PACKAGE_VERSION = "2.94.0";
|
|
7
|
+
export const UPSTREAM_CLI_RELATIVE_PATH = "src/cli/index.ts";
|
|
8
|
+
|
|
9
|
+
export interface UpstreamPackage {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
root: string;
|
|
13
|
+
cliEntry: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class UpstreamPackageError extends Error {
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "UpstreamPackageError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResolveUpstreamPackageDeps {
|
|
24
|
+
resolvePackageJson?: () => string;
|
|
25
|
+
readJson?: (path: string) => unknown;
|
|
26
|
+
exists?: (path: string) => boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Resolve the exact immutable upstream package without importing its runtime. */
|
|
30
|
+
export function resolveUpstreamPackage(
|
|
31
|
+
deps: ResolveUpstreamPackageDeps = {},
|
|
32
|
+
): UpstreamPackage {
|
|
33
|
+
const resolvePackageJson = deps.resolvePackageJson
|
|
34
|
+
?? (() => createRequire(import.meta.url).resolve(UPSTREAM_PACKAGE_NAME + "/package.json"));
|
|
35
|
+
const readJson = deps.readJson
|
|
36
|
+
?? (path => JSON.parse(readFileSync(path, "utf8")) as unknown);
|
|
37
|
+
const exists = deps.exists ?? existsSync;
|
|
38
|
+
|
|
39
|
+
let packageJsonPath: string;
|
|
40
|
+
try {
|
|
41
|
+
packageJsonPath = resolvePackageJson();
|
|
42
|
+
} catch {
|
|
43
|
+
throw new UpstreamPackageError(
|
|
44
|
+
UPSTREAM_PACKAGE_NAME + " is not installed. Reinstall this package with its dependencies.",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
let parsed: unknown;
|
|
48
|
+
try {
|
|
49
|
+
parsed = readJson(packageJsonPath);
|
|
50
|
+
} catch {
|
|
51
|
+
throw new UpstreamPackageError("The installed opencodex package metadata is unreadable.");
|
|
52
|
+
}
|
|
53
|
+
const packageJson = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
54
|
+
? parsed as Record<string, unknown>
|
|
55
|
+
: undefined;
|
|
56
|
+
if (packageJson?.name !== UPSTREAM_PACKAGE_NAME || packageJson.version !== UPSTREAM_PACKAGE_VERSION) {
|
|
57
|
+
throw new UpstreamPackageError(
|
|
58
|
+
"Expected " + UPSTREAM_PACKAGE_NAME + "@" + UPSTREAM_PACKAGE_VERSION
|
|
59
|
+
+ ", found " + String(packageJson?.name ?? "unknown") + "@" + String(packageJson?.version ?? "unknown") + ".",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const root = dirname(packageJsonPath);
|
|
63
|
+
const cliEntry = join(root, UPSTREAM_CLI_RELATIVE_PATH);
|
|
64
|
+
if (!exists(cliEntry)) {
|
|
65
|
+
throw new UpstreamPackageError(
|
|
66
|
+
UPSTREAM_PACKAGE_NAME + "@" + UPSTREAM_PACKAGE_VERSION + " does not contain " + UPSTREAM_CLI_RELATIVE_PATH + ".",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
name: UPSTREAM_PACKAGE_NAME,
|
|
71
|
+
version: UPSTREAM_PACKAGE_VERSION,
|
|
72
|
+
root,
|
|
73
|
+
cliEntry,
|
|
74
|
+
};
|
|
75
|
+
}
|