@bitkyc08/opencodex 1.9.5 → 2.0.1
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.ko.md +95 -71
- package/README.md +94 -46
- package/README.zh-CN.md +101 -70
- package/gui/dist/assets/index-B9nYLpkt.js +9 -0
- package/gui/dist/assets/index-cEIM1XWY.css +1 -0
- package/gui/dist/index.html +13 -3
- package/package.json +1 -3
- package/src/adapters/openai-chat.ts +34 -20
- package/src/bridge.ts +13 -5
- package/src/cli.ts +48 -15
- package/src/codex-catalog.ts +155 -36
- package/src/codex-refresh.ts +49 -0
- package/src/codex-shim.ts +145 -0
- package/src/config.ts +2 -1
- package/src/oauth/index.ts +28 -12
- package/src/oauth/key-providers.ts +27 -0
- package/src/providers/derive.ts +35 -0
- package/src/providers/registry.ts +133 -9
- package/src/reasoning-effort.ts +102 -0
- package/src/responses/parser.ts +1 -1
- package/src/server.ts +33 -17
- package/src/service.ts +26 -2
- package/src/star-prompt.ts +5 -4
- package/src/types.ts +22 -0
- package/src/ws-bridge.ts +5 -2
- package/gui/dist/assets/index-C1wlp1SM.css +0 -1
- package/gui/dist/assets/index-CDhJ0DI7.js +0 -9
- package/scripts/postinstall.mjs +0 -57
package/src/providers/derive.ts
CHANGED
|
@@ -8,8 +8,17 @@ export interface DerivedKeyLoginProvider {
|
|
|
8
8
|
dashboardUrl: string;
|
|
9
9
|
models?: string[];
|
|
10
10
|
defaultModel?: string;
|
|
11
|
+
reasoningEfforts?: string[];
|
|
12
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
13
|
+
reasoningEffortMap?: Record<string, string>;
|
|
14
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
11
15
|
noVisionModels?: string[];
|
|
12
16
|
noReasoningModels?: string[];
|
|
17
|
+
noTemperatureModels?: string[];
|
|
18
|
+
noTopPModels?: string[];
|
|
19
|
+
noPenaltyModels?: string[];
|
|
20
|
+
autoToolChoiceOnlyModels?: string[];
|
|
21
|
+
preserveReasoningContentModels?: string[];
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
export interface DerivedInitProvider {
|
|
@@ -38,6 +47,14 @@ export function listRegistryEntries(): readonly ProviderRegistryEntry[] {
|
|
|
38
47
|
return PROVIDER_REGISTRY;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
function cloneRecordOfArrays(input: Record<string, string[]>): Record<string, string[]> {
|
|
51
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, [...value]]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cloneNestedRecord(input: Record<string, Record<string, string>>): Record<string, Record<string, string>> {
|
|
55
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, { ...value }]));
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderConfig {
|
|
42
59
|
return {
|
|
43
60
|
adapter: entry.adapter,
|
|
@@ -45,8 +62,17 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
45
62
|
authMode: entry.authKind === "local" ? undefined : entry.authKind,
|
|
46
63
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
47
64
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
65
|
+
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
66
|
+
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
67
|
+
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
68
|
+
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
48
69
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
49
70
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
71
|
+
...(entry.noTemperatureModels ? { noTemperatureModels: [...entry.noTemperatureModels] } : {}),
|
|
72
|
+
...(entry.noTopPModels ? { noTopPModels: [...entry.noTopPModels] } : {}),
|
|
73
|
+
...(entry.noPenaltyModels ? { noPenaltyModels: [...entry.noPenaltyModels] } : {}),
|
|
74
|
+
...(entry.autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels: [...entry.autoToolChoiceOnlyModels] } : {}),
|
|
75
|
+
...(entry.preserveReasoningContentModels ? { preserveReasoningContentModels: [...entry.preserveReasoningContentModels] } : {}),
|
|
50
76
|
};
|
|
51
77
|
}
|
|
52
78
|
|
|
@@ -62,8 +88,17 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
|
62
88
|
dashboardUrl: entry.dashboardUrl,
|
|
63
89
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
64
90
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
91
|
+
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
92
|
+
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
93
|
+
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
94
|
+
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
65
95
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
66
96
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
97
|
+
...(entry.noTemperatureModels ? { noTemperatureModels: [...entry.noTemperatureModels] } : {}),
|
|
98
|
+
...(entry.noTopPModels ? { noTopPModels: [...entry.noTopPModels] } : {}),
|
|
99
|
+
...(entry.noPenaltyModels ? { noPenaltyModels: [...entry.noPenaltyModels] } : {}),
|
|
100
|
+
...(entry.autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels: [...entry.autoToolChoiceOnlyModels] } : {}),
|
|
101
|
+
...(entry.preserveReasoningContentModels ? { preserveReasoningContentModels: [...entry.preserveReasoningContentModels] } : {}),
|
|
67
102
|
};
|
|
68
103
|
}
|
|
69
104
|
return out;
|
|
@@ -14,8 +14,17 @@ export interface ProviderRegistryEntry {
|
|
|
14
14
|
dashboardUrl?: string;
|
|
15
15
|
defaultModel?: string;
|
|
16
16
|
models?: string[];
|
|
17
|
+
reasoningEfforts?: string[];
|
|
18
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
19
|
+
reasoningEffortMap?: Record<string, string>;
|
|
20
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
17
21
|
noVisionModels?: string[];
|
|
18
22
|
noReasoningModels?: string[];
|
|
23
|
+
noTemperatureModels?: string[];
|
|
24
|
+
noTopPModels?: string[];
|
|
25
|
+
noPenaltyModels?: string[];
|
|
26
|
+
autoToolChoiceOnlyModels?: string[];
|
|
27
|
+
preserveReasoningContentModels?: string[];
|
|
19
28
|
oauthId?: string;
|
|
20
29
|
jawcodeBundle?: string;
|
|
21
30
|
extraMetadataAliases?: string[];
|
|
@@ -24,9 +33,32 @@ export interface ProviderRegistryEntry {
|
|
|
24
33
|
|
|
25
34
|
export type ProviderConfigSeed = Pick<
|
|
26
35
|
OcxProviderConfig,
|
|
27
|
-
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models"
|
|
36
|
+
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models"
|
|
37
|
+
| "reasoningEfforts" | "modelReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
38
|
+
| "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
|
|
39
|
+
| "autoToolChoiceOnlyModels" | "preserveReasoningContentModels"
|
|
28
40
|
>;
|
|
29
41
|
|
|
42
|
+
|
|
43
|
+
const ZAI_GLM_52_MODELS = ["glm-5.2", "glm-5.2[1m]"];
|
|
44
|
+
const ZAI_GLM_52_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
45
|
+
const ZAI_GLM_52_REASONING_MAP: Record<string, string> = {
|
|
46
|
+
none: "none",
|
|
47
|
+
minimal: "none",
|
|
48
|
+
low: "high",
|
|
49
|
+
medium: "high",
|
|
50
|
+
high: "high",
|
|
51
|
+
xhigh: "max",
|
|
52
|
+
max: "max",
|
|
53
|
+
};
|
|
54
|
+
const KIMI_THINKING_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-k2-0905-preview"];
|
|
55
|
+
const KIMI_LOCKED_PARAMETER_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
|
|
56
|
+
const NEURALWATT_REASONING_HISTORY_MODELS = [
|
|
57
|
+
"glm-5.2",
|
|
58
|
+
"moonshotai/Kimi-K2.5", "kimi-k2.6", "kimi-k2.7-code",
|
|
59
|
+
"qwen3.5-397b", "qwen3.6-35b",
|
|
60
|
+
];
|
|
61
|
+
|
|
30
62
|
export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
31
63
|
{
|
|
32
64
|
id: "openai",
|
|
@@ -75,11 +107,72 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
75
107
|
oauthId: "kimi",
|
|
76
108
|
jawcodeBundle: "moonshot",
|
|
77
109
|
note: "Log in with your Kimi account",
|
|
78
|
-
models: ["kimi-k2.6", "kimi-k2.5"],
|
|
79
|
-
defaultModel: "kimi-k2.
|
|
110
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
|
|
111
|
+
defaultModel: "kimi-k2.7-code",
|
|
112
|
+
// Kimi thinking is controlled by Kimi's `thinking` extension, not OpenAI `reasoning_effort`.
|
|
113
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
114
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
115
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
116
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
117
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
118
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
119
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
80
120
|
},
|
|
81
121
|
{ 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
|
-
{
|
|
122
|
+
{
|
|
123
|
+
id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1",
|
|
124
|
+
authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.7-code",
|
|
125
|
+
jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…",
|
|
126
|
+
modelReasoningEfforts: {
|
|
127
|
+
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
128
|
+
"kimi-k2.7-code": [],
|
|
129
|
+
"kimi-k2.7-code-highspeed": [],
|
|
130
|
+
},
|
|
131
|
+
modelReasoningEffortMap: { "glm-5.2": ZAI_GLM_52_REASONING_MAP },
|
|
132
|
+
noReasoningModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
133
|
+
noTemperatureModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
134
|
+
noTopPModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
135
|
+
noPenaltyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
136
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
137
|
+
preserveReasoningContentModels: ["glm-5.2", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "neuralwatt",
|
|
141
|
+
label: "Neuralwatt Cloud",
|
|
142
|
+
adapter: "openai-chat",
|
|
143
|
+
baseUrl: "https://api.neuralwatt.com/v1",
|
|
144
|
+
authKind: "key",
|
|
145
|
+
dashboardUrl: "https://portal.neuralwatt.com",
|
|
146
|
+
defaultModel: "glm-5.2",
|
|
147
|
+
models: [
|
|
148
|
+
"glm-5.2", "glm-5.2-fast",
|
|
149
|
+
"moonshotai/Kimi-K2.5", "kimi-k2.5-fast", "kimi-k2.6", "kimi-k2.6-fast",
|
|
150
|
+
"kimi-k2.7-code",
|
|
151
|
+
"qwen3.5-397b", "qwen3.5-397b-fast", "qwen3.6-35b", "qwen3.6-35b-fast",
|
|
152
|
+
],
|
|
153
|
+
// Neuralwatt's /v1/models metadata is authoritative; these static hints are the offline fallback.
|
|
154
|
+
modelReasoningEfforts: {
|
|
155
|
+
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
156
|
+
"glm-5.2-fast": [],
|
|
157
|
+
"moonshotai/Kimi-K2.5": [],
|
|
158
|
+
"kimi-k2.5-fast": [],
|
|
159
|
+
"kimi-k2.6": [],
|
|
160
|
+
"kimi-k2.6-fast": [],
|
|
161
|
+
"kimi-k2.7-code": [],
|
|
162
|
+
"qwen3.5-397b": ["low", "medium", "high"],
|
|
163
|
+
"qwen3.5-397b-fast": [],
|
|
164
|
+
"qwen3.6-35b": ["low", "medium", "high"],
|
|
165
|
+
"qwen3.6-35b-fast": [],
|
|
166
|
+
},
|
|
167
|
+
modelReasoningEffortMap: { "glm-5.2": ZAI_GLM_52_REASONING_MAP },
|
|
168
|
+
noReasoningModels: ["glm-5.2-fast", "kimi-k2.5-fast", "kimi-k2.6-fast", "qwen3.5-397b-fast", "qwen3.6-35b-fast"],
|
|
169
|
+
noVisionModels: ["glm-5.2", "glm-5.2-fast", "qwen3.5-397b", "qwen3.5-397b-fast"],
|
|
170
|
+
noTemperatureModels: ["kimi-k2.7-code"],
|
|
171
|
+
noTopPModels: ["kimi-k2.7-code"],
|
|
172
|
+
noPenaltyModels: ["kimi-k2.7-code"],
|
|
173
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code"],
|
|
174
|
+
preserveReasoningContentModels: NEURALWATT_REASONING_HISTORY_MODELS,
|
|
175
|
+
},
|
|
83
176
|
{ 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
177
|
{ 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
178
|
{ 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"] },
|
|
@@ -92,11 +185,31 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
92
185
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
93
186
|
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
94
187
|
{ 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
|
-
{
|
|
188
|
+
{
|
|
189
|
+
id: "moonshot", label: "Moonshot (Kimi API)", baseUrl: "https://api.moonshot.ai/v1", adapter: "openai-chat", authKind: "key",
|
|
190
|
+
dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2.7-code", jawcodeBundle: "moonshot",
|
|
191
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-k2-0905-preview"],
|
|
192
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
193
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
194
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
195
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
196
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
197
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
198
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
199
|
+
},
|
|
96
200
|
{ id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
|
|
97
201
|
{ id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com" },
|
|
98
202
|
{ id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
|
|
99
|
-
{
|
|
203
|
+
{
|
|
204
|
+
id: "zai", label: "Z.AI — GLM Coding Plan", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
|
|
205
|
+
dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-5.2",
|
|
206
|
+
note: "GLM-5.2 coding subscription",
|
|
207
|
+
models: ["glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
|
|
208
|
+
noVisionModels: ZAI_GLM_52_MODELS,
|
|
209
|
+
modelReasoningEfforts: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_EFFORTS])),
|
|
210
|
+
modelReasoningEffortMap: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_MAP])),
|
|
211
|
+
preserveReasoningContentModels: ZAI_GLM_52_MODELS,
|
|
212
|
+
},
|
|
100
213
|
{ id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
|
|
101
214
|
{ id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
|
|
102
215
|
{ id: "qwen-portal", label: "Qwen Portal", baseUrl: "https://portal.qwen.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://portal.qwen.ai" },
|
|
@@ -123,9 +236,20 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
123
236
|
],
|
|
124
237
|
},
|
|
125
238
|
{ 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
|
-
{
|
|
239
|
+
{ id: "minimax", label: "MiniMax — Coding Plan", 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", note: "Subscription Key or API Key" },
|
|
240
|
+
{ id: "minimax-cn", label: "MiniMax — Coding Plan (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", note: "中国区 Subscription Key" },
|
|
241
|
+
{
|
|
242
|
+
id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
|
|
243
|
+
dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
|
|
244
|
+
models: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
|
|
245
|
+
noReasoningModels: KIMI_THINKING_MODELS,
|
|
246
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
247
|
+
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
248
|
+
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
249
|
+
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
250
|
+
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
251
|
+
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
252
|
+
},
|
|
129
253
|
{ id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth" },
|
|
130
254
|
{ 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
255
|
{ id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "./types";
|
|
2
|
+
import { modelInList } from "./types";
|
|
3
|
+
|
|
4
|
+
export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] = [
|
|
5
|
+
{ effort: "low", description: "Fast responses with lighter reasoning" },
|
|
6
|
+
{ effort: "medium", description: "Balances speed and reasoning depth" },
|
|
7
|
+
{ effort: "high", description: "Greater reasoning depth for complex problems" },
|
|
8
|
+
{ effort: "xhigh", description: "Extended reasoning for the hardest problems" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const CODEX_REASONING_ORDER = CODEX_REASONING_LEVELS.map(l => l.effort);
|
|
12
|
+
const CODEX_REASONING_SET = new Set(CODEX_REASONING_ORDER);
|
|
13
|
+
|
|
14
|
+
export function modelRecordValue<T>(record: Record<string, T> | undefined, modelId: string): T | undefined {
|
|
15
|
+
if (!record) return undefined;
|
|
16
|
+
if (Object.prototype.hasOwnProperty.call(record, modelId)) return record[modelId];
|
|
17
|
+
const colon = modelId.indexOf(":");
|
|
18
|
+
if (colon > 0) {
|
|
19
|
+
const family = modelId.slice(0, colon);
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(record, family)) return record[family];
|
|
21
|
+
}
|
|
22
|
+
const folded = modelId.toLowerCase();
|
|
23
|
+
for (const [key, value] of Object.entries(record)) {
|
|
24
|
+
if (key.toLowerCase() === folded) return value;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function sanitizeCodexReasoningEfforts(efforts: readonly string[] | undefined): string[] | undefined {
|
|
30
|
+
if (efforts === undefined) return undefined;
|
|
31
|
+
const seen = new Set<string>();
|
|
32
|
+
const out: string[] = [];
|
|
33
|
+
for (const effort of efforts) {
|
|
34
|
+
if (!CODEX_REASONING_SET.has(effort) || seen.has(effort)) continue;
|
|
35
|
+
seen.add(effort);
|
|
36
|
+
out.push(effort);
|
|
37
|
+
}
|
|
38
|
+
return out.sort((a, b) => CODEX_REASONING_ORDER.indexOf(a) - CODEX_REASONING_ORDER.indexOf(b));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Provider/model configured reasoning levels for the Codex catalog. `undefined` means “no override”,
|
|
43
|
+
* while an empty array means “intentionally expose no effort control for this model”.
|
|
44
|
+
*/
|
|
45
|
+
export function configuredReasoningEfforts(provider: OcxProviderConfig, modelId: string): string[] | undefined {
|
|
46
|
+
if (modelInList(provider.noReasoningModels, modelId)) return [];
|
|
47
|
+
const modelEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId);
|
|
48
|
+
if (modelEfforts !== undefined) return sanitizeCodexReasoningEfforts(modelEfforts) ?? [];
|
|
49
|
+
if (provider.reasoningEfforts !== undefined) return sanitizeCodexReasoningEfforts(provider.reasoningEfforts) ?? [];
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requestToCodexEffort(requested: string): string | undefined {
|
|
54
|
+
if (requested === "none") return undefined;
|
|
55
|
+
if (requested === "minimal") return "low";
|
|
56
|
+
if (requested === "max") return "xhigh";
|
|
57
|
+
return CODEX_REASONING_SET.has(requested) ? requested : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function clampToSupportedCodexEffort(requested: string, supported: readonly string[]): string | undefined {
|
|
61
|
+
if (supported.length === 0) return undefined;
|
|
62
|
+
const codex = requestToCodexEffort(requested);
|
|
63
|
+
if (!codex) return undefined;
|
|
64
|
+
if (supported.includes(codex)) return codex;
|
|
65
|
+
|
|
66
|
+
const requestedRank = CODEX_REASONING_ORDER.indexOf(codex);
|
|
67
|
+
let best = supported[0];
|
|
68
|
+
let bestRank = CODEX_REASONING_ORDER.indexOf(best);
|
|
69
|
+
for (const effort of supported) {
|
|
70
|
+
const rank = CODEX_REASONING_ORDER.indexOf(effort);
|
|
71
|
+
if (rank <= requestedRank && rank >= bestRank) {
|
|
72
|
+
best = effort;
|
|
73
|
+
bestRank = rank;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If every supported tier is above the requested tier, choose the lowest supported tier.
|
|
77
|
+
return best;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function reasoningEffortMapFor(provider: OcxProviderConfig, modelId: string): Record<string, string> | undefined {
|
|
81
|
+
return modelRecordValue(provider.modelReasoningEffortMap, modelId) ?? provider.reasoningEffortMap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Translate Codex's reasoning label into the provider's real wire value. The Codex catalog must only
|
|
86
|
+
* advertise labels Codex itself accepts (`low`/`medium`/`high`/`xhigh`), but some upstreams use
|
|
87
|
+
* different values (`max`) or a smaller subset (`low`/`medium`/`high`).
|
|
88
|
+
*/
|
|
89
|
+
export function mapReasoningEffort(provider: OcxProviderConfig, modelId: string, requested: string | undefined): string | undefined {
|
|
90
|
+
if (!requested) return undefined;
|
|
91
|
+
if (modelInList(provider.noReasoningModels, modelId)) return undefined;
|
|
92
|
+
|
|
93
|
+
const wireMap = reasoningEffortMapFor(provider, modelId);
|
|
94
|
+
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, requested)) return wireMap[requested];
|
|
95
|
+
|
|
96
|
+
const supported = configuredReasoningEfforts(provider, modelId);
|
|
97
|
+
const codexEffort = supported !== undefined ? clampToSupportedCodexEffort(requested, supported) : requestToCodexEffort(requested);
|
|
98
|
+
if (!codexEffort) return undefined;
|
|
99
|
+
|
|
100
|
+
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, codexEffort)) return wireMap[codexEffort];
|
|
101
|
+
return codexEffort;
|
|
102
|
+
}
|
package/src/responses/parser.ts
CHANGED
|
@@ -188,7 +188,7 @@ function findToolNameById(messages: OcxMessage[], callId: string): string {
|
|
|
188
188
|
return "";
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
const REASONING_EFFORTS = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
191
|
+
const REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
192
192
|
|
|
193
193
|
export function parseRequest(body: unknown): OcxParsedRequest {
|
|
194
194
|
const parsed = responsesRequestSchema.safeParse(body);
|
package/src/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
3
|
import { createAnthropicAdapter } from "./adapters/anthropic";
|
|
4
4
|
import { createAzureAdapter } from "./adapters/azure";
|
|
@@ -32,7 +32,15 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "./oauth/key-pr
|
|
|
32
32
|
import { deriveProviderPresets } from "./providers/derive";
|
|
33
33
|
import type { OcxConfig, OcxProviderConfig } from "./types";
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// Single source of truth = package.json (../ from src/), so /healthz + the GUI badge match the
|
|
36
|
+
// installed npm version instead of a stale hardcode.
|
|
37
|
+
const VERSION = (() => {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version as string;
|
|
40
|
+
} catch {
|
|
41
|
+
return "0.0.0";
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
36
44
|
|
|
37
45
|
const MIME_TYPES: Record<string, string> = {
|
|
38
46
|
".html": "text/html", ".js": "application/javascript", ".css": "text/css",
|
|
@@ -354,6 +362,15 @@ function jsonResponse(data: unknown, status = 200): Response {
|
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): Promise<Response | null> {
|
|
365
|
+
async function refreshCodexCatalogBestEffort(): Promise<void> {
|
|
366
|
+
try {
|
|
367
|
+
const { refreshCodexModelCatalog } = await import("./codex-refresh");
|
|
368
|
+
await refreshCodexModelCatalog(config);
|
|
369
|
+
} catch {
|
|
370
|
+
/* catalog absent */
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
357
374
|
if (url.pathname === "/api/config" && req.method === "GET") {
|
|
358
375
|
const safeConfig = JSON.parse(JSON.stringify(config));
|
|
359
376
|
for (const prov of Object.values(safeConfig.providers as Record<string, OcxProviderConfig>)) {
|
|
@@ -398,6 +415,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
398
415
|
config.providers[name] = prov;
|
|
399
416
|
if (body.setDefault) config.defaultProvider = name;
|
|
400
417
|
save(config);
|
|
418
|
+
await refreshCodexCatalogBestEffort();
|
|
401
419
|
return jsonResponse({ success: true, name });
|
|
402
420
|
}
|
|
403
421
|
|
|
@@ -408,11 +426,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
408
426
|
delete config.providers[name];
|
|
409
427
|
save(config);
|
|
410
428
|
// Drop its models from Codex's catalog immediately (re-sync + cache bust) so removal is live.
|
|
411
|
-
|
|
412
|
-
const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
|
|
413
|
-
await syncCatalogModels(config);
|
|
414
|
-
invalidateCodexModelsCache();
|
|
415
|
-
} catch { /* catalog absent */ }
|
|
429
|
+
await refreshCodexCatalogBestEffort();
|
|
416
430
|
return jsonResponse({ success: true });
|
|
417
431
|
}
|
|
418
432
|
|
|
@@ -434,11 +448,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
434
448
|
config.disabledModels = disabled;
|
|
435
449
|
const { saveConfig: save } = await import("./config");
|
|
436
450
|
save(config);
|
|
437
|
-
|
|
438
|
-
const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
|
|
439
|
-
await syncCatalogModels(config);
|
|
440
|
-
invalidateCodexModelsCache();
|
|
441
|
-
} catch { /* catalog absent */ }
|
|
451
|
+
await refreshCodexCatalogBestEffort();
|
|
442
452
|
return jsonResponse({ ok: true, disabled });
|
|
443
453
|
}
|
|
444
454
|
|
|
@@ -479,11 +489,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
479
489
|
config.subagentModels = chosen;
|
|
480
490
|
const { saveConfig: save } = await import("./config");
|
|
481
491
|
save(config);
|
|
482
|
-
|
|
483
|
-
const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
|
|
484
|
-
await syncCatalogModels(config);
|
|
485
|
-
invalidateCodexModelsCache();
|
|
486
|
-
} catch { /* catalog absent */ }
|
|
492
|
+
await refreshCodexCatalogBestEffort();
|
|
487
493
|
return jsonResponse({ ok: true, applied: chosen });
|
|
488
494
|
}
|
|
489
495
|
|
|
@@ -522,6 +528,15 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
522
528
|
return jsonResponse({ success: true });
|
|
523
529
|
}
|
|
524
530
|
|
|
531
|
+
if (url.pathname === "/api/stop" && req.method === "POST") {
|
|
532
|
+
const { restoreNativeCodex } = await import("./codex-inject");
|
|
533
|
+
const { stopServiceIfInstalled } = await import("./service");
|
|
534
|
+
stopServiceIfInstalled();
|
|
535
|
+
restoreNativeCodex();
|
|
536
|
+
setTimeout(() => process.exit(0), 200);
|
|
537
|
+
return jsonResponse({ success: true, message: "Proxy stopping, native Codex restored." });
|
|
538
|
+
}
|
|
539
|
+
|
|
525
540
|
return null;
|
|
526
541
|
}
|
|
527
542
|
|
|
@@ -551,6 +566,7 @@ export function startServer(port?: number) {
|
|
|
551
566
|
|
|
552
567
|
const server = Bun.serve<WsData>({
|
|
553
568
|
port: listenPort,
|
|
569
|
+
idleTimeout: 255,
|
|
554
570
|
async fetch(req) {
|
|
555
571
|
const url = new URL(req.url);
|
|
556
572
|
|
package/src/service.ts
CHANGED
|
@@ -107,9 +107,13 @@ export function buildWindowsServiceScript(): string {
|
|
|
107
107
|
windowsBatchSet("OCX_SERVICE", "1"),
|
|
108
108
|
windowsBatchSet("PATH", path),
|
|
109
109
|
windowsBatchSet("CODEX_HOME", process.env.CODEX_HOME?.trim()),
|
|
110
|
+
":loop",
|
|
110
111
|
`"${bun}" "${cli}" start`,
|
|
111
|
-
"
|
|
112
|
-
"
|
|
112
|
+
"if %ERRORLEVEL% NEQ 0 (",
|
|
113
|
+
" timeout /t 5 /nobreak >nul",
|
|
114
|
+
" goto loop",
|
|
115
|
+
")",
|
|
116
|
+
"endlocal",
|
|
113
117
|
].filter((line): line is string => Boolean(line));
|
|
114
118
|
return `${lines.join("\r\n")}\r\n`;
|
|
115
119
|
}
|
|
@@ -232,6 +236,26 @@ function platformOps(): ServiceOps | null {
|
|
|
232
236
|
return null;
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
/**
|
|
240
|
+
* If a service is installed, stop it so the process manager doesn't respawn after `ocx stop`.
|
|
241
|
+
* Returns true if a service was found and stopped.
|
|
242
|
+
*/
|
|
243
|
+
export function stopServiceIfInstalled(): boolean {
|
|
244
|
+
if (process.platform === "darwin") {
|
|
245
|
+
if (existsSync(plistPath())) {
|
|
246
|
+
try { stopLaunchd(); return true; } catch { return false; }
|
|
247
|
+
}
|
|
248
|
+
} else if (process.platform === "win32") {
|
|
249
|
+
try {
|
|
250
|
+
const q = sh(`schtasks /query /tn ${TASK} 2>nul`);
|
|
251
|
+
if (q.includes(TASK)) { stopWindows(); return true; }
|
|
252
|
+
} catch { /* task not found */ }
|
|
253
|
+
} else if (process.platform === "linux" && isSystemd() && existsSync(unitPath())) {
|
|
254
|
+
try { stopSystemd(); return true; } catch { return false; }
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
235
259
|
export function serviceCommand(sub?: string): void {
|
|
236
260
|
const ops = platformOps();
|
|
237
261
|
if (!ops) {
|
package/src/star-prompt.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { getConfigDir } from "./config";
|
|
6
6
|
|
|
7
7
|
const REPO = "lidge-jun/opencodex";
|
|
8
|
-
/**
|
|
8
|
+
/** Fires exactly once from the first interactive `ocx start`. */
|
|
9
9
|
const MARKER = ".star-prompted";
|
|
10
10
|
|
|
11
11
|
function ghAvailable(): boolean {
|
|
@@ -22,9 +22,10 @@ function starRepo(): { ok: boolean; error?: string } {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* First interactive `ocx start`: a one-time `[Y/n]` "star on GitHub?" prompt.
|
|
26
|
-
* via the user's `gh` auth
|
|
27
|
-
* for non-TTY/piped runs, when already prompted, or when `gh` is
|
|
25
|
+
* First interactive `ocx start`: a one-time `[Y/n]` "star on GitHub?" prompt.
|
|
26
|
+
* On yes, stars the repo via the user's `gh` auth. No-op under the background
|
|
27
|
+
* service, for non-TTY/piped runs, when already prompted, or when `gh` is
|
|
28
|
+
* unavailable. Never throws.
|
|
28
29
|
*/
|
|
29
30
|
export async function maybeShowStarPrompt(): Promise<void> {
|
|
30
31
|
try {
|
package/src/types.ts
CHANGED
|
@@ -221,11 +221,33 @@ export interface OcxProviderConfig {
|
|
|
221
221
|
* Only the openai-responses adapter implements "forward"; openai-chat uses its own key/token.
|
|
222
222
|
*/
|
|
223
223
|
authMode?: "key" | "forward" | "oauth";
|
|
224
|
+
/**
|
|
225
|
+
* Provider-wide Codex-visible reasoning tiers for routed models. Use only Codex-supported labels
|
|
226
|
+
* here (`low`, `medium`, `high`, `xhigh`); translate to provider-specific wire values with
|
|
227
|
+
* `reasoningEffortMap` / `modelReasoningEffortMap` below.
|
|
228
|
+
*/
|
|
229
|
+
reasoningEfforts?: string[];
|
|
230
|
+
/** Model-specific Codex-visible reasoning tiers. An empty array means “do not expose effort”. */
|
|
231
|
+
modelReasoningEfforts?: Record<string, string[]>;
|
|
232
|
+
/** Provider-wide mapping from Codex effort labels to upstream `reasoning_effort` values. */
|
|
233
|
+
reasoningEffortMap?: Record<string, string>;
|
|
234
|
+
/** Model-specific mapping from Codex effort labels to upstream `reasoning_effort` values. */
|
|
235
|
+
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
224
236
|
/**
|
|
225
237
|
* Model ids that do NOT support a reasoning/thinking parameter. The openai-chat adapter drops
|
|
226
238
|
* reasoning_effort for these even when Codex selects a reasoning level (e.g. xAI grok-build-0.1).
|
|
227
239
|
*/
|
|
228
240
|
noReasoningModels?: string[];
|
|
241
|
+
/** Model ids that reject caller-specified temperature. */
|
|
242
|
+
noTemperatureModels?: string[];
|
|
243
|
+
/** Model ids that reject caller-specified top_p. */
|
|
244
|
+
noTopPModels?: string[];
|
|
245
|
+
/** Model ids that reject caller-specified presence/frequency penalty values. */
|
|
246
|
+
noPenaltyModels?: string[];
|
|
247
|
+
/** Model ids whose tool_choice only accepts `auto` or `none`; forced/named choices are downgraded. */
|
|
248
|
+
autoToolChoiceOnlyModels?: string[];
|
|
249
|
+
/** Model ids that expect prior assistant `reasoning_content` to be preserved in chat history. */
|
|
250
|
+
preserveReasoningContentModels?: string[];
|
|
229
251
|
/**
|
|
230
252
|
* Model ids that do NOT accept image inputs. The proxy gives them "eyes" via the vision sidecar:
|
|
231
253
|
* attached images are described by a gpt vision model and replaced with text before the call.
|
package/src/ws-bridge.ts
CHANGED
|
@@ -219,9 +219,12 @@ export function sendResponsesJsonAsEvents(
|
|
|
219
219
|
item,
|
|
220
220
|
});
|
|
221
221
|
});
|
|
222
|
+
const finalStatus = response.status === "failed" || response.status === "incomplete"
|
|
223
|
+
? response.status
|
|
224
|
+
: "completed";
|
|
222
225
|
sendJsonFrame(ws, {
|
|
223
|
-
type: "response.completed",
|
|
224
|
-
response: { ...response, status:
|
|
226
|
+
type: finalStatus === "failed" ? "response.failed" : "response.completed",
|
|
227
|
+
response: { ...response, status: finalStatus },
|
|
225
228
|
});
|
|
226
229
|
}
|
|
227
230
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg:#0b0b0f;--surface:#14141a;--raised:#1c1c25;--raised-hover:#232330;--border:#2a2a35;--border-soft:#20202a;--text:#e9e9ee;--muted:#9a9aa6;--faint:#6a6a76;--accent:#7c5cff;--accent-hover:#9077ff;--accent-ink:#fff;--accent-soft:#7c5cff24;--accent-ring:#7c5cff73;--green:#34d399;--green-soft:#34d39921;--red:#f87171;--red-soft:#f8717121;--amber:#fbbf24;--amber-soft:#fbbf2421;--radius:12px;--radius-sm:8px;--radius-xs:6px;--font:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, "Helvetica Neue", sans-serif;--mono:ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;--shadow:0 1px 2px #00000080, 0 12px 32px #00000047;--shadow-sm:0 1px 2px #0006;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;background-image:radial-gradient(1200px 600px at 18% -10%,#7c5cff14,#0000 60%);margin:0;font-size:14px;line-height:1.5}a{color:var(--accent-hover);text-decoration:none}a:hover{text-decoration:underline}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:-.01em;margin:0;font-weight:650}::selection{background:var(--accent-soft)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--border);border:2px solid var(--bg);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:#353542}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px;border-radius:4px}.app{grid-template-columns:232px 1fr;min-height:100dvh;display:grid}.sidebar{border-right:1px solid var(--border-soft);background:linear-gradient(#ffffff04,#0000);flex-direction:column;align-self:start;gap:4px;height:100dvh;padding:18px 14px;display:flex;position:sticky;top:0}.brand{align-items:center;gap:10px;padding:6px 8px 14px;display:flex}.brand img{width:26px;height:26px}.brand .name{letter-spacing:-.02em;font-size:15px;font-weight:700}.brand .ver{font-family:var(--mono);color:var(--muted);background:var(--raised);border:1px solid var(--border);border-radius:99px;padding:1px 6px;font-size:10px}.nav-item{border-radius:var(--radius-sm);text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;background:0 0;border:none;align-items:center;gap:10px;padding:8px 10px;font-size:13.5px;font-weight:500;transition:background .12s,color .12s;display:flex}.nav-item:hover{background:var(--raised);color:var(--text)}.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--accent)}.sidebar-foot{margin-top:auto;padding-top:12px}.sidebar-link{color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;display:flex}.sidebar-link:hover{background:var(--raised);color:var(--text);text-decoration:none}.sidebar-link svg{width:16px;height:16px}.main{min-width:0}.main-inner{max-width:980px;margin:0 auto;padding:32px 36px 64px}.page-head{justify-content:space-between;align-items:center;gap:16px;margin-bottom:6px;display:flex}.page-head h2{font-size:19px}.page-sub{color:var(--muted);max-width:70ch;margin:4px 0 22px;font-size:13.5px}.page-sub b{color:var(--text);font-weight:600}.btn{border-radius:var(--radius-sm);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:550;transition:background .12s,border-color .12s,opacity .12s;display:inline-flex}.btn svg{width:15px;height:15px}.btn:disabled{opacity:.55;cursor:default}.btn-primary{background:var(--accent);color:var(--accent-ink)}.btn-primary:hover:not(:disabled){background:var(--accent-hover)}.btn-ghost{background:var(--raised);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised-hover)}.btn-danger{color:var(--red);background:0 0;border-color:#f871714d}.btn-danger:hover:not(:disabled){background:var(--red-soft)}.btn-sm{border-radius:var(--radius-xs);padding:4px 9px;font-size:12px}.btn-icon{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:15px 16px}.stat .label{color:var(--muted);align-items:center;gap:6px;margin-bottom:7px;font-size:12px;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:-.02em;font-size:23px;font-weight:700;line-height:1.1}.stat .value.mono{font-family:var(--mono);font-size:19px}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:.01em;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background:#ffffff05}.tbl .num{text-align:right;font-family:var(--mono)}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.input,textarea.input{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);width:100%;color:var(--text);font:inherit;padding:8px 11px;font-size:13px;transition:border-color .12s}.input::placeholder{color:var(--faint)}.input:focus{border-color:var(--accent);outline:none}textarea.input{resize:vertical;font-family:var(--mono);line-height:1.55}.field-label{color:var(--muted);margin-bottom:5px;font-size:12px;font-weight:500;display:block}select.input{appearance:none}.switch{cursor:pointer;background:var(--border);border:none;border-radius:99px;flex-shrink:0;width:34px;height:19px;padding:0;transition:background .15s;position:relative}.switch.on{background:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:#fff;border-radius:50%;width:15px;height:15px;transition:left .15s;position:absolute;top:2px;left:2px}.switch.on .knob{left:17px}.muted{color:var(--muted)}.faint{color:var(--faint)}.row{align-items:center;gap:10px;display:flex}.spread{justify-content:space-between;align-items:center;gap:12px;display:flex}.stack{flex-direction:column;display:flex}.chip{font-family:var(--mono);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text);padding:1px 7px;font-size:12px}.empty{text-align:center;border:1px dashed var(--border);border-radius:var(--radius);color:var(--muted);padding:56px 20px}.empty svg{width:30px;height:30px;color:var(--faint);margin-bottom:12px}.empty .title{color:var(--text);margin-bottom:6px;font-weight:600}.notice{border-radius:var(--radius-sm);align-items:center;gap:8px;margin-bottom:14px;padding:9px 12px;font-size:13px;display:flex}.notice svg{flex-shrink:0;width:15px;height:15px}.notice-ok{background:var(--green-soft);color:var(--green)}.notice-err{background:var(--red-soft);color:var(--red)}.h-section{color:var(--text);align-items:center;gap:8px;margin:30px 0 12px;font-size:13px;font-weight:600;display:flex}.h-section .count{color:var(--muted);font-weight:500;font-family:var(--mono);font-size:12px}.spin{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:14px;height:14px;animation:.7s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}.modal-overlay{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:50;background:#0009;justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);width:100%;max-width:520px;box-shadow:var(--shadow);max-height:84vh;padding:20px;overflow-y:auto}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.list-row{text-align:left;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--raised);cursor:pointer;width:100%;color:var(--text);font:inherit;justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;transition:background .12s,border-color .12s;display:flex}.list-row:hover{background:var(--raised-hover);border-color:#34343f}.list-row .title{font-size:14px;font-weight:600}.list-row .sub{color:var(--muted);margin-top:2px;font-size:12px}.prov-card{justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;display:flex}.link-btn{color:var(--accent-hover);font:inherit;cursor:pointer;background:0 0;border:none;padding:6px 2px;font-size:13px;text-decoration:underline}@media (width<=760px){.app{grid-template-columns:1fr}.sidebar{border-right:none;border-bottom:1px solid var(--border-soft);flex-flow:wrap;align-items:center;height:auto;position:static}.brand{width:100%;padding:6px 8px}.nav-item{width:auto}.sidebar-foot{margin:0;padding:0}.main-inner{padding:22px 18px 48px}.stat-row{grid-template-columns:repeat(2,1fr)}}
|