@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/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: typeof CLINE_PROVIDER_ID;
21
- providers: Record<typeof CLINE_PROVIDER_ID, ClineProviderConfig>;
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 normalizeProvider(value: unknown): ClineProviderConfig {
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
- if (row.defaultProvider !== undefined && row.defaultProvider !== CLINE_PROVIDER_ID) {
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 !== CLINE_PROVIDER_ID) console.warn(`[opencodex] ignoring unsupported provider ${JSON.stringify(key)}.`);
156
+ if (!providerId(key)) console.warn(`[opencodex] ignoring unsupported provider ${JSON.stringify(key)}.`);
118
157
  }
119
- const provider = normalizeProvider(providers?.[CLINE_PROVIDER_ID] ?? defaultConfig().providers[CLINE_PROVIDER_ID]);
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]: provider },
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 Cline-only default.`);
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(/(authorization\s*[:=]\s*)\S+/gi, "$1[redacted]")
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
  }
@@ -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 { handleResponses, modelList, RequestValidationError, UnsupportedMediaError } from "./responses";
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(env: NodeJS.ProcessEnv, deps: ClineTurnDeps | undefined): Promise<string[]> {
77
- const live = await discoverClineLiveModels({ env, ...(deps ? { deps } : {}) });
78
- if (live.ok) return ["default", ...live.models.filter(id => id !== "default")];
79
- if (live.reason !== "not_found") console.warn(`[opencodex] Cline model discovery degraded [${live.reason}]: ${live.detail}`);
80
- return ["default"];
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-cline", version: packageInfo.version });
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, clineDeps);
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, clineDeps);
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,
@@ -1,12 +1,15 @@
1
1
  import type { AdapterEvent, IncomingMeta, OcxMessage, OcxParsedRequest, OcxTool, OcxToolChoice } from "../types";
2
- import { runClineTurn, type ClineTurnDeps } from "../adapters/cli-agent/cline/adapter";
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
- resolveClineModel,
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.modelId = parsed.requestedModel.startsWith("cline-cli/")
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({
@@ -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("Cline CLI is text-only; image, video, and audio input are not supported.");
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 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)}`,
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: "cline-cli",
276
- display_name: id === "default" ? "Cline default" : id,
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: ClineTurnDeps = {},
701
+ knownModels: string[] | ProviderModels,
702
+ deps: TurnDepsInput = {},
703
+ defaultProvider: ProviderId = CLINE_PROVIDER_ID,
613
704
  ): Promise<Response> {
614
705
  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);
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" },
package/src/types.ts CHANGED
@@ -89,6 +89,7 @@ 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;