@koreanhole/opencodex 2.95.10 → 2.97.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.
@@ -6,8 +6,18 @@ import type {
6
6
  OcxTool,
7
7
  OcxToolChoice,
8
8
  OcxUsage,
9
+ OcxProviderModel,
9
10
  } from "../types";
10
11
  import { runClineTurn, type ClineTurnDeps } from "../adapters/cli-agent/cline/adapter";
12
+ import { runGeminiTurn, type GeminiTurnDeps } from "../adapters/cli-agent/gemini/adapter";
13
+ import { CLINE_PROVIDER_ID, GEMINI_PROVIDER_ID, type ProviderId } from "../config";
14
+
15
+ export interface ProviderTurnDeps {
16
+ cline?: ClineTurnDeps;
17
+ gemini?: GeminiTurnDeps;
18
+ }
19
+
20
+ type TurnDepsInput = ClineTurnDeps | ProviderTurnDeps;
11
21
 
12
22
  export interface DeclaredTool {
13
23
  tool: OcxTool;
@@ -34,7 +44,7 @@ export class UnsupportedMediaError extends RequestValidationError {
34
44
  readonly code = "unsupported_input_modality";
35
45
 
36
46
  constructor() {
37
- super("Cline CLI is text-only; image, video, and audio input are not supported.");
47
+ super("The selected CLI provider is text-only; image, video, and audio input are not supported.");
38
48
  this.name = "UnsupportedMediaError";
39
49
  }
40
50
  }
@@ -266,15 +276,119 @@ export function resolveClineModel(requested: string, known: Iterable<string>): s
266
276
  return decodeRoutedModelId(suffix, known);
267
277
  }
268
278
 
