@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.
- package/README.md +3 -1
- package/gui/dist/assets/{index-Dt5t57MW.js → index-CDhJ0DI7.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +3 -1
- package/src/abort.ts +29 -0
- package/src/adapters/anthropic.ts +15 -5
- package/src/adapters/google.ts +27 -11
- package/src/adapters/openai-chat.ts +38 -12
- package/src/adapters/openai-responses.ts +18 -1
- package/src/bridge.ts +155 -17
- package/src/cli.ts +0 -0
- package/src/codex-catalog.ts +102 -11
- package/src/codex-inject.ts +47 -4
- package/src/config.ts +5 -0
- package/src/debug.ts +10 -0
- package/src/errors.ts +47 -0
- package/src/generated/jawcode-model-metadata.ts +69 -0
- package/src/init.ts +5 -32
- package/src/oauth/index.ts +19 -33
- package/src/oauth/key-providers.ts +2 -63
- package/src/providers/derive.ts +163 -0
- package/src/providers/registry.ts +140 -0
- package/src/responses/parser.ts +6 -1
- package/src/server.ts +182 -9
- package/src/types.ts +6 -0
- package/src/vision/describe.ts +6 -1
- package/src/vision/index.ts +2 -1
- package/src/web-search/executor.ts +6 -1
- package/src/web-search/loop.ts +9 -3
- package/src/ws-bridge.ts +359 -0
package/src/oauth/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getCredential, saveCredential } from "./store";
|
|
|
5
5
|
import { loginXai, refreshXaiToken } from "./xai";
|
|
6
6
|
import { ANTHROPIC_OAUTH_BETA, loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
7
7
|
import { loginKimi, refreshKimiToken } from "./kimi";
|
|
8
|
+
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
8
9
|
|
|
9
10
|
const REFRESH_SKEW_MS = 60_000;
|
|
10
11
|
|
|
@@ -16,51 +17,36 @@ interface OAuthProviderDef {
|
|
|
16
17
|
defaultModel: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function oauthConfig(id: string): OcxProviderConfig {
|
|
21
|
+
const config = deriveOAuthProviderConfig(id);
|
|
22
|
+
if (!config) throw new Error(`OAuth provider missing from registry: ${id}`);
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function oauthDefaultModel(id: string): string {
|
|
27
|
+
const model = deriveOAuthDefaultModel(id);
|
|
28
|
+
if (!model) throw new Error(`OAuth provider missing default model in registry: ${id}`);
|
|
29
|
+
return model;
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
20
33
|
xai: {
|
|
21
34
|
login: (ctrl) => loginXai(ctrl, { importLocal: "fallback" }),
|
|
22
35
|
refresh: refreshXaiToken,
|
|
23
|
-
providerConfig:
|
|
24
|
-
|
|
25
|
-
baseUrl: "https://api.x.ai/v1",
|
|
26
|
-
authMode: "oauth",
|
|
27
|
-
// Real xAI model ids (verified live via GET api.x.ai/v1/models); the proxy also fetches
|
|
28
|
-
// the live list at sync time, so this is the routing hint / fallback + explicit additions.
|
|
29
|
-
models: ["grok-4.3", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
|
|
30
|
-
defaultModel: "grok-4.3",
|
|
31
|
-
// These don't accept a reasoning/thinking param — never forward reasoning_effort for them.
|
|
32
|
-
noReasoningModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
|
|
33
|
-
// These are text-only (no image input) — the vision sidecar describes images for them.
|
|
34
|
-
noVisionModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
|
|
35
|
-
},
|
|
36
|
-
defaultModel: "grok-4.3",
|
|
36
|
+
providerConfig: oauthConfig("xai"),
|
|
37
|
+
defaultModel: oauthDefaultModel("xai"),
|
|
37
38
|
},
|
|
38
39
|
anthropic: {
|
|
39
40
|
login: (ctrl) => loginAnthropic(ctrl, { importLocal: "fallback" }),
|
|
40
41
|
refresh: refreshAnthropicToken,
|
|
41
|
-
providerConfig:
|
|
42
|
-
|
|
43
|
-
baseUrl: "https://api.anthropic.com",
|
|
44
|
-
authMode: "oauth",
|
|
45
|
-
// Current dateless flagship ids — routing hint / fallback only; the proxy fetches the live
|
|
46
|
-
// list from Anthropic's GET /v1/models at sync time (always-latest), so this just seeds a
|
|
47
|
-
// sane set when that fetch is unavailable.
|
|
48
|
-
models: ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
|
49
|
-
defaultModel: "claude-sonnet-4-6",
|
|
50
|
-
},
|
|
51
|
-
defaultModel: "claude-sonnet-4-6",
|
|
42
|
+
providerConfig: oauthConfig("anthropic"),
|
|
43
|
+
defaultModel: oauthDefaultModel("anthropic"),
|
|
52
44
|
},
|
|
53
45
|
kimi: {
|
|
54
46
|
login: (ctrl) => loginKimi(ctrl),
|
|
55
47
|
refresh: refreshKimiToken,
|
|
56
|
-
providerConfig:
|
|
57
|
-
|
|
58
|
-
baseUrl: "https://api.kimi.com/coding/v1",
|
|
59
|
-
authMode: "oauth",
|
|
60
|
-
models: ["kimi-k2.6", "kimi-k2.5"],
|
|
61
|
-
defaultModel: "kimi-k2.6",
|
|
62
|
-
},
|
|
63
|
-
defaultModel: "kimi-k2.6",
|
|
48
|
+
providerConfig: oauthConfig("kimi"),
|
|
49
|
+
defaultModel: oauthDefaultModel("kimi"),
|
|
64
50
|
},
|
|
65
51
|
};
|
|
66
52
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import { deriveKeyLoginMap } from "../providers/derive";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* API-key "login" providers: not OAuth — the flow opens the provider's dashboard so the user can
|
|
@@ -23,69 +24,7 @@ export interface KeyLoginProvider {
|
|
|
23
24
|
noReasoningModels?: string[];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export const KEY_LOGIN_PROVIDERS: Record<string, KeyLoginProvider> =
|
|
27
|
-
deepseek: { label: "DeepSeek", baseUrl: "https://api.deepseek.com", adapter: "openai-chat", dashboardUrl: "https://platform.deepseek.com/api_keys", models: ["deepseek-chat", "deepseek-reasoner"], defaultModel: "deepseek-chat" },
|
|
28
|
-
cerebras: { label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "llama-3.3-70b" },
|
|
29
|
-
together: { label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
30
|
-
fireworks: { label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
31
|
-
firepass: { label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
32
|
-
moonshot: { label: "Moonshot (Kimi API)", baseUrl: "https://api.moonshot.ai/v1", adapter: "openai-chat", dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2-0905-preview" },
|
|
33
|
-
huggingface: { label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", dashboardUrl: "https://huggingface.co/settings/tokens" },
|
|
34
|
-
nvidia: { label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", dashboardUrl: "https://build.nvidia.com" },
|
|
35
|
-
venice: { label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", dashboardUrl: "https://venice.ai/settings/api" },
|
|
36
|
-
zai: { label: "Z.AI (GLM Coding)", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-4.6" },
|
|
37
|
-
nanogpt: { label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", dashboardUrl: "https://nano-gpt.com/api" },
|
|
38
|
-
synthetic: { label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", dashboardUrl: "https://synthetic.new" },
|
|
39
|
-
"qwen-portal": { label: "Qwen Portal", baseUrl: "https://portal.qwen.ai/v1", adapter: "openai-chat", dashboardUrl: "https://portal.qwen.ai" },
|
|
40
|
-
qianfan: { label: "Qianfan (Baidu)", baseUrl: "https://qianfan.baidubce.com/v2", adapter: "openai-chat", dashboardUrl: "https://console.bce.baidu.com/iam/#/iam/apikey/list" },
|
|
41
|
-
alibaba: { label: "Alibaba Coding Plan", baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1", adapter: "openai-chat", dashboardUrl: "https://dashscope.console.aliyun.com/apiKey" },
|
|
42
|
-
parallel: { label: "Parallel", baseUrl: "https://platform.parallel.ai", adapter: "openai-chat", dashboardUrl: "https://platform.parallel.ai" },
|
|
43
|
-
zenmux: { label: "ZenMux", baseUrl: "https://zenmux.ai/api/v1", adapter: "openai-chat", dashboardUrl: "https://zenmux.ai" },
|
|
44
|
-
litellm: { label: "LiteLLM (self-hosted)", baseUrl: "http://localhost:4000/v1", adapter: "openai-chat", dashboardUrl: "https://docs.litellm.ai/docs/proxy/quick_start" },
|
|
45
|
-
// Ollama Cloud — hosted (not local), OpenAI-compatible at /v1, Bearer key from ollama.com.
|
|
46
|
-
// models/noVisionModels reflect the live ollama.com cloud lineup (the proxy still fetches /v1/models
|
|
47
|
-
// live; this is the seed + the vision/text classification, web-verified against ollama.com search
|
|
48
|
-
// filters). Vision-capable cloud models are EXCLUDED from noVisionModels: kimi-k2.5/.6/.7-code,
|
|
49
|
-
// minimax-m3, gemma3/gemma4, qwen3.5, gemini-3-flash-preview, ministral-3, devstral-small-2,
|
|
50
|
-
// mistral-large-3. gpt-oss is text-only despite a stale third-party list claiming otherwise.
|
|
51
|
-
"ollama-cloud": {
|
|
52
|
-
label: "Ollama Cloud",
|
|
53
|
-
baseUrl: "https://ollama.com/v1",
|
|
54
|
-
adapter: "openai-chat",
|
|
55
|
-
dashboardUrl: "https://ollama.com/settings/keys",
|
|
56
|
-
models: ["glm-5.2", "deepseek-v4-pro", "qwen3-coder", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5", "gemma4"],
|
|
57
|
-
defaultModel: "glm-5.2",
|
|
58
|
-
noVisionModels: [
|
|
59
|
-
"glm-5.2", "glm-5.1", "glm-5", "glm-4.7",
|
|
60
|
-
"minimax-m2.7", "minimax-m2.5", "minimax-m2.1",
|
|
61
|
-
"nemotron-3-ultra", "nemotron-3-super",
|
|
62
|
-
"deepseek-v4-pro", "deepseek-v4-flash",
|
|
63
|
-
"gpt-oss", "qwen3-coder",
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
// ── Brought over from the jawcode provider registry ────────────────────────────────────
|
|
67
|
-
// Real LLM API providers. CLI-agent integrations (cursor, github-copilot, gitlab-duo,
|
|
68
|
-
// google-gemini-cli/antigravity, kilo, opencode, openai-codex) and native-cloud-auth providers
|
|
69
|
-
// (amazon-bedrock, google-vertex) are intentionally excluded. baseUrls are taken from jawcode.
|
|
70
|
-
mistral: { label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
|
|
71
|
-
minimax: { label: "MiniMax", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5" },
|
|
72
|
-
"minimax-cn": { label: "MiniMax (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5" },
|
|
73
|
-
"kimi-code": { label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.5" },
|
|
74
|
-
"opencode-zen": { label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", dashboardUrl: "https://opencode.ai/auth" },
|
|
75
|
-
"vercel-ai-gateway": { label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/v1", adapter: "openai-chat", dashboardUrl: "https://vercel.com/dashboard" },
|
|
76
|
-
// Xiaomi MiMo exposes an Anthropic-compatible endpoint → anthropic adapter (x-api-key).
|
|
77
|
-
xiaomi: { label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
78
|
-
// ── Gateways / multi-model proxies (standard wire; subscription-token auth) ──────────────
|
|
79
|
-
// kilo: single-protocol OpenAI-compatible gateway (443 models). Cloudflare AI Gateway: anthropic
|
|
80
|
-
// wire, URL is a template (fill in your account + gateway). github-copilot & gitlab-duo are
|
|
81
|
-
// multi-model gateways whose models span 3 protocols on ONE host — mapped to their universal
|
|
82
|
-
// OpenAI-compatible endpoint (one wire serves the whole lineup). Both need a Bearer subscription
|
|
83
|
-
// token (not a plain API key), and copilot may need a `User-Agent` header via custom provider config.
|
|
84
|
-
kilo: { label: "Kilo", baseUrl: "https://api.kilo.ai/api/gateway", adapter: "openai-chat", dashboardUrl: "https://kilo.ai" },
|
|
85
|
-
"cloudflare-ai-gateway": { label: "Cloudflare AI Gateway", baseUrl: "https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic", adapter: "anthropic", dashboardUrl: "https://dash.cloudflare.com/?to=/:account/ai/ai-gateway" },
|
|
86
|
-
"github-copilot": { label: "GitHub Copilot", baseUrl: "https://api.githubcopilot.com", adapter: "openai-chat", dashboardUrl: "https://github.com/settings/copilot" },
|
|
87
|
-
"gitlab-duo": { label: "GitLab Duo", baseUrl: "https://cloud.gitlab.com/ai/v1/proxy/openai/v1", adapter: "openai-chat", dashboardUrl: "https://gitlab.com/-/user_settings/personal_access_tokens" },
|
|
88
|
-
};
|
|
27
|
+
export const KEY_LOGIN_PROVIDERS: Record<string, KeyLoginProvider> = deriveKeyLoginMap();
|
|
89
28
|
|
|
90
29
|
/**
|
|
91
30
|
* Copy a key-login catalog entry's seed/classification (`models`, `noVisionModels`,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import { PROVIDER_REGISTRY, type ProviderRegistryEntry } from "./registry";
|
|
3
|
+
|
|
4
|
+
export interface DerivedKeyLoginProvider {
|
|
5
|
+
label: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
adapter: string;
|
|
8
|
+
dashboardUrl: string;
|
|
9
|
+
models?: string[];
|
|
10
|
+
defaultModel?: string;
|
|
11
|
+
noVisionModels?: string[];
|
|
12
|
+
noReasoningModels?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DerivedInitProvider {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
adapter: string;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
kind: "forward" | "oauth" | "key" | "local";
|
|
21
|
+
dashboardUrl?: string;
|
|
22
|
+
defaultModel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DerivedProviderPreset {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
adapter: string;
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
defaultModel?: string;
|
|
31
|
+
auth: "oauth" | "forward" | "key";
|
|
32
|
+
oauthProvider?: string;
|
|
33
|
+
dashboardUrl?: string;
|
|
34
|
+
note?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function listRegistryEntries(): readonly ProviderRegistryEntry[] {
|
|
38
|
+
return PROVIDER_REGISTRY;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderConfig {
|
|
42
|
+
return {
|
|
43
|
+
adapter: entry.adapter,
|
|
44
|
+
baseUrl: entry.baseUrl,
|
|
45
|
+
authMode: entry.authKind === "local" ? undefined : entry.authKind,
|
|
46
|
+
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
47
|
+
...(entry.models ? { models: [...entry.models] } : {}),
|
|
48
|
+
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
49
|
+
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
54
|
+
const out: Record<string, DerivedKeyLoginProvider> = {};
|
|
55
|
+
for (const entry of PROVIDER_REGISTRY) {
|
|
56
|
+
if (entry.authKind !== "key") continue;
|
|
57
|
+
if (!entry.dashboardUrl) throw new Error(`Registry key provider missing dashboardUrl: ${entry.id}`);
|
|
58
|
+
out[entry.id] = {
|
|
59
|
+
label: entry.label,
|
|
60
|
+
baseUrl: entry.baseUrl,
|
|
61
|
+
adapter: entry.adapter,
|
|
62
|
+
dashboardUrl: entry.dashboardUrl,
|
|
63
|
+
...(entry.models ? { models: [...entry.models] } : {}),
|
|
64
|
+
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
65
|
+
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
66
|
+
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function deriveInitProviders(): DerivedInitProvider[] {
|
|
73
|
+
return PROVIDER_REGISTRY.map(entry => ({
|
|
74
|
+
id: entry.id,
|
|
75
|
+
label: formatInitLabel(entry),
|
|
76
|
+
adapter: entry.adapter,
|
|
77
|
+
baseUrl: entry.baseUrl,
|
|
78
|
+
kind: entry.authKind,
|
|
79
|
+
...(entry.dashboardUrl ? { dashboardUrl: entry.dashboardUrl } : {}),
|
|
80
|
+
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function deriveOAuthProviderConfig(id: string): OcxProviderConfig | undefined {
|
|
85
|
+
const entry = PROVIDER_REGISTRY.find(row => row.id === id && row.authKind === "oauth");
|
|
86
|
+
return entry ? providerConfigSeed(entry) : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function deriveOAuthDefaultModel(id: string): string | undefined {
|
|
90
|
+
return PROVIDER_REGISTRY.find(row => row.id === id && row.authKind === "oauth")?.defaultModel;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function deriveOAuthIds(): string[] {
|
|
94
|
+
return PROVIDER_REGISTRY.filter(entry => entry.authKind === "oauth").map(entry => entry.oauthId ?? entry.id);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function deriveProviderPresets(): DerivedProviderPreset[] {
|
|
98
|
+
const presets = PROVIDER_REGISTRY
|
|
99
|
+
.filter(entry => entry.featured || entry.authKind === "key")
|
|
100
|
+
.map(entryToPreset);
|
|
101
|
+
return [...dedupePresets(presets), customPreset()];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function deriveFeaturedProviderIds(): string[] {
|
|
105
|
+
return PROVIDER_REGISTRY.filter(entry => entry.featured).map(entry => entry.id);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function deriveJawcodeAliases(): Record<string, string> {
|
|
109
|
+
const aliases: Record<string, string> = {};
|
|
110
|
+
for (const entry of PROVIDER_REGISTRY) {
|
|
111
|
+
if (!entry.jawcodeBundle) continue;
|
|
112
|
+
aliases[entry.id] = entry.jawcodeBundle;
|
|
113
|
+
for (const alias of entry.extraMetadataAliases ?? []) {
|
|
114
|
+
aliases[alias] = entry.jawcodeBundle;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return aliases;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function shouldCaseFoldMetadataModelId(providerId: string): boolean {
|
|
121
|
+
const entry = PROVIDER_REGISTRY.find(row => row.id === providerId);
|
|
122
|
+
return entry?.metadataModelIdNormalize === "case-insensitive";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function entryToPreset(entry: ProviderRegistryEntry): DerivedProviderPreset {
|
|
126
|
+
return {
|
|
127
|
+
id: entry.id,
|
|
128
|
+
label: entry.label,
|
|
129
|
+
adapter: entry.adapter,
|
|
130
|
+
baseUrl: entry.baseUrl,
|
|
131
|
+
auth: entry.authKind === "forward" ? "forward" : entry.authKind === "oauth" ? "oauth" : "key",
|
|
132
|
+
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
133
|
+
...(entry.authKind === "oauth" ? { oauthProvider: entry.oauthId ?? entry.id } : {}),
|
|
134
|
+
...(entry.dashboardUrl ? { dashboardUrl: entry.dashboardUrl } : {}),
|
|
135
|
+
...(entry.note ? { note: entry.note } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function dedupePresets(presets: DerivedProviderPreset[]): DerivedProviderPreset[] {
|
|
140
|
+
const seen = new Set<string>();
|
|
141
|
+
const out: DerivedProviderPreset[] = [];
|
|
142
|
+
for (const preset of presets) {
|
|
143
|
+
if (seen.has(preset.id)) continue;
|
|
144
|
+
seen.add(preset.id);
|
|
145
|
+
out.push(preset);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function customPreset(): DerivedProviderPreset {
|
|
151
|
+
return { id: "custom", label: "Custom provider", adapter: "openai-chat", baseUrl: "", auth: "key" };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatInitLabel(entry: ProviderRegistryEntry): string {
|
|
155
|
+
if (entry.authKind === "forward") return "OpenAI — ChatGPT login (no key)";
|
|
156
|
+
if (entry.authKind === "oauth") {
|
|
157
|
+
if (entry.id === "xai") return "xAI (Grok) — account login";
|
|
158
|
+
if (entry.id === "anthropic") return "Anthropic (Claude) — account login";
|
|
159
|
+
if (entry.id === "kimi") return "Kimi (Moonshot) — account login";
|
|
160
|
+
return `${entry.label} — account login`;
|
|
161
|
+
}
|
|
162
|
+
return entry.label;
|
|
163
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
export type ProviderAuthKind = "forward" | "oauth" | "key" | "local";
|
|
4
|
+
export type MetadataModelIdNormalize = "case-insensitive";
|
|
5
|
+
|
|
6
|
+
export interface ProviderRegistryEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
adapter: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
authKind: ProviderAuthKind;
|
|
12
|
+
featured?: boolean;
|
|
13
|
+
note?: string;
|
|
14
|
+
dashboardUrl?: string;
|
|
15
|
+
defaultModel?: string;
|
|
16
|
+
models?: string[];
|
|
17
|
+
noVisionModels?: string[];
|
|
18
|
+
noReasoningModels?: string[];
|
|
19
|
+
oauthId?: string;
|
|
20
|
+
jawcodeBundle?: string;
|
|
21
|
+
extraMetadataAliases?: string[];
|
|
22
|
+
metadataModelIdNormalize?: MetadataModelIdNormalize;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ProviderConfigSeed = Pick<
|
|
26
|
+
OcxProviderConfig,
|
|
27
|
+
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models" | "noVisionModels" | "noReasoningModels"
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
31
|
+
{
|
|
32
|
+
id: "openai",
|
|
33
|
+
label: "OpenAI (ChatGPT login)",
|
|
34
|
+
adapter: "openai-responses",
|
|
35
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
36
|
+
authKind: "forward",
|
|
37
|
+
featured: true,
|
|
38
|
+
note: "Uses your codex login — no API key",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "xai",
|
|
42
|
+
label: "xAI Grok",
|
|
43
|
+
adapter: "openai-chat",
|
|
44
|
+
baseUrl: "https://api.x.ai/v1",
|
|
45
|
+
authKind: "oauth",
|
|
46
|
+
featured: true,
|
|
47
|
+
oauthId: "xai",
|
|
48
|
+
jawcodeBundle: "xai",
|
|
49
|
+
note: "Log in with your Grok account",
|
|
50
|
+
models: ["grok-4.3", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
|
|
51
|
+
defaultModel: "grok-4.3",
|
|
52
|
+
noReasoningModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
|
|
53
|
+
noVisionModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "anthropic",
|
|
57
|
+
label: "Anthropic Claude",
|
|
58
|
+
adapter: "anthropic",
|
|
59
|
+
baseUrl: "https://api.anthropic.com",
|
|
60
|
+
authKind: "oauth",
|
|
61
|
+
featured: true,
|
|
62
|
+
oauthId: "anthropic",
|
|
63
|
+
jawcodeBundle: "anthropic",
|
|
64
|
+
note: "Log in with your Claude account",
|
|
65
|
+
models: ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
|
66
|
+
defaultModel: "claude-sonnet-4-6",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "kimi",
|
|
70
|
+
label: "Kimi",
|
|
71
|
+
adapter: "openai-chat",
|
|
72
|
+
baseUrl: "https://api.kimi.com/coding/v1",
|
|
73
|
+
authKind: "oauth",
|
|
74
|
+
featured: true,
|
|
75
|
+
oauthId: "kimi",
|
|
76
|
+
jawcodeBundle: "moonshot",
|
|
77
|
+
note: "Log in with your Kimi account",
|
|
78
|
+
models: ["kimi-k2.6", "kimi-k2.5"],
|
|
79
|
+
defaultModel: "kimi-k2.6",
|
|
80
|
+
},
|
|
81
|
+
{ id: "openai-apikey", label: "OpenAI (API key)", adapter: "openai-responses", baseUrl: "https://api.openai.com/v1", authKind: "key", featured: true, dashboardUrl: "https://platform.openai.com/api-keys", defaultModel: "gpt-5.5" },
|
|
82
|
+
{ id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1", authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.6", jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…" },
|
|
83
|
+
{ id: "openrouter", label: "OpenRouter", adapter: "openai-chat", baseUrl: "https://openrouter.ai/api/v1", authKind: "key", featured: true, dashboardUrl: "https://openrouter.ai/keys", jawcodeBundle: "openrouter" },
|
|
84
|
+
{ id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
|
|
85
|
+
{ id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true, dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro", jawcodeBundle: "google", extraMetadataAliases: ["gemini"] },
|
|
86
|
+
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai/deployments/{deployment}", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
87
|
+
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
88
|
+
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
89
|
+
{ id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", authKind: "local", featured: true, note: "Local — no key needed" },
|
|
90
|
+
{ id: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.deepseek.com/api_keys", models: ["deepseek-chat", "deepseek-reasoner"], defaultModel: "deepseek-chat" },
|
|
91
|
+
{ id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "llama-3.3-70b" },
|
|
92
|
+
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
93
|
+
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
94
|
+
{ id: "firepass", label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
95
|
+
{ id: "moonshot", label: "Moonshot (Kimi API)", baseUrl: "https://api.moonshot.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2-0905-preview", jawcodeBundle: "moonshot" },
|
|
96
|
+
{ id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
|
|
97
|
+
{ id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com" },
|
|
98
|
+
{ id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
|
|
99
|
+
{ id: "zai", label: "Z.AI (GLM Coding)", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-4.6" },
|
|
100
|
+
{ id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
|
|
101
|
+
{ id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
|
|
102
|
+
{ id: "qwen-portal", label: "Qwen Portal", baseUrl: "https://portal.qwen.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://portal.qwen.ai" },
|
|
103
|
+
{ id: "qianfan", label: "Qianfan (Baidu)", baseUrl: "https://qianfan.baidubce.com/v2", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.bce.baidu.com/iam/#/iam/apikey/list" },
|
|
104
|
+
{ id: "alibaba", label: "Alibaba Coding Plan", baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://dashscope.console.aliyun.com/apiKey" },
|
|
105
|
+
{ id: "parallel", label: "Parallel", baseUrl: "https://platform.parallel.ai", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.parallel.ai" },
|
|
106
|
+
{ id: "zenmux", label: "ZenMux", baseUrl: "https://zenmux.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://zenmux.ai" },
|
|
107
|
+
{ id: "litellm", label: "LiteLLM (self-hosted)", baseUrl: "http://localhost:4000/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://docs.litellm.ai/docs/proxy/quick_start" },
|
|
108
|
+
{
|
|
109
|
+
id: "ollama-cloud",
|
|
110
|
+
label: "Ollama Cloud",
|
|
111
|
+
baseUrl: "https://ollama.com/v1",
|
|
112
|
+
adapter: "openai-chat",
|
|
113
|
+
authKind: "key",
|
|
114
|
+
dashboardUrl: "https://ollama.com/settings/keys",
|
|
115
|
+
models: ["glm-5.2", "deepseek-v4-pro", "qwen3-coder", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5", "gemma4"],
|
|
116
|
+
defaultModel: "glm-5.2",
|
|
117
|
+
noVisionModels: [
|
|
118
|
+
"glm-5.2", "glm-5.1", "glm-5", "glm-4.7",
|
|
119
|
+
"minimax-m2.7", "minimax-m2.5", "minimax-m2.1",
|
|
120
|
+
"nemotron-3-ultra", "nemotron-3-super",
|
|
121
|
+
"deepseek-v4-pro", "deepseek-v4-flash",
|
|
122
|
+
"gpt-oss", "qwen3-coder",
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{ id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
|
|
126
|
+
{ id: "minimax", label: "MiniMax", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
|
|
127
|
+
{ id: "minimax-cn", label: "MiniMax (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
|
|
128
|
+
{ id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.5" },
|
|
129
|
+
{ id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth" },
|
|
130
|
+
{ id: "vercel-ai-gateway", label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://vercel.com/dashboard" },
|
|
131
|
+
{ id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
132
|
+
{ id: "kilo", label: "Kilo", baseUrl: "https://api.kilo.ai/api/gateway", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://kilo.ai" },
|
|
133
|
+
{ id: "cloudflare-ai-gateway", label: "Cloudflare AI Gateway", baseUrl: "https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://dash.cloudflare.com/?to=/:account/ai/ai-gateway" },
|
|
134
|
+
{ id: "github-copilot", label: "GitHub Copilot", baseUrl: "https://api.githubcopilot.com", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://github.com/settings/copilot" },
|
|
135
|
+
{ id: "gitlab-duo", label: "GitLab Duo", baseUrl: "https://cloud.gitlab.com/ai/v1/proxy/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://gitlab.com/-/user_settings/personal_access_tokens" },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
export function getProviderRegistryEntry(id: string): ProviderRegistryEntry | undefined {
|
|
139
|
+
return PROVIDER_REGISTRY.find(entry => entry.id === id);
|
|
140
|
+
}
|
package/src/responses/parser.ts
CHANGED
|
@@ -386,7 +386,12 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
386
386
|
const structuredOutput = detectStructuredOutput(data.text);
|
|
387
387
|
|
|
388
388
|
return {
|
|
389
|
-
modelId: data.model,
|
|
389
|
+
modelId: data.model,
|
|
390
|
+
...(data.previous_response_id ? { previousResponseId: data.previous_response_id } : {}),
|
|
391
|
+
context,
|
|
392
|
+
stream: data.stream === true,
|
|
393
|
+
options,
|
|
394
|
+
_rawBody: body,
|
|
390
395
|
...(webSearch ? { _webSearch: webSearch } : {}),
|
|
391
396
|
...(structuredOutput ? { _structuredOutput: true } : {}),
|
|
392
397
|
};
|