@bitkyc08/opencodex 0.2.2 → 1.9.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.
@@ -1,11 +1,13 @@
1
1
  import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { atomicWriteFile } from "./config";
4
+ import { atomicWriteFile, websocketsEnabled } from "./config";
5
5
  import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, readRootTomlString, resolveCodexConfigPath } from "./codex-paths";
6
6
  import { DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCached, setCached } from "./model-cache";
7
7
  import { buildModelsRequest, resolveModelsAuthToken } from "./oauth/index";
8
8
  import type { OcxConfig, OcxProviderConfig } from "./types";
9
+ import { getJawcodeModelMetadata, getJawcodeModelMetadataCaseInsensitive, listJawcodeModelMetadata, resolveJawcodeProvider } from "./generated/jawcode-model-metadata";
10
+ import { shouldCaseFoldMetadataModelId } from "./providers/derive";
9
11
 
10
12
  const OCX_DIR = join(homedir(), ".opencodex");
11
13
  const CATALOG_BACKUP_PATH = join(OCX_DIR, "catalog-backup.json");
@@ -33,6 +35,7 @@ export function nativeOpenAiSlugs(): string[] {
33
35
 
34
36
  export interface CatalogModel { id: string; provider: string; owned_by?: string; }
35
37
  type RawEntry = Record<string, unknown>;
38
+ const JAWCODE_CATALOG_AUGMENT_PROVIDERS = new Set(["opencode-go"]);
36
39
 
37
40
  /** Resolve the `model_catalog_json` path from Codex config.toml, else the default. */
38
41
  export function readCodexCatalogPath(): string {
@@ -55,11 +58,13 @@ function readCatalog(path: string): { models?: RawEntry[]; [k: string]: unknown
55
58
  }
56
59
 
57
60
  function normalizeServiceTiers(entry: RawEntry): RawEntry {
58
- if (entry.service_tier === "priority") entry.service_tier = "fast";
61
+ // Codex stores the user-facing config spelling as "fast", but the catalog/request
62
+ // service tier id is "priority" in current codex-rs. Keep legacy catalogs working.
63
+ if (entry.service_tier === "fast") entry.service_tier = "priority";
59
64
  if (Array.isArray(entry.service_tiers)) {
60
65
  entry.service_tiers = entry.service_tiers.map(tier => {
61
- if (tier && typeof tier === "object" && "id" in tier && tier.id === "priority") {
62
- return { ...tier, id: "fast" };
66
+ if (tier && typeof tier === "object" && "id" in tier && tier.id === "fast") {
67
+ return { ...tier, id: "priority" };
63
68
  }
64
69
  return tier;
65
70
  });
@@ -67,6 +72,54 @@ function normalizeServiceTiers(entry: RawEntry): RawEntry {
67
72
  return entry;
68
73
  }
69
74
 
75
+ function ensureAutoCompactTokenLimit(entry: RawEntry): RawEntry {
76
+ if (
77
+ typeof entry.context_window === "number"
78
+ && entry.context_window > 0
79
+ && typeof entry.auto_compact_token_limit !== "number"
80
+ ) {
81
+ entry.auto_compact_token_limit = Math.floor(entry.context_window * 0.9);
82
+ }
83
+ return entry;
84
+ }
85
+
86
+ export function normalizeRoutedCatalogEntry(entry: RawEntry): RawEntry {
87
+ delete entry.model_messages;
88
+ delete entry.tool_mode;
89
+ delete entry.multi_agent_version;
90
+ delete entry.use_responses_lite;
91
+ delete entry.supports_websockets;
92
+ delete entry.additional_speed_tiers;
93
+ delete entry.service_tier;
94
+ delete entry.service_tiers;
95
+ delete entry.default_service_tier;
96
+ // Routed providers use opencodex sidecars and client-executed tool discovery. The sidecar
97
+ // runs through native gpt-5.4-mini, so image search is available and verbalized for text-only models.
98
+ entry.web_search_tool_type = "text_and_image";
99
+ entry.supports_search_tool = true;
100
+ return ensureAutoCompactTokenLimit(entry);
101
+ }
102
+
103
+ function applyJawcodeCatalogMetadata(entry: RawEntry, slug: string): void {
104
+ const slash = slug.indexOf("/");
105
+ if (slash < 0) return;
106
+ const provider = slug.slice(0, slash);
107
+ const modelId = slug.slice(slash + 1);
108
+ const jawcodeProvider = resolveJawcodeProvider(provider);
109
+ if (!jawcodeProvider) return;
110
+ const meta = getJawcodeModelMetadata(jawcodeProvider, modelId)
111
+ ?? (shouldCaseFoldMetadataModelId(provider) ? getJawcodeModelMetadataCaseInsensitive(jawcodeProvider, modelId) : undefined);
112
+ if (!meta) return;
113
+ if (typeof meta.contextWindow === "number" && meta.contextWindow > 0) {
114
+ entry.context_window = meta.contextWindow;
115
+ entry.max_context_window = meta.contextWindow;
116
+ entry.auto_compact_token_limit = Math.floor(meta.contextWindow * 0.9);
117
+ }
118
+ if (Array.isArray(meta.input) && meta.input.length > 0) {
119
+ entry.input_modalities = meta.input;
120
+ }
121
+ }
122
+
70
123
  function loadCatalogForSync(path: string): { models?: RawEntry[]; [k: string]: unknown } | null {
71
124
  const catalog = readCatalog(path);
72
125
  if (catalog) return catalog;
@@ -130,17 +183,22 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
130
183
  );
131
184
  e.supported_reasoning_levels = ROUTED_REASONING_LEVELS.map(l => byEffort.get(l.effort) ?? { ...l });
132
185
  e.default_reasoning_level = "medium";
186
+ normalizeRoutedCatalogEntry(e);
187
+ applyJawcodeCatalogMetadata(e, slug);
133
188
  }
134
- return normalizeServiceTiers(e);
189
+ return ensureAutoCompactTokenLimit(normalizeServiceTiers(e));
135
190
  }
136
191
  // Fallback when no template is available (best-effort; strict parser may need more).
137
- return normalizeServiceTiers({
192
+ const entry: RawEntry = {
138
193
  slug, display_name: slug, description: desc,
139
194
  default_reasoning_level: "medium",
140
195
  supported_reasoning_levels: ROUTED_REASONING_LEVELS.map(l => ({ ...l })),
141
196
  shell_type: "shell_command", visibility: "list", supported_in_api: true,
142
197
  priority, base_instructions: "You are a helpful coding assistant.",
143
- });
198
+ ...(slug.includes("/") ? { web_search_tool_type: "text_and_image", supports_search_tool: true } : {}),
199
+ };
200
+ applyJawcodeCatalogMetadata(entry, slug);
201
+ return ensureAutoCompactTokenLimit(normalizeServiceTiers(entry));
144
202
  }
145
203
 
146
204
  /**
@@ -148,7 +206,7 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
148
206
  * catalog sync and the proxy `/v1/models?client_version` branch.
149
207
  * Native gpt slugs stay bare; routed models are namespaced `<provider>/<model>`.
150
208
  */
151
- export function buildCatalogEntries(template: RawEntry | null, gptSlugs: string[], goModels: CatalogModel[], featured?: string[]): RawEntry[] {
209
+ export function buildCatalogEntries(template: RawEntry | null, gptSlugs: string[], goModels: CatalogModel[], featured?: string[], wsEnabled = false): RawEntry[] {
152
210
  // Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
153
211
  // models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
154
212
  // ARRAY order is discarded — so "featuring" a model = giving it the LOWEST priority (0..N-1) so
@@ -166,6 +224,13 @@ export function buildCatalogEntries(template: RawEntry | null, gptSlugs: string[
166
224
  if (rank.has(slug)) e.priority = rank.get(slug)!;
167
225
  out.push(e);
168
226
  }
227
+ // Central capability override (phase 120.4): the advertised flag must match the implemented WS
228
+ // endpoint. Overrides both the routed strip (normalizeRoutedCatalogEntry) and any native template
229
+ // leak (deriveEntry clones the template as-is for native slugs).
230
+ for (const entry of out) {
231
+ if (wsEnabled) entry.supports_websockets = true;
232
+ else delete entry.supports_websockets;
233
+ }
169
234
  return out;
170
235
  }
171
236
 
@@ -233,11 +298,28 @@ export async function gatherRoutedModels(config: OcxConfig): Promise<CatalogMode
233
298
  const lists = await Promise.all(
234
299
  Object.entries(config.providers).map(([name, prov]) => fetchProviderModels(name, prov, ttlMs)),
235
300
  );
236
- const all = lists.flat();
301
+ const all = augmentRoutedModelsWithJawcodeMetadata(lists.flat(), Object.keys(config.providers));
237
302
  all.sort((a, b) => (a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)));
238
303
  return all;
239
304
  }
240
305
 
306
+ export function augmentRoutedModelsWithJawcodeMetadata(models: CatalogModel[], providerNames: string[]): CatalogModel[] {
307
+ const out = [...models];
308
+ const seen = new Set(out.map(m => `${m.provider}/${m.id}`));
309
+ for (const provider of providerNames) {
310
+ if (!JAWCODE_CATALOG_AUGMENT_PROVIDERS.has(provider)) continue;
311
+ const jawcodeProvider = resolveJawcodeProvider(provider);
312
+ if (!jawcodeProvider) continue;
313
+ for (const meta of listJawcodeModelMetadata(jawcodeProvider)) {
314
+ const key = `${provider}/${meta.id}`;
315
+ if (seen.has(key)) continue;
316
+ seen.add(key);
317
+ out.push({ provider, id: meta.id, owned_by: provider });
318
+ }
319
+ }
320
+ return out;
321
+ }
322
+
241
323
  /**
242
324
  * Reorder routed models so the configured subagent picks come FIRST (in the chosen order).
243
325
  * Codex's spawn_agent advertises only the first 5 routed catalog entries, so putting the chosen
@@ -283,7 +365,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{ added: num
283
365
  const featured = config.subagentModels ?? [];
284
366
  const rank = new Map(featured.map((slug, i) => [slug, i] as const));
285
367
  const orderedGoModels = orderForSubagents(enabledGo, featured); // stable tie-break among equal priorities
286
- const goEntries = buildCatalogEntries(template ? JSON.parse(JSON.stringify(template)) : null, [], orderedGoModels, featured);
368
+ const goEntries = buildCatalogEntries(template ? JSON.parse(JSON.stringify(template)) : null, [], orderedGoModels, featured, websocketsEnabled(config));
287
369
  // Keep genuine native entries (gpt-*, codex-*) with their real per-model fields, but drop bare
288
370
  // duplicates of routed models (replaced by namespaced entries) + any prior "/" entries. Re-derive
289
371
  // each native's priority from the pristine baseline so featuring a native is reversible.
@@ -296,7 +378,16 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{ added: num
296
378
  const priority = rank.has(slug) ? rank.get(slug)! : (baseline.get(slug) ?? (m.priority as number));
297
379
  return normalizeServiceTiers({ ...m, priority });
298
380
  });
299
- catalog.models = [...native, ...goEntries].map(m => normalizeServiceTiers(m));
381
+ // Central WS capability override on the FINAL on-disk catalog (the file Codex reads). Applies to
382
+ // native AND routed so the advertised flag matches the implemented endpoint (phase 120.4) and a
383
+ // native template can never leak supports_websockets while the flag is off.
384
+ const wsEnabled = websocketsEnabled(config);
385
+ catalog.models = [...native, ...goEntries].map(m => {
386
+ const e = ensureAutoCompactTokenLimit(normalizeServiceTiers(m));
387
+ if (wsEnabled) e.supports_websockets = true;
388
+ else delete e.supports_websockets;
389
+ return e;
390
+ });
300
391
 
301
392
  try {
302
393
  if (!existsSync(OCX_DIR)) mkdirSync(OCX_DIR, { recursive: true });
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { atomicWriteFile } from "./config";
2
+ import { atomicWriteFile, websocketsEnabled } from "./config";
3
3
  import { restoreCodexCatalog } from "./codex-catalog";
4
4
  import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH, DEFAULT_CATALOG_PATH, parseTomlString, readRootTomlString, tomlString } from "./codex-paths";
5
5
  import type { OcxConfig } from "./types";
@@ -14,7 +14,7 @@ const OCX_SECTION_MARKER = "# Auto-injected by opencodex";
14
14
  * whatever `[table]` happened to be open last (e.g. `[plugins."chrome@openai-bundled"]`), so Codex
15
15
  * never saw a global model_provider and silently fell back to the `openai` (ChatGPT) provider.
16
16
  */
17
- function buildProviderTableBlock(port: number): string {
17
+ export function buildProviderTableBlock(port: number, supportsWebsockets = false): string {
18
18
  const lines = [
19
19
  "",
20
20
  OCX_SECTION_MARKER,
@@ -22,7 +22,9 @@ function buildProviderTableBlock(port: number): string {
22
22
  'name = "OpenCodex Proxy"',
23
23
  `base_url = "http://localhost:${port}/v1"`,
24
24
  'wire_api = "responses"',
25
+ "requires_openai_auth = true",
25
26
  ];
27
+ if (supportsWebsockets) lines.push("supports_websockets = true");
26
28
  return lines.join("\n") + "\n";
27
29
  }
28
30
 
@@ -48,6 +50,17 @@ function stripExistingModelProvider(content: string): string {
48
50
  return out.join("\n");
49
51
  }
50
52
 
53
+ function stripRootContextWindowOverrides(content: string): string {
54
+ const lines = content.split("\n");
55
+ const firstTable = lines.findIndex(l => /^\s*\[/.test(l));
56
+ return lines
57
+ .filter((line, i) => {
58
+ const isRoot = firstTable === -1 || i < firstTable;
59
+ return !isRoot || !/^\s*model_(?:context_window|auto_compact_token_limit)\s*=/.test(line);
60
+ })
61
+ .join("\n");
62
+ }
63
+
51
64
  /**
52
65
  * Insert `model_provider = "opencodex"` at the document ROOT — immediately before the first table
53
66
  * header (TOML root keys must precede all tables). If there are no tables, append it to the root body.
@@ -108,6 +121,28 @@ function normalizeServiceTier(content: string): string {
108
121
  return content.replace(/^(\s*service_tier\s*=\s*)["']priority["']\s*$/gm, '$1"fast"');
109
122
  }
110
123
 
124
+ function ensureFastModeFeature(content: string): string {
125
+ const lines = content.split("\n");
126
+ const featuresStart = lines.findIndex(line => line.trim() === "[features]");
127
+ if (featuresStart === -1) {
128
+ return content.trimEnd() + "\n\n[features]\nfast_mode = true\n";
129
+ }
130
+
131
+ const nextTable = lines.findIndex((line, index) => index > featuresStart && /^\s*\[/.test(line));
132
+ const featuresEnd = nextTable === -1 ? lines.length : nextTable;
133
+ for (let i = featuresStart + 1; i < featuresEnd; i++) {
134
+ if (/^\s*fast_mode\s*=/.test(lines[i])) {
135
+ lines[i] = lines[i].replace(/^(\s*)fast_mode\s*=.*$/, "$1fast_mode = true");
136
+ return lines.join("\n");
137
+ }
138
+ }
139
+
140
+ let insertAt = featuresEnd;
141
+ while (insertAt > featuresStart + 1 && lines[insertAt - 1].trim() === "") insertAt--;
142
+ lines.splice(insertAt, 0, "fast_mode = true");
143
+ return lines.join("\n");
144
+ }
145
+
111
146
  function stripDefaultCatalogPath(content: string): string {
112
147
  return content
113
148
  .split("\n")
@@ -125,10 +160,13 @@ function buildProfileFile(port: number, catalogPath: string): string {
125
160
  'model_provider = "opencodex"',
126
161
  `model_catalog_json = ${tomlString(catalogPath)}`,
127
162
  "",
163
+ "[features]",
164
+ "fast_mode = true",
165
+ "",
128
166
  ].join("\n");
129
167
  }
130
168
 
131
- export async function injectCodexConfig(port: number, _config?: OcxConfig): Promise<{ success: boolean; message: string }> {
169
+ export async function injectCodexConfig(port: number, config?: OcxConfig): Promise<{ success: boolean; message: string }> {
132
170
  if (!existsSync(CODEX_CONFIG_PATH)) {
133
171
  return { success: false, message: `Codex config not found at ${CODEX_CONFIG_PATH}. Is Codex installed?` };
134
172
  }
@@ -143,7 +181,9 @@ export async function injectCodexConfig(port: number, _config?: OcxConfig): Prom
143
181
  }
144
182
  content = removeProfileSection(content);
145
183
  content = stripExistingModelProvider(content);
184
+ content = stripRootContextWindowOverrides(content);
146
185
  content = normalizeServiceTier(content);
186
+ content = ensureFastModeFeature(content);
147
187
 
148
188
  const catalogPath = readRootModelCatalogPath(content) ?? DEFAULT_CATALOG_PATH;
149
189
  content = setRootModelCatalogPath(content, catalogPath);
@@ -151,7 +191,7 @@ export async function injectCodexConfig(port: number, _config?: OcxConfig): Prom
151
191
  // 1) Root key BEFORE the first table header (must be a global, not nested under a table).
152
192
  content = setRootModelProvider(content);
153
193
  // 2) Provider table appended at EOF (position-independent).
154
- content = content.trimEnd() + "\n" + buildProviderTableBlock(port);
194
+ content = content.trimEnd() + "\n" + buildProviderTableBlock(port, websocketsEnabled(config ?? {}));
155
195
 
156
196
  writeFileSync(CODEX_CONFIG_PATH, content, "utf-8");
157
197
  writeFileSync(CODEX_PROFILE_PATH, buildProfileFile(port, catalogPath), "utf-8");
@@ -199,6 +239,7 @@ export function stripOpencodexConfig(content: string): string {
199
239
  // Regex (not exact-string) removal so compact `model_provider="opencodex"` is stripped too —
200
240
  // must match the detection regex above, or a detected line could survive un-removed.
201
241
  out = out.split("\n").filter(l => !/^\s*model_provider\s*=\s*"opencodex"\s*$/.test(l)).join("\n");
242
+ out = stripRootContextWindowOverrides(out);
202
243
  out = stripDefaultCatalogPath(out);
203
244
  return out.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
204
245
  }
@@ -215,6 +256,8 @@ export function removeCodexConfig(): { success: boolean; message: string } {
215
256
  const had = hasOpencodexRouting(content);
216
257
  if (had) {
217
258
  atomicWriteFile(CODEX_CONFIG_PATH, stripOpencodexConfig(content));
259
+ } else if (stripRootContextWindowOverrides(content) !== content) {
260
+ atomicWriteFile(CODEX_CONFIG_PATH, stripOpencodexConfig(content));
218
261
  }
219
262
  if (existsSync(CODEX_PROFILE_PATH)) unlinkSync(CODEX_PROFILE_PATH);
220
263
  return {
package/src/config.ts CHANGED
@@ -57,6 +57,10 @@ export function saveConfig(config: OcxConfig): void {
57
57
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
58
58
  }
59
59
 
60
+ export function websocketsEnabled(config: Pick<OcxConfig, "websockets">): boolean {
61
+ return config.websockets === true;
62
+ }
63
+
60
64
  export function getDefaultConfig(): OcxConfig {
61
65
  // Fresh-install default: works out of the box with Codex's ChatGPT OAuth (no API key).
62
66
  // gpt-* requests forward the caller's incoming OAuth headers to the ChatGPT backend.
@@ -72,6 +76,7 @@ export function getDefaultConfig(): OcxConfig {
72
76
  },
73
77
  defaultProvider: "openai",
74
78
  subagentModels: [...DEFAULT_SUBAGENT_MODELS],
79
+ websockets: false,
75
80
  };
76
81
  }
77
82
 
package/src/debug.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Opt-in frame-drop visibility. The streaming path is intentionally quiet (no unconditional
2
+ // console output), so this no-ops unless OCX_DEBUG_FRAMES=1. Lets a malformed/chunk-split
3
+ // upstream frame be detected instead of silently truncating content.
4
+ const DEBUG_FRAMES = process.env.OCX_DEBUG_FRAMES === "1";
5
+
6
+ export function debugDroppedFrame(adapter: string, payload: string): void {
7
+ if (!DEBUG_FRAMES) return;
8
+ const preview = payload.length > 200 ? `${payload.slice(0, 200)}…` : payload;
9
+ console.error(`[ocx:frame-drop] ${adapter}: ${preview}`);
10
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,47 @@
1
+ export interface OcxErrorPayload {
2
+ message: string;
3
+ type: string;
4
+ code: string | null;
5
+ }
6
+
7
+ export function classifyError(status: number, type: string, message: string): OcxErrorPayload {
8
+ const text = message.toLowerCase();
9
+ if (
10
+ text.includes("context_length_exceeded") ||
11
+ text.includes("context window") ||
12
+ text.includes("context length") ||
13
+ text.includes("maximum context") ||
14
+ text.includes("too many tokens")
15
+ ) {
16
+ return { message, type: "invalid_request_error", code: "context_length_exceeded" };
17
+ }
18
+ if (
19
+ text.includes("insufficient_quota") ||
20
+ text.includes("exceeded your current quota")
21
+ ) {
22
+ return { message, type: "insufficient_quota", code: "insufficient_quota" };
23
+ }
24
+ if (status === 429 || text.includes("rate limit") || text.includes("too many requests")) {
25
+ return { message, type: "rate_limit_error", code: "rate_limit_exceeded" };
26
+ }
27
+ if (status === 401 || status === 403 || type === "authentication_error") {
28
+ return { message, type: "authentication_error", code: "invalid_api_key" };
29
+ }
30
+ if (
31
+ status === 503 ||
32
+ text.includes("overloaded") ||
33
+ text.includes("server is busy") ||
34
+ text.includes("temporarily unavailable")
35
+ ) {
36
+ // Codex recognizes "server_is_overloaded" and applies retry-after backoff
37
+ // (responses.rs is_server_overloaded_error); generic "upstream_server_error" is not recognized.
38
+ return { message, type: "server_error", code: "server_is_overloaded" };
39
+ }
40
+ if (status >= 500) {
41
+ return { message, type: "server_error", code: "upstream_server_error" };
42
+ }
43
+ if (status === 400 || type === "invalid_request_error") {
44
+ return { message, type: "invalid_request_error", code: "invalid_request_error" };
45
+ }
46
+ return { message, type, code: type || null };
47
+ }
@@ -0,0 +1,69 @@
1
+ /* eslint-disable */
2
+ // Generated by scripts/generate-jawcode-metadata.ts. Do not edit by hand.
3
+
4
+ export interface JawcodeModelMetadata {
5
+ provider: string;
6
+ id: string;
7
+ contextWindow?: number;
8
+ maxTokens?: number;
9
+ input?: ("text" | "image")[];
10
+ reasoning?: boolean;
11
+ wireModelId?: string;
12
+ }
13
+
14
+ const PROVIDER_ALIASES: Record<string, string> = {
15
+ "xai": "xai",
16
+ "anthropic": "anthropic",
17
+ "kimi": "moonshot",
18
+ "opencode-go": "opencode-go",
19
+ "openrouter": "openrouter",
20
+ "google": "google",
21
+ "gemini": "google",
22
+ "moonshot": "moonshot",
23
+ "minimax": "minimax",
24
+ "minimax-cn": "minimax"
25
+ } as const;
26
+
27
+ type Row = readonly [id: string, contextWindow?: number, maxTokens?: number, input?: string, reasoning?: 0 | 1, wireModelId?: string];
28
+ const DATA: Record<string, readonly Row[]> = {
29
+ "anthropic": [["claude-3-5-sonnet-20240620",200000,8192,"text,image",0],["claude-3-5-sonnet-20241022",200000,8192,"text,image",0],["claude-3-haiku-20240307",200000,4096,"text,image",0],["claude-fable-5",1000000,128000,"text,image",1],["claude-haiku-4-5",200000,64000,"text,image",1],["claude-haiku-4-5-20251001",200000,64000,"text,image",1],["claude-opus-4-0",200000,32000,"text,image",1],["claude-opus-4-1",200000,32000,"text,image",1],["claude-opus-4-1-20250805",200000,32000,"text,image",1],["claude-opus-4-20250514",200000,32000,"text,image",1],["claude-opus-4-5",200000,64000,"text,image",1],["claude-opus-4-5-20251101",200000,64000,"text,image",1],["claude-opus-4-6",1000000,128000,"text,image",1],["claude-opus-4-6[1m]",1000000,128000,"text,image",1,"claude-opus-4-6"],["claude-opus-4-7",1000000,128000,"text,image",1],["claude-opus-4-7[1m]",1000000,128000,"text,image",1,"claude-opus-4-7"],["claude-opus-4-8",1000000,128000,"text,image",1],["claude-opus-4-8[1m]",1000000,128000,"text,image",1,"claude-opus-4-8"],["claude-sonnet-4-0",200000,64000,"text,image",1],["claude-sonnet-4-20250514",200000,64000,"text,image",1],["claude-sonnet-4-5",200000,64000,"text,image",1],["claude-sonnet-4-5-20250929",200000,64000,"text,image",1],["claude-sonnet-4-6",200000,64000,"text,image",1],["claude-sonnet-4-6[1m]",200000,64000,"text,image",1,"claude-sonnet-4-6"]],
30
+ "google": [["gemini-1.5-flash",1000000,8192,"text,image",0],["gemini-1.5-flash-8b",1000000,8192,"text,image",0],["gemini-1.5-pro",1000000,8192,"text,image",0],["gemini-2.0-flash",1048576,8192,"text,image",0],["gemini-2.0-flash-lite",1048576,8192,"text,image",0],["gemini-2.5-flash",1048576,65536,"text,image",1],["gemini-2.5-flash-lite",1048576,65536,"text,image",1],["gemini-2.5-flash-lite-preview-06-17",1048576,65536,"text,image",1],["gemini-2.5-flash-lite-preview-09-2025",1048576,65536,"text,image",1],["gemini-2.5-flash-preview-04-17",1048576,65536,"text,image",1],["gemini-2.5-flash-preview-05-20",1048576,65536,"text,image",1],["gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1],["gemini-2.5-pro",1048576,65536,"text,image",1],["gemini-2.5-pro-preview-05-06",1048576,65536,"text,image",1],["gemini-2.5-pro-preview-06-05",1048576,65536,"text,image",1],["gemini-3-flash-preview",1048576,65536,"text,image",1],["gemini-3-pro-preview",1048576,65536,"text,image",1],["gemini-3.1-flash-lite",1048576,65536,"text,image",1],["gemini-3.1-flash-lite-preview",1048576,65536,"text,image",1],["gemini-3.1-pro-preview",1048576,65536,"text,image",1],["gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1],["gemini-3.5-flash",1048576,65536,"text,image",1],["gemini-flash-latest",1048576,65536,"text,image",1],["gemini-flash-lite-latest",1048576,65536,"text,image",1],["gemini-live-2.5-flash",128000,8000,"text,image",1],["gemini-live-2.5-flash-preview-native-audio",131072,65536,"text",1],["gemma-3-27b-it",131072,8192,"text,image",0],["gemma-4-26b",256000,8192,"text,image",1],["gemma-4-26b-a4b-it",262144,32768,"text,image",1],["gemma-4-26b-it",256000,8192,"text,image",1],["gemma-4-31b",256000,8192,"text,image",1],["gemma-4-31b-it",262144,32768,"text,image",1],["gemma-4-E2B-it",131072,8192,"text,image",1],["gemma-4-E4B-it",131072,8192,"text,image",1]],
31
+ "minimax": [["MiniMax-M2",196608,128000,"text",1],["MiniMax-M2.1",204800,131072,"text",1],["MiniMax-M2.5",204800,131072,"text",1],["MiniMax-M2.5-highspeed",204800,131072,"text",1],["MiniMax-M2.5-lightning",204800,32000,"text",1],["MiniMax-M2.7",204800,131072,"text",1],["MiniMax-M2.7-highspeed",204800,131072,"text",1],["minimax-m3",512000,128000,"text,image",1],["MiniMax-M3",512000,128000,"text,image",1]],
32
+ "moonshot": [["kimi-k2.5",262144,65536,"text,image",1]],
33
+ "opencode-go": [["deepseek-v4-flash",1000000,384000,"text",1],["deepseek-v4-pro",1000000,384000,"text",1],["glm-5",204800,131072,"text",1],["glm-5.1",200000,131072,"text",1],["glm-5.2",1000000,131072,"text",1],["hy3-preview",256000,64000,"text",1],["kimi-k2.5",262144,262144,"text,image",1],["kimi-k2.6",262144,262144,"text,image",1],["kimi-k2.7-code",262144,262144,"text,image",1],["mimo-v2-omni",262144,131072,"text,image",1],["mimo-v2-pro",1048576,131072,"text",1],["mimo-v2.5",1048576,131072,"text,image",1],["mimo-v2.5-pro",1048576,131072,"text",1],["minimax-m2.5",204800,131072,"text",1],["minimax-m2.7",204800,131072,"text",1],["minimax-m3",512000,128000,"text,image",1],["qwen3.5-plus",1000000,65536,"text,image",1],["qwen3.6-plus",1000000,65536,"text,image",1],["qwen3.7-max",1000000,65536,"text",1],["qwen3.7-plus",1000000,64000,"text,image",1]],
34
+ "openrouter": [["~anthropic/claude-fable-latest",1000000,128000,"text,image",1],["~anthropic/claude-haiku-latest",200000,64000,"text,image",1],["~anthropic/claude-opus-latest",1000000,128000,"text,image",1],["~anthropic/claude-sonnet-latest",1000000,128000,"text,image",1],["~google/gemini-flash-latest",1048576,65536,"text,image",1],["~google/gemini-pro-latest",1048576,65536,"text,image",1],["~moonshotai/kimi-latest",262144,262144,"text,image",1],["~openai/gpt-latest",1050000,128000,"text,image",1],["~openai/gpt-mini-latest",400000,128000,"text,image",1],["ai21/jamba-large-1.7",256000,4096,"text",0],["alibaba/tongyi-deepresearch-30b-a3b",131072,131072,"text",1],["allenai/olmo-3.1-32b-instruct",65536,16384,"text",0],["amazon/nova-2-lite-v1",1000000,65535,"text,image",1],["amazon/nova-lite-v1",300000,5120,"text,image",0],["amazon/nova-micro-v1",128000,5120,"text",0],["amazon/nova-premier-v1",1000000,32000,"text,image",0],["amazon/nova-pro-v1",300000,5120,"text,image",0],["anthropic/claude-3-haiku",200000,4096,"text,image",0],["anthropic/claude-3.5-haiku",200000,8192,"text,image",0],["anthropic/claude-3.5-sonnet",200000,8192,"text,image",0],["anthropic/claude-3.7-sonnet",200000,128000,"text,image",1],["anthropic/claude-3.7-sonnet:thinking",200000,64000,"text,image",1],["anthropic/claude-fable-5",1000000,128000,"text,image",1],["anthropic/claude-haiku-4.5",200000,64000,"text,image",0],["anthropic/claude-opus-4",200000,32000,"text,image",1],["anthropic/claude-opus-4.1",200000,32000,"text,image",1],["anthropic/claude-opus-4.5",200000,64000,"text,image",1],["anthropic/claude-opus-4.6",1000000,128000,"text,image",1],["anthropic/claude-opus-4.6-fast",1000000,128000,"text,image",1],["anthropic/claude-opus-4.7",1000000,128000,"text,image",1],["anthropic/claude-opus-4.7-fast",1000000,128000,"text,image",1],["anthropic/claude-opus-4.8",1000000,128000,"text,image",1],["anthropic/claude-opus-4.8-fast",1000000,128000,"text,image",1],["anthropic/claude-sonnet-4",1000000,64000,"text,image",1],["anthropic/claude-sonnet-4.5",1000000,64000,"text,image",1],["anthropic/claude-sonnet-4.6",1000000,128000,"text,image",1],["arcee-ai/trinity-large-preview",131000,8888,"text",0],["arcee-ai/trinity-large-preview:free",131000,8888,"text",0],["arcee-ai/trinity-large-thinking",262144,262144,"text",1],["arcee-ai/trinity-large-thinking:free",262144,80000,"text",1],["arcee-ai/trinity-mini",131072,131072,"text",1],["arcee-ai/trinity-mini:free",131072,8888,"text",1],["arcee-ai/virtuoso-large",131072,64000,"text",0],["auto",2000000,30000,"text,image",1],["baidu/cobuddy:free",131072,65536,"text",1],["baidu/ernie-4.5-21b-a3b",131072,8000,"text",0],["baidu/ernie-4.5-vl-28b-a3b",131072,8000,"text,image",1],["bytedance-seed/seed-1.6",262144,32768,"text,image",1],["bytedance-seed/seed-1.6-flash",262144,32768,"text,image",1],["bytedance-seed/seed-2.0-lite",262144,131072,"text,image",1],["bytedance-seed/seed-2.0-mini",262144,131072,"text,image",1],["cohere/command-r-08-2024",128000,4000,"text",0],["cohere/command-r-plus-08-2024",128000,4000,"text",0],["cohere/north-mini-code:free",256000,64000,"text",1],["deepseek/deepseek-chat",131072,16000,"text",0],["deepseek/deepseek-chat-v3-0324",163840,16384,"text",1],["deepseek/deepseek-chat-v3.1",163840,32768,"text",1],["deepseek/deepseek-r1",163840,16000,"text",1],["deepseek/deepseek-r1-0528",163840,32768,"text",1],["deepseek/deepseek-v3.1-terminus",163840,32768,"text",1],["deepseek/deepseek-v3.1-terminus:exacto",163840,8888,"text",1],["deepseek/deepseek-v3.2",131072,64000,"text",1],["deepseek/deepseek-v3.2-exp",163840,65536,"text",1],["deepseek/deepseek-v4-flash",1048576,65536,"text",1],["deepseek/deepseek-v4-flash:free",1048576,384000,"text",1],["deepseek/deepseek-v4-pro",1048576,384000,"text",1],["essentialai/rnj-1-instruct",32768,8888,"text",0],["google/gemini-2.0-flash-001",1048576,8192,"text,image",0],["google/gemini-2.0-flash-lite-001",1048576,8192,"text,image",0],["google/gemini-2.5-flash",1048576,65535,"text,image",1],["google/gemini-2.5-flash-lite",1048576,65535,"text,image",0],["google/gemini-2.5-flash-lite-preview-09-2025",1048576,65535,"text,image",1],["google/gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1],["google/gemini-2.5-pro",1048576,65536,"text,image",1],["google/gemini-2.5-pro-preview",1048576,65536,"text,image",1],["google/gemini-2.5-pro-preview-05-06",1048576,65535,"text,image",1],["google/gemini-3-flash-preview",1048576,65535,"text,image",1],["google/gemini-3-pro-image",65536,32768,"text,image",1],["google/gemini-3-pro-preview",1048000,64000,"text,image",1],["google/gemini-3.1-flash-lite",1048576,65536,"text,image",1],["google/gemini-3.1-flash-lite-preview",1048576,65536,"text,image",0],["google/gemini-3.1-pro-preview",1048576,65536,"text,image",1],["google/gemini-3.1-pro-preview-customtools",1048756,65536,"text,image",1],["google/gemini-3.5-flash",1048576,65536,"text,image",1],["google/gemma-3-12b-it",131072,16384,"text,image",0],["google/gemma-3-27b-it",131072,16384,"text,image",1],["google/gemma-3-27b-it:free",131072,8192,"text,image",0],["google/gemma-4-26b-a4b-it",262144,8888,"text,image",1],["google/gemma-4-26b-a4b-it:free",262144,32768,"text,image",1],["google/gemma-4-31b-it",262144,262144,"text,image",1],["google/gemma-4-31b-it:free",262144,8192,"text,image",1],["ibm-granite/granite-4.1-8b",131072,131072,"text",0],["inception/mercury",128000,32000,"text",0],["inception/mercury-2",128000,50000,"text",1],["inception/mercury-coder",128000,32000,"text",0],["inclusionai/ling-2.6-1t",262144,32768,"text",0],["inclusionai/ling-2.6-1t:free",262144,32768,"text",0],["inclusionai/ling-2.6-flash",262144,32768,"text",0],["inclusionai/ling-2.6-flash:free",262144,32768,"text",0],["inclusionai/ring-2.6-1t",262144,65536,"text",1],["inclusionai/ring-2.6-1t:free",262144,65536,"text",1],["kwaipilot/kat-coder-pro",256000,128000,"text",0],["kwaipilot/kat-coder-pro-v2",256000,80000,"text",0],["liquid/lfm-2.5-1.2b-thinking:free",32768,8888,"text",1],["meituan/longcat-flash-chat",131072,131072,"text",0],["meta-llama/llama-3-8b-instruct",8192,16384,"text",0],["meta-llama/llama-3.1-405b-instruct",131000,8888,"text",0],["meta-llama/llama-3.1-70b-instruct",131072,16384,"text",0],["meta-llama/llama-3.1-8b-instruct",131072,16384,"text",0],["meta-llama/llama-3.3-70b-instruct",131072,16384,"text",0],["meta-llama/llama-3.3-70b-instruct:free",131072,8888,"text",0],["meta-llama/llama-4-maverick",1048576,16384,"text,image",0],["meta-llama/llama-4-scout",10000000,16384,"text,image",0],["minimax/minimax-m1",1000000,40000,"text",1],["minimax/minimax-m2",204800,196608,"text",1],["minimax/minimax-m2.1",204800,196608,"text",1],["minimax/minimax-m2.5",204800,196608,"text",1],["minimax/minimax-m2.5:free",262144,8192,"text",1],["minimax/minimax-m2.7",204800,131072,"text",1],["minimax/minimax-m3",1048576,512000,"text,image",1],["mistralai/codestral-2508",256000,8888,"text",0],["mistralai/devstral-2512",262144,8888,"text",0],["mistralai/devstral-medium",131072,8888,"text",0],["mistralai/devstral-small",131072,8888,"text",0],["mistralai/ministral-14b-2512",262144,8888,"text,image",0],["mistralai/ministral-3b-2512",131072,8888,"text,image",0],["mistralai/ministral-8b-2512",262144,8888,"text,image",0],["mistralai/mistral-large",128000,8888,"text",0],["mistralai/mistral-large-2407",131072,8888,"text",0],["mistralai/mistral-large-2411",131072,8888,"text",0],["mistralai/mistral-large-2512",262144,8888,"text,image",0],["mistralai/mistral-medium-3",131072,8888,"text,image",0],["mistralai/mistral-medium-3-5",262144,8888,"text,image",1],["mistralai/mistral-medium-3.1",131072,8888,"text,image",0],["mistralai/mistral-nemo",131072,8888,"text",0],["mistralai/mistral-saba",32768,8888,"text",0],["mistralai/mistral-small-24b-instruct-2501",32768,16384,"text",0],["mistralai/mistral-small-2603",262144,8888,"text,image",1],["mistralai/mistral-small-3.1-24b-instruct",131072,131072,"text,image",0],["mistralai/mistral-small-3.1-24b-instruct:free",128000,8888,"text,image",0],["mistralai/mistral-small-3.2-24b-instruct",128000,16384,"text,image",0],["mistralai/mistral-small-creative",32768,8888,"text",0],["mistralai/mixtral-8x22b-instruct",65536,13108,"text",0],["mistralai/mixtral-8x7b-instruct",32768,16384,"text",0],["mistralai/pixtral-large-2411",131072,8888,"text,image",0],["mistralai/voxtral-small-24b-2507",32000,8888,"text",0],["moonshotai/kimi-k2",131072,32768,"text",0],["moonshotai/kimi-k2-0905",262144,262144,"text",0],["moonshotai/kimi-k2-0905:exacto",262144,8888,"text",0],["moonshotai/kimi-k2-thinking",262144,262144,"text",1],["moonshotai/kimi-k2.5",262144,64000,"text,image",1],["moonshotai/kimi-k2.6",262144,262144,"text,image",1],["moonshotai/kimi-k2.6:free",262144,8888,"text,image",1],["moonshotai/kimi-k2.7-code",262144,262144,"text,image",1],["nex-agi/deepseek-v3.1-nex-n1",131072,163840,"text",0],["nex-agi/nex-n2-pro:free",262144,262144,"text,image",1],["nousresearch/deephermes-3-mistral-24b-preview",32768,32768,"text",1],["nousresearch/hermes-4-70b",131072,131072,"text",1],["nvidia/llama-3.1-nemotron-70b-instruct",131072,16384,"text",0],["nvidia/llama-3.3-nemotron-super-49b-v1.5",131072,16384,"text",1],["nvidia/nemotron-3-nano-30b-a3b",262144,228000,"text",1],["nvidia/nemotron-3-nano-30b-a3b:free",256000,8888,"text",1],["nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",256000,65536,"text,image",1],["nvidia/nemotron-3-super-120b-a12b",1000000,262144,"text",1],["nvidia/nemotron-3-super-120b-a12b:free",1000000,262144,"text",1],["nvidia/nemotron-3-ultra-550b-a55b",1000000,16384,"text",1],["nvidia/nemotron-3-ultra-550b-a55b:free",1000000,65536,"text",1],["nvidia/nemotron-nano-12b-v2-vl:free",128000,128000,"text,image",1],["nvidia/nemotron-nano-9b-v2",131072,16384,"text",1],["nvidia/nemotron-nano-9b-v2:free",128000,8888,"text",1],["openai/gpt-3.5-turbo",16385,4096,"text",0],["openai/gpt-3.5-turbo-0613",4095,4096,"text",0],["openai/gpt-3.5-turbo-16k",16385,4096,"text",0],["openai/gpt-4",8191,8192,"text",0],["openai/gpt-4-0314",8191,4096,"text",0],["openai/gpt-4-1106-preview",128000,4096,"text",0],["openai/gpt-4-turbo",128000,4096,"text,image",0],["openai/gpt-4-turbo-preview",128000,4096,"text",0],["openai/gpt-4.1",1047576,8888,"text,image",0],["openai/gpt-4.1-mini",1047576,32768,"text,image",0],["openai/gpt-4.1-nano",1047576,32768,"text,image",0],["openai/gpt-4o",128000,16384,"text,image",0],["openai/gpt-4o-2024-05-13",128000,4096,"text,image",0],["openai/gpt-4o-2024-08-06",128000,16384,"text,image",0],["openai/gpt-4o-2024-11-20",128000,16384,"text,image",0],["openai/gpt-4o-audio-preview",128000,16384,"text",0],["openai/gpt-4o-mini",128000,16384,"text,image",0],["openai/gpt-4o-mini-2024-07-18",128000,16384,"text,image",0],["openai/gpt-4o:extended",128000,64000,"text,image",0],["openai/gpt-5",400000,128000,"text,image",1],["openai/gpt-5-codex",272000,128000,"text,image",1],["openai/gpt-5-image",400000,128000,"text,image",1],["openai/gpt-5-image-mini",400000,128000,"text,image",1],["openai/gpt-5-mini",400000,128000,"text,image",1],["openai/gpt-5-nano",400000,8888,"text,image",1],["openai/gpt-5-pro",400000,128000,"text,image",1],["openai/gpt-5.1",400000,128000,"text,image",1],["openai/gpt-5.1-chat",128000,32000,"text,image",0],["openai/gpt-5.1-codex",272000,128000,"text,image",1],["openai/gpt-5.1-codex-max",272000,128000,"text,image",1],["openai/gpt-5.1-codex-mini",272000,100000,"text,image",1],["openai/gpt-5.2",400000,128000,"text,image",1],["openai/gpt-5.2-chat",128000,16384,"text,image",0],["openai/gpt-5.2-codex",272000,128000,"text,image",1],["openai/gpt-5.2-pro",400000,128000,"text,image",1],["openai/gpt-5.3-chat",128000,16384,"text",0],["openai/gpt-5.3-codex",272000,128000,"text,image",1],["openai/gpt-5.4",1050000,128000,"text,image",1],["openai/gpt-5.4-mini",400000,128000,"text",0],["openai/gpt-5.4-nano",400000,128000,"text",0],["openai/gpt-5.4-pro",1050000,128000,"text,image",1],["openai/gpt-5.5",1050000,128000,"text,image",1],["openai/gpt-5.5-pro",1050000,128000,"text,image",1],["openai/gpt-audio",128000,16384,"text",0],["openai/gpt-audio-mini",128000,16384,"text",0],["openai/gpt-chat-latest",400000,128000,"text,image",0],["openai/gpt-oss-120b",131072,65536,"text",1],["openai/gpt-oss-120b:exacto",131072,8888,"text",1],["openai/gpt-oss-120b:free",131072,131072,"text",1],["openai/gpt-oss-20b",131072,65536,"text",1],["openai/gpt-oss-20b:free",131072,32768,"text",1],["openai/gpt-oss-safeguard-20b",131072,65536,"text",1],["openai/o1",200000,100000,"text,image",1],["openai/o3",200000,100000,"text,image",1],["openai/o3-deep-research",200000,100000,"text,image",1],["openai/o3-mini",200000,100000,"text",1],["openai/o3-mini-high",200000,100000,"text",1],["openai/o3-pro",200000,100000,"text,image",1],["openai/o4-mini",200000,100000,"text,image",1],["openai/o4-mini-deep-research",200000,100000,"text,image",1],["openai/o4-mini-high",200000,100000,"text,image",1],["openrouter/aurora-alpha",128000,50000,"text",1],["openrouter/auto",2000000,8888,"text,image",1],["openrouter/elephant-alpha",262144,32768,"text",0],["openrouter/free",200000,8888,"text,image",1],["openrouter/healer-alpha",262144,32000,"text,image",1],["openrouter/hunter-alpha",1048576,32000,"text",1],["openrouter/owl-alpha",1048756,262144,"text",0],["poolside/laguna-m.1",262144,32768,"text",1],["poolside/laguna-m.1:free",262144,32768,"text",1],["poolside/laguna-xs.2",262144,32768,"text",1],["poolside/laguna-xs.2:free",262144,32768,"text",1],["prime-intellect/intellect-3",131072,131072,"text",1],["qwen/qwen-2.5-72b-instruct",131072,16384,"text",0],["qwen/qwen-2.5-7b-instruct",131072,32768,"text",0],["qwen/qwen-max",32768,8192,"text",0],["qwen/qwen-plus",1000000,32768,"text",0],["qwen/qwen-plus-2025-07-28",1000000,32768,"text",0],["qwen/qwen-plus-2025-07-28:thinking",1000000,32768,"text",1],["qwen/qwen-turbo",131072,8192,"text",0],["qwen/qwen-vl-max",131072,32768,"text,image",0],["qwen/qwen3-14b",131702,40960,"text",1],["qwen/qwen3-235b-a22b",131072,8192,"text",1],["qwen/qwen3-235b-a22b-2507",262144,16384,"text",1],["qwen/qwen3-235b-a22b-thinking-2507",262144,262144,"text",1],["qwen/qwen3-30b-a3b",131072,16384,"text",1],["qwen/qwen3-30b-a3b-instruct-2507",131072,32000,"text",0],["qwen/qwen3-30b-a3b-thinking-2507",131072,131072,"text",1],["qwen/qwen3-32b",131072,16384,"text",1],["qwen/qwen3-4b",131072,8192,"text",1],["qwen/qwen3-4b:free",40960,8888,"text",1],["qwen/qwen3-8b",131072,8192,"text",1],["qwen/qwen3-coder",1048576,65536,"text",0],["qwen/qwen3-coder-30b-a3b-instruct",160000,32768,"text",0],["qwen/qwen3-coder-flash",1000000,65536,"text",0],["qwen/qwen3-coder-next",262144,262144,"text",0],["qwen/qwen3-coder-plus",1000000,65536,"text",0],["qwen/qwen3-coder:exacto",262144,65536,"text",0],["qwen/qwen3-coder:free",1048576,262000,"text",0],["qwen/qwen3-max",262144,32768,"text",1],["qwen/qwen3-max-thinking",262144,32768,"text",1],["qwen/qwen3-next-80b-a3b-instruct",262144,16384,"text",0],["qwen/qwen3-next-80b-a3b-instruct:free",262144,8888,"text",0],["qwen/qwen3-next-80b-a3b-thinking",262144,32768,"text",1],["qwen/qwen3-vl-235b-a22b-instruct",262144,16384,"text,image",0],["qwen/qwen3-vl-235b-a22b-thinking",131072,32768,"text,image",1],["qwen/qwen3-vl-30b-a3b-instruct",262144,32768,"text,image",0],["qwen/qwen3-vl-30b-a3b-thinking",131072,32768,"text,image",1],["qwen/qwen3-vl-32b-instruct",262144,32768,"text,image",0],["qwen/qwen3-vl-8b-instruct",256000,32768,"text,image",0],["qwen/qwen3-vl-8b-thinking",256000,32768,"text,image",1],["qwen/qwen3.5-122b-a10b",262144,262144,"text,image",1],["qwen/qwen3.5-27b",262144,65536,"text,image",1],["qwen/qwen3.5-35b-a3b",262144,262144,"text,image",1],["qwen/qwen3.5-397b-a17b",256000,8192,"text,image",1],["qwen/qwen3.5-9b",262144,262144,"text,image",1],["qwen/qwen3.5-flash-02-23",1000000,65536,"text,image",1],["qwen/qwen3.5-plus-02-15",1000000,65536,"text,image",1],["qwen/qwen3.5-plus-20260420",1000000,65536,"text,image",1],["qwen/qwen3.6-27b",262144,262140,"text,image",1],["qwen/qwen3.6-35b-a3b",262144,262144,"text,image",1],["qwen/qwen3.6-flash",1000000,65536,"text,image",1],["qwen/qwen3.6-max-preview",262144,65536,"text",1],["qwen/qwen3.6-plus",1000000,65536,"text",1],["qwen/qwen3.6-plus-preview:free",1000000,32000,"text",1],["qwen/qwen3.6-plus:free",1000000,65536,"text,image",1],["qwen/qwen3.7-max",1000000,65536,"text",1],["qwen/qwen3.7-plus",1000000,65536,"text,image",1],["qwen/qwq-32b",131072,131072,"text",1],["reka/reka-edge",16384,16384,"text,image",0],["rekaai/reka-edge",16384,16384,"text,image",0],["relace/relace-search",256000,128000,"text",0],["sao10k/l3-euryale-70b",8192,8192,"text",0],["sao10k/l3.1-euryale-70b",131072,16384,"text",0],["stepfun/step-3.5-flash",262144,16384,"text",0],["stepfun/step-3.5-flash:free",256000,256000,"text",1],["stepfun/step-3.7-flash",256000,256000,"text,image",1],["tencent/hy3-preview",262144,262144,"text",1],["tencent/hy3-preview:free",262144,262144,"text",1],["thedrummer/rocinante-12b",32768,32768,"text",0],["thedrummer/unslopnemo-12b",32768,32768,"text",0],["tngtech/deepseek-r1t2-chimera",163840,163840,"text",1],["tngtech/tng-r1t-chimera",163840,65536,"text",1],["upstage/solar-pro-3",128000,8888,"text",1],["upstage/solar-pro-3:free",128000,8888,"text",1],["x-ai/grok-3",131072,8888,"text",0],["x-ai/grok-3-beta",131072,8888,"text",0],["x-ai/grok-3-mini",131072,8888,"text",1],["x-ai/grok-3-mini-beta",131072,8888,"text",1],["x-ai/grok-4",256000,64000,"text,image",1],["x-ai/grok-4-fast",2000000,30000,"text,image",1],["x-ai/grok-4.1-fast",2000000,30000,"text,image",1],["x-ai/grok-4.20",2000000,8888,"text,image",1],["x-ai/grok-4.20-beta",2000000,8888,"text,image",1],["x-ai/grok-4.3",1000000,1000000,"text,image",1],["x-ai/grok-build-0.1",256000,256000,"text,image",1],["x-ai/grok-code-fast-1",256000,10000,"text",1],["xiaomi/mimo-v2-flash",262144,65536,"text",1],["xiaomi/mimo-v2-omni",262144,65536,"text,image",1],["xiaomi/mimo-v2-pro",1048576,131072,"text",1],["xiaomi/mimo-v2.5",1048576,131072,"text,image",1],["xiaomi/mimo-v2.5-pro",1048576,131072,"text",1],["z-ai/glm-4-32b",128000,8888,"text",0],["z-ai/glm-4.5",131072,98304,"text",1],["z-ai/glm-4.5-air",131072,98304,"text",1],["z-ai/glm-4.5-air:free",131072,96000,"text",1],["z-ai/glm-4.5v",65536,16384,"text,image",1],["z-ai/glm-4.6",202752,131072,"text",1],["z-ai/glm-4.6:exacto",204800,131072,"text",1],["z-ai/glm-4.6v",131072,32768,"text,image",1],["z-ai/glm-4.7",202752,131072,"text",1],["z-ai/glm-4.7-flash",202752,16384,"text",1],["z-ai/glm-5",202752,128000,"text",1],["z-ai/glm-5-turbo",262144,131072,"text",1],["z-ai/glm-5.1",202752,131072,"text",1],["z-ai/glm-5.2",1048576,131072,"text",1],["z-ai/glm-5v-turbo",202752,131072,"text,image",1]],
35
+ "xai": [["grok-2",131072,8192,"text",0],["grok-2-1212",131072,8192,"text",0],["grok-2-latest",131072,8192,"text",0],["grok-2-vision",8192,4096,"text,image",0],["grok-2-vision-1212",8192,4096,"text,image",0],["grok-2-vision-latest",8192,4096,"text,image",0],["grok-3",131072,8192,"text",0],["grok-3-fast",131072,8192,"text",0],["grok-3-fast-latest",131072,8192,"text",0],["grok-3-latest",131072,8192,"text",0],["grok-3-mini",131072,8192,"text",1],["grok-3-mini-fast",131072,8192,"text",1],["grok-3-mini-fast-latest",131072,8192,"text",1],["grok-3-mini-latest",131072,8192,"text",1],["grok-4",256000,64000,"text",1],["grok-4-1-fast",2000000,30000,"text,image",1],["grok-4-1-fast-non-reasoning",2000000,30000,"text,image",0],["grok-4-fast",2000000,30000,"text,image",1],["grok-4-fast-non-reasoning",2000000,30000,"text,image",0],["grok-4.20-0309-non-reasoning",1000000,30000,"text,image",0],["grok-4.20-0309-reasoning",1000000,30000,"text,image",1],["grok-4.20-beta-latest-non-reasoning",2000000,30000,"text,image",0],["grok-4.20-beta-latest-reasoning",2000000,30000,"text,image",1],["grok-4.20-multi-agent-beta-latest",2000000,30000,"text,image",1],["grok-4.3",1000000,30000,"text,image",1],["grok-beta",131072,4096,"text",0],["grok-build-0.1",256000,256000,"text,image",1],["grok-code-fast-1",256000,10000,"text",1],["grok-composer-2.5-fast",200000,64000,"text",1],["grok-vision-beta",8192,4096,"text,image",0]],
36
+ };
37
+
38
+ export function resolveJawcodeProvider(provider: string): string | undefined {
39
+ return PROVIDER_ALIASES[provider];
40
+ }
41
+
42
+ export function getJawcodeModelMetadata(provider: string, modelId: string): JawcodeModelMetadata | undefined {
43
+ const row = DATA[provider]?.find(r => r[0] === modelId);
44
+ if (!row) return undefined;
45
+ return rowToMetadata(provider, row);
46
+ }
47
+
48
+ export function getJawcodeModelMetadataCaseInsensitive(provider: string, modelId: string): JawcodeModelMetadata | undefined {
49
+ const lower = modelId.toLowerCase();
50
+ const row = DATA[provider]?.find(r => r[0].toLowerCase() === lower);
51
+ if (!row) return undefined;
52
+ return rowToMetadata(provider, row);
53
+ }
54
+
55
+ export function listJawcodeModelMetadata(provider: string): JawcodeModelMetadata[] {
56
+ return (DATA[provider] ?? []).map(row => rowToMetadata(provider, row));
57
+ }
58
+
59
+ function rowToMetadata(provider: string, row: Row): JawcodeModelMetadata {
60
+ const [id, contextWindow, maxTokens, input, reasoning, wireModelId] = row;
61
+ return {
62
+ provider, id,
63
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
64
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
65
+ ...(input ? { input: input.split(",") as ("text" | "image")[] } : {}),
66
+ ...(reasoning !== undefined ? { reasoning: reasoning === 1 } : {}),
67
+ ...(wireModelId !== undefined ? { wireModelId } : {}),
68
+ };
69
+ }
package/src/init.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as readline from "node:readline";
2
2
  import { injectCodexConfig } from "./codex-inject";
3
3
  import { getDefaultConfig, saveConfig } from "./config";
4
- import { KEY_LOGIN_PROVIDERS, enrichProviderFromCatalog } from "./oauth/key-providers";
5
- import { OAUTH_PROVIDERS } from "./oauth";
4
+ import { enrichProviderFromCatalog } from "./oauth/key-providers";
5
+ import { deriveInitProviders } from "./providers/derive";
6
6
  import type { OcxConfig, OcxProviderConfig } from "./types";
7
7
 
8
8
  function createPrompt(): { ask(question: string): Promise<string>; close(): void } {
@@ -26,39 +26,12 @@ export interface InitProvider {
26
26
  defaultModel?: string;
27
27
  }
28
28
 
29
- const OAUTH_LABELS: Record<string, string> = {
30
- xai: "xAI (Grok)", anthropic: "Anthropic (Claude)", kimi: "Kimi (Moonshot)",
31
- };
32
-
33
29
  /**
34
- * The full CLI provider menu, built from the SAME registries the GUI uses (OAUTH_PROVIDERS +
35
- * KEY_LOGIN_PROVIDERS) plus the ChatGPT-forward, a few non-catalog key providers, and local servers
36
- * so `ocx init` reaches provider parity with the GUI. Exported for verification.
30
+ * The full CLI provider menu, derived from the canonical provider registry so `ocx init`,
31
+ * the GUI picker, key-login catalog, OAuth seeds, and metadata aliases cannot drift.
37
32
  */
38
33
  export function buildInitProviders(): InitProvider[] {
39
- const out: InitProvider[] = [];
40
- // ChatGPT login (no key) — the default forward provider.
41
- out.push({ id: "openai", label: "OpenAI — ChatGPT login (no key)", adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", kind: "forward" });
42
- // Real account logins (OAuth).
43
- for (const id of Object.keys(OAUTH_PROVIDERS)) {
44
- const pc = OAUTH_PROVIDERS[id].providerConfig;
45
- out.push({ id, label: `${OAUTH_LABELS[id] ?? id} — account login`, adapter: pc.adapter, baseUrl: pc.baseUrl, kind: "oauth", defaultModel: pc.defaultModel });
46
- }
47
- // Key providers not in the catalog (native adapters / well-known endpoints).
48
- out.push({ id: "openai-apikey", label: "OpenAI (API key)", adapter: "openai-responses", baseUrl: "https://api.openai.com/v1", kind: "key", dashboardUrl: "https://platform.openai.com/api-keys", defaultModel: "gpt-5.5" });
49
- out.push({ id: "openrouter", label: "OpenRouter", adapter: "openai-chat", baseUrl: "https://openrouter.ai/api/v1", kind: "key", dashboardUrl: "https://openrouter.ai/keys" });
50
- out.push({ id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", kind: "key", dashboardUrl: "https://console.groq.com/keys" });
51
- out.push({ id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", kind: "key", dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro" });
52
- out.push({ id: "azure-openai", label: "Azure OpenAI", adapter: "azure", baseUrl: "https://{resource}.openai.azure.com/openai/deployments/{deployment}", kind: "key", dashboardUrl: "https://portal.azure.com" });
53
- // The full API-key catalog (deepseek, mistral, kilo, minimax, … — same set the GUI shows).
54
- for (const [id, p] of Object.entries(KEY_LOGIN_PROVIDERS)) {
55
- out.push({ id, label: p.label, adapter: p.adapter, baseUrl: p.baseUrl, kind: "key", dashboardUrl: p.dashboardUrl, defaultModel: p.defaultModel });
56
- }
57
- // Local servers (usually no key).
58
- out.push({ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", kind: "local" });
59
- out.push({ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", kind: "local" });
60
- out.push({ id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", kind: "local" });
61
- return out;
34
+ return deriveInitProviders();
62
35
  }
63
36
 
64
37
  const KIND_HEADING: Record<InitKind, string> = {