@koreanhole/opencodex 2.95.10 → 2.96.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 +20 -5
- package/README.md +68 -31
- package/package.json +5 -2
- package/src/adapters/cli-agent/acp-client.ts +545 -0
- package/src/adapters/cli-agent/cline/acp.ts +15 -279
- package/src/adapters/cli-agent/cline/adapter.ts +3 -3
- package/src/adapters/cli-agent/cline/live-models.ts +6 -5
- package/src/adapters/cli-agent/cline/stream.ts +6 -37
- package/src/adapters/cli-agent/gemini/acp.ts +629 -0
- package/src/adapters/cli-agent/gemini/adapter.ts +222 -0
- package/src/adapters/cli-agent/gemini/binary.ts +23 -0
- package/src/adapters/cli-agent/gemini/live-models.ts +134 -0
- package/src/adapters/cli-agent/mcp-server.ts +29 -4
- package/src/adapters/cli-agent/tool-intent.ts +71 -0
- package/src/cli/index.ts +38 -18
- package/src/config.ts +50 -9
- package/src/index.ts +1 -0
- package/src/lib/redact.ts +4 -1
- package/src/server/index.ts +32 -13
- package/src/server/messages.ts +9 -8
- package/src/server/responses.ts +102 -12
- package/src/types.ts +1 -0
package/src/config.ts
CHANGED
|
@@ -3,10 +3,13 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
export const CLINE_PROVIDER_ID = "cline-cli";
|
|
6
|
+
export const GEMINI_PROVIDER_ID = "gemini-cli";
|
|
6
7
|
export const CLINE_BASE_URL = "https://api.cline.bot";
|
|
7
8
|
export const DEFAULT_PORT = 10_100;
|
|
8
9
|
export const DEFAULT_HOST = "127.0.0.1";
|
|
9
10
|
|
|
11
|
+
export type ProviderId = typeof CLINE_PROVIDER_ID | typeof GEMINI_PROVIDER_ID;
|
|
12
|
+
|
|
10
13
|
export interface ClineProviderConfig {
|
|
11
14
|
adapter: "cline-cli";
|
|
12
15
|
baseUrl: typeof CLINE_BASE_URL;
|
|
@@ -14,11 +17,19 @@ export interface ClineProviderConfig {
|
|
|
14
17
|
models: string[];
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
export interface GeminiProviderConfig {
|
|
21
|
+
adapter: "gemini-cli";
|
|
22
|
+
defaultModel: "default";
|
|
23
|
+
models: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ProviderConfig = ClineProviderConfig | GeminiProviderConfig;
|
|
27
|
+
|
|
17
28
|
export interface RuntimeConfig {
|
|
18
29
|
port: number;
|
|
19
30
|
host: string;
|
|
20
|
-
defaultProvider:
|
|
21
|
-
providers: Record<
|
|
31
|
+
defaultProvider: ProviderId;
|
|
32
|
+
providers: Record<ProviderId, ProviderConfig>;
|
|
22
33
|
apiAuthToken?: string;
|
|
23
34
|
}
|
|
24
35
|
|
|
@@ -67,6 +78,11 @@ export function defaultConfig(): RuntimeConfig {
|
|
|
67
78
|
defaultModel: "default",
|
|
68
79
|
models: ["default"],
|
|
69
80
|
},
|
|
81
|
+
[GEMINI_PROVIDER_ID]: {
|
|
82
|
+
adapter: GEMINI_PROVIDER_ID,
|
|
83
|
+
defaultModel: "default",
|
|
84
|
+
models: ["default"],
|
|
85
|
+
},
|
|
70
86
|
},
|
|
71
87
|
};
|
|
72
88
|
}
|
|
@@ -85,7 +101,7 @@ function validPort(value: unknown): number {
|
|
|
85
101
|
return value;
|
|
86
102
|
}
|
|
87
103
|
|
|
88
|
-
function
|
|
104
|
+
function normalizeClineProvider(value: unknown): ClineProviderConfig {
|
|
89
105
|
const row = asRecord(value);
|
|
90
106
|
if (!row) throw new ConfigError("config.providers.cline-cli must be an object.");
|
|
91
107
|
if (row.adapter !== undefined && row.adapter !== CLINE_PROVIDER_ID) {
|
|
@@ -106,17 +122,42 @@ function normalizeProvider(value: unknown): ClineProviderConfig {
|
|
|
106
122
|
};
|
|
107
123
|
}
|
|
108
124
|
|
|
125
|
+
function normalizeGeminiProvider(value: unknown): GeminiProviderConfig {
|
|
126
|
+
const row = asRecord(value);
|
|
127
|
+
if (!row) throw new ConfigError("config.providers.gemini-cli must be an object.");
|
|
128
|
+
if (row.adapter !== undefined && row.adapter !== GEMINI_PROVIDER_ID) {
|
|
129
|
+
throw new ConfigError("config.providers.gemini-cli.adapter must be gemini-cli.");
|
|
130
|
+
}
|
|
131
|
+
const models = Array.isArray(row.models)
|
|
132
|
+
? [...new Set(row.models.filter((item): item is string => typeof item === "string" && item.length > 0))]
|
|
133
|
+
: ["default"];
|
|
134
|
+
if (!models.includes("default")) models.unshift("default");
|
|
135
|
+
return {
|
|
136
|
+
adapter: GEMINI_PROVIDER_ID,
|
|
137
|
+
defaultModel: "default",
|
|
138
|
+
models,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function providerId(value: unknown): ProviderId | undefined {
|
|
143
|
+
return value === CLINE_PROVIDER_ID || value === GEMINI_PROVIDER_ID ? value : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
109
146
|
export function normalizeConfig(input: unknown): RuntimeConfig {
|
|
110
147
|
const row = asRecord(input);
|
|
111
148
|
if (!row) throw new ConfigError("config.json must contain a JSON object.");
|
|
112
|
-
|
|
149
|
+
let defaultProvider = providerId(row.defaultProvider);
|
|
150
|
+
if (row.defaultProvider !== undefined && !defaultProvider) {
|
|
113
151
|
console.warn(`[opencodex] ignoring unsupported defaultProvider ${JSON.stringify(row.defaultProvider)}; using cline-cli.`);
|
|
152
|
+
defaultProvider = CLINE_PROVIDER_ID;
|
|
114
153
|
}
|
|
115
154
|
const providers = asRecord(row.providers);
|
|
116
155
|
for (const key of Object.keys(providers ?? {})) {
|
|
117
|
-
if (key
|
|
156
|
+
if (!providerId(key)) console.warn(`[opencodex] ignoring unsupported provider ${JSON.stringify(key)}.`);
|
|
118
157
|
}
|
|
119
|
-
const
|
|
158
|
+
const defaults = defaultConfig().providers;
|
|
159
|
+
const cline = normalizeClineProvider(providers?.[CLINE_PROVIDER_ID] ?? defaults[CLINE_PROVIDER_ID]);
|
|
160
|
+
const gemini = normalizeGeminiProvider(providers?.[GEMINI_PROVIDER_ID] ?? defaults[GEMINI_PROVIDER_ID]);
|
|
120
161
|
const host = typeof row.host === "string" && row.host.trim() ? row.host.trim() : DEFAULT_HOST;
|
|
121
162
|
const apiAuthToken = typeof row.apiAuthToken === "string" && row.apiAuthToken.trim()
|
|
122
163
|
? row.apiAuthToken.trim()
|
|
@@ -124,8 +165,8 @@ export function normalizeConfig(input: unknown): RuntimeConfig {
|
|
|
124
165
|
return {
|
|
125
166
|
port: row.port === undefined ? DEFAULT_PORT : validPort(row.port),
|
|
126
167
|
host,
|
|
127
|
-
defaultProvider: CLINE_PROVIDER_ID,
|
|
128
|
-
providers: { [CLINE_PROVIDER_ID]:
|
|
168
|
+
defaultProvider: defaultProvider ?? CLINE_PROVIDER_ID,
|
|
169
|
+
providers: { [CLINE_PROVIDER_ID]: cline, [GEMINI_PROVIDER_ID]: gemini },
|
|
129
170
|
...(apiAuthToken ? { apiAuthToken } : {}),
|
|
130
171
|
};
|
|
131
172
|
}
|
|
@@ -143,7 +184,7 @@ export async function loadConfig(env: NodeJS.ProcessEnv = process.env): Promise<
|
|
|
143
184
|
try {
|
|
144
185
|
parsed = JSON.parse(text);
|
|
145
186
|
} catch {
|
|
146
|
-
throw new ConfigError(`${path} is not valid JSON. Repair it or remove it to recreate the
|
|
187
|
+
throw new ConfigError(`${path} is not valid JSON. Repair it or remove it to recreate the default config.`);
|
|
147
188
|
}
|
|
148
189
|
return normalizeConfig(parsed);
|
|
149
190
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { startServer, type RunningServer, type ServerOptions } from "./server";
|
|
2
2
|
export { loadConfig, saveConfig, defaultConfig } from "./config";
|
|
3
3
|
export { runClineTurn } from "./adapters/cli-agent/cline/adapter";
|
|
4
|
+
export { runGeminiTurn } from "./adapters/cli-agent/gemini/adapter";
|
|
4
5
|
export type {
|
|
5
6
|
AdapterEvent,
|
|
6
7
|
IncomingMeta,
|
package/src/lib/redact.ts
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
export function redactSecretString(value: string): string {
|
|
3
3
|
return value
|
|
4
4
|
.replace(/\bsk-[A-Za-z0-9_-]{6,}\b/g, "[redacted]")
|
|
5
|
-
.replace(
|
|
5
|
+
.replace(/\bAIza[0-9A-Za-z_-]{20,}\b/g, "[redacted]")
|
|
6
|
+
.replace(/\bya29\.[0-9A-Za-z._-]{20,}\b/g, "[redacted]")
|
|
7
|
+
.replace(/(authorization\s*[:=]\s*)(?:bearer\s+)?\S+/gi, "$1[redacted]")
|
|
6
8
|
.replace(/(x-api-key\s*[:=]\s*)\S+/gi, "$1[redacted]")
|
|
9
|
+
.replace(/((?:api[_-]?key|token)\s*[:=]\s*)\S+/gi, "$1[redacted]")
|
|
7
10
|
.slice(0, 4_096);
|
|
8
11
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
} from "../config";
|
|
10
10
|
import type { IncomingMeta } from "../types";
|
|
11
11
|
import type { ClineTurnDeps } from "../adapters/cli-agent/cline/adapter";
|
|
12
|
+
import type { GeminiTurnDeps } from "../adapters/cli-agent/gemini/adapter";
|
|
12
13
|
import { discoverClineLiveModels } from "../adapters/cli-agent/cline/live-models";
|
|
13
|
-
import {
|
|
14
|
+
import { discoverGeminiLiveModels } from "../adapters/cli-agent/gemini/live-models";
|
|
15
|
+
import { handleResponses, modelList, RequestValidationError, UnsupportedMediaError, type ProviderModels } from "./responses";
|
|
14
16
|
import { handleCountTokens, handleMessages } from "./messages";
|
|
15
17
|
|
|
16
18
|
const MAX_REQUEST_BYTES = 32 * 1024 * 1024;
|
|
@@ -19,6 +21,7 @@ const REQUEST_TIMEOUT_MS = 300_000;
|
|
|
19
21
|
export interface ServerOptions {
|
|
20
22
|
env?: NodeJS.ProcessEnv;
|
|
21
23
|
clineDeps?: ClineTurnDeps;
|
|
24
|
+
geminiDeps?: GeminiTurnDeps;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export interface RunningServer {
|
|
@@ -73,11 +76,25 @@ function errorResponse(error: unknown): Response {
|
|
|
73
76
|
return Response.json({ error: { message: "Proxy request failed.", type: "proxy_error", code: "proxy_error" } }, { status: 500 });
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
async function discoverModels(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
async function discoverModels(
|
|
80
|
+
env: NodeJS.ProcessEnv,
|
|
81
|
+
clineDeps: ClineTurnDeps | undefined,
|
|
82
|
+
geminiDeps: GeminiTurnDeps | undefined,
|
|
83
|
+
): Promise<ProviderModels> {
|
|
84
|
+
const [cline, gemini] = await Promise.all([
|
|
85
|
+
discoverClineLiveModels({ env, ...(clineDeps ? { deps: clineDeps } : {}) }),
|
|
86
|
+
discoverGeminiLiveModels({ env, ...(geminiDeps ? { deps: geminiDeps } : {}) }),
|
|
87
|
+
]);
|
|
88
|
+
if (!cline.ok && cline.reason !== "not_found" && env.OPENCODEX_CLINE_ACP?.trim() !== "0") {
|
|
89
|
+
console.warn(`[opencodex] Cline model discovery degraded [${cline.reason}]: ${cline.detail}`);
|
|
90
|
+
}
|
|
91
|
+
if (!gemini.ok && gemini.reason !== "not_found" && env.OPENCODEX_GEMINI_ACP?.trim() !== "0") {
|
|
92
|
+
console.warn(`[opencodex] Gemini model discovery degraded [${gemini.reason}]: ${gemini.detail}`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
"cline-cli": cline.ok ? ["default", ...cline.models.filter(id => id !== "default")] : ["default"],
|
|
96
|
+
"gemini-cli": gemini.ok ? ["default", ...gemini.models.filter(id => id !== "default")] : ["default"],
|
|
97
|
+
};
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
export function startServer(config: RuntimeConfig | number, options: ServerOptions = {}): RunningServer {
|
|
@@ -86,6 +103,8 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
86
103
|
: normalizeConfig(config);
|
|
87
104
|
const env = options.env ?? process.env;
|
|
88
105
|
const clineDeps = { ...options.clineDeps, env: options.clineDeps?.env ?? env };
|
|
106
|
+
const geminiDeps = { ...options.geminiDeps, env: options.geminiDeps?.env ?? env };
|
|
107
|
+
const turnDeps = { cline: clineDeps, gemini: geminiDeps };
|
|
89
108
|
assertSafeListener(config, env);
|
|
90
109
|
const token = apiToken(config, env);
|
|
91
110
|
let server: ReturnType<typeof Bun.serve>;
|
|
@@ -93,28 +112,28 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
93
112
|
const fetch = async (req: Request): Promise<Response> => {
|
|
94
113
|
const url = new URL(req.url);
|
|
95
114
|
if (url.pathname === "/healthz") {
|
|
96
|
-
return Response.json({ ok: true, service: "opencodex
|
|
115
|
+
return Response.json({ ok: true, service: "opencodex", version: packageInfo.version });
|
|
97
116
|
}
|
|
98
117
|
if (!authorized(req, token)) {
|
|
99
118
|
return Response.json({ error: { message: "Unauthorized", type: "authentication_error", code: "unauthorized" } }, { status: 401 });
|
|
100
119
|
}
|
|
101
120
|
try {
|
|
102
121
|
if (req.method === "GET" && url.pathname === "/v1/models") {
|
|
103
|
-
const known = await discoverModels(env, options.clineDeps);
|
|
122
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps);
|
|
104
123
|
return Response.json({ object: "list", data: modelList(known) });
|
|
105
124
|
}
|
|
106
125
|
if (req.method === "POST" && url.pathname === "/v1/responses") {
|
|
107
126
|
const body = await boundedJson(req);
|
|
108
|
-
const known = await discoverModels(env, options.clineDeps);
|
|
127
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps);
|
|
109
128
|
const signal = AbortSignal.any([req.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
|
|
110
129
|
const incoming: IncomingMeta = { headers: req.headers, abortSignal: signal };
|
|
111
|
-
return await handleResponses(body, incoming, known,
|
|
130
|
+
return await handleResponses(body, incoming, known, turnDeps, config.defaultProvider);
|
|
112
131
|
}
|
|
113
132
|
if (req.method === "POST" && url.pathname === "/v1/messages") {
|
|
114
133
|
const body = await boundedJson(req);
|
|
115
|
-
const known = await discoverModels(env, options.clineDeps);
|
|
134
|
+
const known = await discoverModels(env, options.clineDeps, options.geminiDeps);
|
|
116
135
|
const signal = AbortSignal.any([req.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
|
|
117
|
-
return await handleMessages(body, { headers: req.headers, abortSignal: signal }, known,
|
|
136
|
+
return await handleMessages(body, { headers: req.headers, abortSignal: signal }, known, turnDeps, config.defaultProvider);
|
|
118
137
|
}
|
|
119
138
|
if (req.method === "POST" && url.pathname === "/v1/messages/count_tokens") {
|
|
120
139
|
return handleCountTokens(await boundedJson(req));
|
|
@@ -125,7 +144,7 @@ export function startServer(config: RuntimeConfig | number, options: ServerOptio
|
|
|
125
144
|
}
|
|
126
145
|
};
|
|
127
146
|
|
|
128
|
-
server = Bun.serve({ hostname: config.host, port: config.port, fetch });
|
|
147
|
+
server = Bun.serve({ hostname: config.host, port: config.port, idleTimeout: 255, fetch });
|
|
129
148
|
return {
|
|
130
149
|
port: server.port ?? config.port,
|
|
131
150
|
host: config.host,
|
package/src/server/messages.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { AdapterEvent, IncomingMeta, OcxMessage, OcxParsedRequest, OcxTool, OcxToolChoice } from "../types";
|
|
2
|
-
import {
|
|
2
|
+
import type { ClineTurnDeps } from "../adapters/cli-agent/cline/adapter";
|
|
3
|
+
import { CLINE_PROVIDER_ID, type ProviderId } from "../config";
|
|
3
4
|
import {
|
|
4
5
|
collectResponsesResult,
|
|
5
6
|
RequestValidationError,
|
|
6
|
-
|
|
7
|
+
routedTurn,
|
|
7
8
|
UnsupportedMediaError,
|
|
8
9
|
type DeclaredTool,
|
|
9
10
|
type ParsedResponsesRequest,
|
|
11
|
+
type ProviderModels,
|
|
12
|
+
type ProviderTurnDeps,
|
|
10
13
|
} from "./responses";
|
|
11
14
|
|
|
12
15
|
function record(value: unknown): Record<string, unknown> | undefined {
|
|
@@ -210,14 +213,12 @@ function anthropicUsage(value: unknown): { input_tokens: number; output_tokens:
|
|
|
210
213
|
export async function handleMessages(
|
|
211
214
|
body: unknown,
|
|
212
215
|
incoming: IncomingMeta,
|
|
213
|
-
knownModels: string[],
|
|
214
|
-
deps: ClineTurnDeps = {},
|
|
216
|
+
knownModels: string[] | ProviderModels,
|
|
217
|
+
deps: ClineTurnDeps | ProviderTurnDeps = {},
|
|
218
|
+
defaultProvider: ProviderId = CLINE_PROVIDER_ID,
|
|
215
219
|
): Promise<Response> {
|
|
216
220
|
const parsed = parseMessagesRequest(body);
|
|
217
|
-
parsed.parsed
|
|
218
|
-
? resolveClineModel(parsed.requestedModel, knownModels)
|
|
219
|
-
: "default";
|
|
220
|
-
const run = (emit: (event: AdapterEvent) => void) => runClineTurn(parsed.parsed, incoming, emit, deps);
|
|
221
|
+
const { run } = routedTurn(parsed.parsed, parsed.requestedModel, incoming, knownModels, deps, defaultProvider);
|
|
221
222
|
const result = await collectResponsesResult(parsed, run);
|
|
222
223
|
if (result.failure) {
|
|
223
224
|
return Response.json({
|
package/src/server/responses.ts
CHANGED
|
@@ -8,6 +8,15 @@ import type {
|
|
|
8
8
|
OcxUsage,
|
|
9
9
|
} from "../types";
|
|
10
10
|
import { runClineTurn, type ClineTurnDeps } from "../adapters/cli-agent/cline/adapter";
|
|
11
|
+
import { runGeminiTurn, type GeminiTurnDeps } from "../adapters/cli-agent/gemini/adapter";
|
|
12
|
+
import { CLINE_PROVIDER_ID, GEMINI_PROVIDER_ID, type ProviderId } from "../config";
|
|
13
|
+
|
|
14
|
+
export interface ProviderTurnDeps {
|
|
15
|
+
cline?: ClineTurnDeps;
|
|
16
|
+
gemini?: GeminiTurnDeps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type TurnDepsInput = ClineTurnDeps | ProviderTurnDeps;
|
|
11
20
|
|
|
12
21
|
export interface DeclaredTool {
|
|
13
22
|
tool: OcxTool;
|
|
@@ -34,7 +43,7 @@ export class UnsupportedMediaError extends RequestValidationError {
|
|
|
34
43
|
readonly code = "unsupported_input_modality";
|
|
35
44
|
|
|
36
45
|
constructor() {
|
|
37
|
-
super("
|
|
46
|
+
super("The selected CLI provider is text-only; image, video, and audio input are not supported.");
|
|
38
47
|
this.name = "UnsupportedMediaError";
|
|
39
48
|
}
|
|
40
49
|
}
|
|
@@ -266,15 +275,96 @@ export function resolveClineModel(requested: string, known: Iterable<string>): s
|
|
|
266
275
|
return decodeRoutedModelId(suffix, known);
|
|
267
276
|
}
|
|
268
277
|
|
|
269
|
-
export
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
278
|
+
export type ProviderModels = Partial<Record<ProviderId, readonly string[]>>;
|
|
279
|
+
|
|
280
|
+
export interface RoutedModel {
|
|
281
|
+
providerId: ProviderId;
|
|
282
|
+
modelId: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function resolveRoutedModel(
|
|
286
|
+
requested: string,
|
|
287
|
+
known: ProviderModels,
|
|
288
|
+
defaultProvider: ProviderId = CLINE_PROVIDER_ID,
|
|
289
|
+
): RoutedModel {
|
|
290
|
+
if (requested === "default") return { providerId: defaultProvider, modelId: "default" };
|
|
291
|
+
const providerId = requested.startsWith(`${CLINE_PROVIDER_ID}/`)
|
|
292
|
+
? CLINE_PROVIDER_ID
|
|
293
|
+
: requested.startsWith(`${GEMINI_PROVIDER_ID}/`)
|
|
294
|
+
? GEMINI_PROVIDER_ID
|
|
295
|
+
: defaultProvider;
|
|
296
|
+
const prefix = `${providerId}/`;
|
|
297
|
+
const suffix = requested.startsWith(prefix) ? requested.slice(prefix.length) : requested;
|
|
298
|
+
return {
|
|
299
|
+
providerId,
|
|
300
|
+
modelId: suffix === "default" ? "default" : decodeRoutedModelId(suffix, known[providerId] ?? []),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const PROVIDER_LABELS: Record<ProviderId, string> = {
|
|
305
|
+
[CLINE_PROVIDER_ID]: "Cline CLI",
|
|
306
|
+
[GEMINI_PROVIDER_ID]: "Gemini CLI",
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export function modelList(known: string[] | ProviderModels): Array<Record<string, unknown>> {
|
|
310
|
+
if (Array.isArray(known)) {
|
|
311
|
+
const ids = ["default", ...known.filter(id => id !== "default")];
|
|
312
|
+
return [...new Set(ids)].map(id => ({
|
|
313
|
+
id: `${CLINE_PROVIDER_ID}/${encodeRoutedModelId(id)}`,
|
|
314
|
+
object: "model",
|
|
315
|
+
created: 0,
|
|
316
|
+
owned_by: CLINE_PROVIDER_ID,
|
|
317
|
+
display_name: id === "default" ? "Cline default" : id,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
const rows: Array<Record<string, unknown>> = [{
|
|
321
|
+
id: "default",
|
|
273
322
|
object: "model",
|
|
274
323
|
created: 0,
|
|
275
|
-
owned_by: "
|
|
276
|
-
display_name:
|
|
277
|
-
}
|
|
324
|
+
owned_by: "opencodex",
|
|
325
|
+
display_name: "Configured default provider",
|
|
326
|
+
}];
|
|
327
|
+
for (const providerId of [CLINE_PROVIDER_ID, GEMINI_PROVIDER_ID] as const) {
|
|
328
|
+
const ids = ["default", ...(known[providerId] ?? []).filter(id => id !== "default")];
|
|
329
|
+
for (const id of new Set(ids)) {
|
|
330
|
+
rows.push({
|
|
331
|
+
id: `${providerId}/${encodeRoutedModelId(id)}`,
|
|
332
|
+
object: "model",
|
|
333
|
+
created: 0,
|
|
334
|
+
owned_by: providerId,
|
|
335
|
+
display_name: id === "default" ? `${PROVIDER_LABELS[providerId]} default` : id,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return rows;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function providerDeps(deps: TurnDepsInput): ProviderTurnDeps {
|
|
343
|
+
if ("cline" in deps || "gemini" in deps) return deps as ProviderTurnDeps;
|
|
344
|
+
return { cline: deps as ClineTurnDeps };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function providerModels(known: string[] | ProviderModels, defaultProvider: ProviderId): ProviderModels {
|
|
348
|
+
return Array.isArray(known) ? { [defaultProvider]: known } : known;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function routedTurn(
|
|
352
|
+
parsed: OcxParsedRequest,
|
|
353
|
+
requestedModel: string,
|
|
354
|
+
incoming: IncomingMeta,
|
|
355
|
+
known: string[] | ProviderModels,
|
|
356
|
+
deps: TurnDepsInput,
|
|
357
|
+
defaultProvider: ProviderId = CLINE_PROVIDER_ID,
|
|
358
|
+
): { providerId: ProviderId; run: (emit: (event: AdapterEvent) => void) => Promise<void> } {
|
|
359
|
+
const route = resolveRoutedModel(requestedModel, providerModels(known, defaultProvider), defaultProvider);
|
|
360
|
+
parsed.modelId = route.modelId;
|
|
361
|
+
const resolved = providerDeps(deps);
|
|
362
|
+
return {
|
|
363
|
+
providerId: route.providerId,
|
|
364
|
+
run: route.providerId === GEMINI_PROVIDER_ID
|
|
365
|
+
? emit => runGeminiTurn(parsed, incoming, emit, resolved.gemini)
|
|
366
|
+
: emit => runClineTurn(parsed, incoming, emit, resolved.cline),
|
|
367
|
+
};
|
|
278
368
|
}
|
|
279
369
|
|
|
280
370
|
function usage(value: OcxUsage | undefined): Record<string, unknown> | null {
|
|
@@ -608,12 +698,12 @@ export function streamResponsesResult(
|
|
|
608
698
|
export async function handleResponses(
|
|
609
699
|
body: unknown,
|
|
610
700
|
incoming: IncomingMeta,
|
|
611
|
-
knownModels: string[],
|
|
612
|
-
deps:
|
|
701
|
+
knownModels: string[] | ProviderModels,
|
|
702
|
+
deps: TurnDepsInput = {},
|
|
703
|
+
defaultProvider: ProviderId = CLINE_PROVIDER_ID,
|
|
613
704
|
): Promise<Response> {
|
|
614
705
|
const parsed = parseResponsesRequest(body);
|
|
615
|
-
|
|
616
|
-
const run = (emit: (event: AdapterEvent) => void) => runClineTurn(parsed.parsed, incoming, emit, deps);
|
|
706
|
+
const { run } = routedTurn(parsed.parsed, parsed.requestedModel, incoming, knownModels, deps, defaultProvider);
|
|
617
707
|
if (parsed.parsed.stream) {
|
|
618
708
|
return new Response(streamResponsesResult(parsed, run, incoming.abortSignal), {
|
|
619
709
|
headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache" },
|