269
- export function modelList(known: string[]): Array<Record<string, unknown>> {
270
- const ids = ["default", ...known.filter(id => id !== "default")];
271
- return [...new Set(ids)].map(id => ({
272
- id: `cline-cli/${encodeRoutedModelId(id)}`,
279
+ export type ProviderModelEntry = string | OcxProviderModel;
280
+ export type ProviderModels = Partial<Record<ProviderId, readonly ProviderModelEntry[]>>;
281
+
282
+ export interface RoutedModel {
283
+ providerId: ProviderId;
284
+ modelId: string;
285
+ }
286
+
287
+ export function resolveRoutedModel(
288
+ requested: string,
289
+ known: ProviderModels,
290
+ defaultProvider: ProviderId = CLINE_PROVIDER_ID,
291
+ ): RoutedModel {
292
+ if (requested === "default") return { providerId: defaultProvider, modelId: "default" };
293
+ const providerId = requested.startsWith(`${CLINE_PROVIDER_ID}/`)
294
+ ? CLINE_PROVIDER_ID
295
+ : requested.startsWith(`${GEMINI_PROVIDER_ID}/`)
296
+ ? GEMINI_PROVIDER_ID
297
+ : defaultProvider;
298
+ const prefix = `${providerId}/`;
299
+ const suffix = requested.startsWith(prefix) ? requested.slice(prefix.length) : requested;
300
+ return {
301
+ providerId,
302
+ modelId: suffix === "default"
303
+ ? "default"
304
+ : decodeRoutedModelId(suffix, (known[providerId] ?? []).map(modelIdOf)),
305
+ };
306
+ }
307
+
308
+ function modelIdOf(model: ProviderModelEntry): string {
309
+ return typeof model === "string" ? model : model.id;
310
+ }
311
+
312
+ const PROVIDER_LABELS: Record<ProviderId, string> = {
313
+ [CLINE_PROVIDER_ID]: "Cline CLI",
314
+ [GEMINI_PROVIDER_ID]: "Gemini CLI",
315
+ };
316
+
317
+ export function modelList(known: string[] | ProviderModels): Array<Record<string, unknown>> {
318
+ if (Array.isArray(known)) {
319
+ const ids = ["default", ...known.filter(id => id !== "default")];
320
+ return [...new Set(ids)].map(id => ({
321
+ id: `${CLINE_PROVIDER_ID}/${encodeRoutedModelId(id)}`,
322
+ object: "model",
323
+ created: 0,
324
+ owned_by: CLINE_PROVIDER_ID,
325
+ display_name: id === "default" ? "Cline default" : id,
326
+ }));
327
+ }
328
+ const rows: Array<Record<string, unknown>> = [{
329
+ id: "default",
273
330
  object: "model",
274
331
  created: 0,
275
- owned_by: "cline-cli",
276
- display_name: id === "default" ? "Cline default" : id,
277
- }));
332
+ owned_by: "opencodex",
333
+ display_name: "Configured default provider",
334
+ }];
335
+ for (const providerId of [CLINE_PROVIDER_ID, GEMINI_PROVIDER_ID] as const) {
336
+ const entries = ["default", ...(known[providerId] ?? []).filter(entry => modelIdOf(entry) !== "default")];
337
+ const seen = new Set<string>();
338
+ for (const entry of entries) {
339
+ const id = modelIdOf(entry);
340
+ if (seen.has(id)) continue;
341
+ seen.add(id);
342
+ const info = typeof entry === "string" ? { id: entry } as OcxProviderModel : entry;
343
+ const reasoning = info.supportedReasoningLevels ?? [];
344
+ rows.push({
345
+ id: `${providerId}/${encodeRoutedModelId(id)}`,
346
+ object: "model",
347
+ created: 0,
348
+ owned_by: providerId,
349
+ display_name: info.name ?? (id === "default" ? `${PROVIDER_LABELS[providerId]} default` : id),
350
+ ...(info.description ? { description: info.description } : {}),
351
+ ...(reasoning.length > 0
352
+ ? {
353
+ supported_reasoning_levels: reasoning.map(level => ({
354
+ effort: level.effort,
355
+ description: level.description,
356
+ })),
357
+ default_reasoning_level: info.defaultReasoningLevel ?? reasoning[0]!.effort,
358
+ }
359
+ : { supported_reasoning_levels: [] }),
360
+ });
361
+ }
362
+ }
363
+ return rows;
364
+ }
365
+
366
+ function providerDeps(deps: TurnDepsInput): ProviderTurnDeps {
367
+ if ("cline" in deps || "gemini" in deps) return deps as ProviderTurnDeps;
368
+ return { cline: deps as ClineTurnDeps };
369
+ }
370
+
371
+ function providerModels(known: string[] | ProviderModels, defaultProvider: ProviderId): ProviderModels {
372
+ return Array.isArray(known) ? { [defaultProvider]: known } : known;
373
+ }
374
+
375
+ export function routedTurn(
376
+ parsed: OcxParsedRequest,
377
+ requestedModel: string,
378
+ incoming: IncomingMeta,
379
+ known: string[] | ProviderModels,
380
+ deps: TurnDepsInput,
381
+ defaultProvider: ProviderId = CLINE_PROVIDER_ID,
382
+ ): { providerId: ProviderId; run: (emit: (event: AdapterEvent) => void) => Promise<void> } {
383
+ const route = resolveRoutedModel(requestedModel, providerModels(known, defaultProvider), defaultProvider);
384
+ parsed.modelId = route.modelId;
385
+ const resolved = providerDeps(deps);
386
+ return {
387
+ providerId: route.providerId,
388
+ run: route.providerId === GEMINI_PROVIDER_ID
389
+ ? emit => runGeminiTurn(parsed, incoming, emit, resolved.gemini)
390
+ : emit => runClineTurn(parsed, incoming, emit, resolved.cline),
391
+ };
278
392
  }
279
393
 
280
394
  function usage(value: OcxUsage | undefined): Record<string, unknown> | null {
@@ -608,12 +722,12 @@ export function streamResponsesResult(
608
722
  export async function handleResponses(
609
723
  body: unknown,
610
724
  incoming: IncomingMeta,
611
- knownModels: string[],
612
- deps: ClineTurnDeps = {},
725
+ knownModels: string[] | ProviderModels,
726
+ deps: TurnDepsInput = {},
727
+ defaultProvider: ProviderId = CLINE_PROVIDER_ID,
613
728
  ): Promise<Response> {
614
729
  const parsed = parseResponsesRequest(body);
615
- parsed.parsed.modelId = resolveClineModel(parsed.requestedModel, knownModels);
616
- const run = (emit: (event: AdapterEvent) => void) => runClineTurn(parsed.parsed, incoming, emit, deps);
730
+ const { run } = routedTurn(parsed.parsed, parsed.requestedModel, incoming, knownModels, deps, defaultProvider);
617
731
  if (parsed.parsed.stream) {
618
732
  return new Response(streamResponsesResult(parsed, run, incoming.abortSignal), {
619
733
  headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache" },
package/src/types.ts CHANGED
@@ -89,12 +89,26 @@ export interface OcxParsedRequest {
89
89
  export interface OcxUsage {
90
90
  inputTokens: number;
91
91
  outputTokens: number;
92
+ modelId?: string;
92
93
  totalTokens?: number;
93
94
  cachedInputTokens?: number;
94
95
  cacheReadInputTokens?: number;
95
96
  cacheCreationInputTokens?: number;
96
97
  }
97
98
 
99
+ export interface OcxReasoningLevel {
100
+ effort: string;
101
+ description: string;
102
+ }
103
+
104
+ export interface OcxProviderModel {
105
+ id: string;
106
+ name?: string;
107
+ description?: string;
108
+ supportedReasoningLevels?: readonly OcxReasoningLevel[];
109
+ defaultReasoningLevel?: string;
110
+ }
111
+
98
112
  export type AdapterEvent =
99
113
  | { type: "heartbeat" }
100
114
  | { type: "text_delta"; text: string }