@bitkyc08/opencodex 2.6.26-preview.20260705 → 2.6.28-preview.20260707
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 +1 -0
- package/bin/ocx.mjs +4 -4
- package/gui/dist/assets/index-ByGC8-Bm.css +1 -0
- package/gui/dist/assets/index-CkV5xFA8.js +15 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/adapters/anthropic-image-guard.ts +195 -0
- package/src/adapters/anthropic.ts +85 -14
- package/src/adapters/cursor/cursor-errors.ts +2 -2
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/transport-retry.ts +2 -2
- package/src/adapters/google-errors.ts +1 -1
- package/src/adapters/google-http.ts +1 -1
- package/src/adapters/google-truncation.ts +1 -1
- package/src/adapters/google.ts +1 -1
- package/src/adapters/kiro-errors.ts +1 -1
- package/src/adapters/kiro-retry.ts +1 -1
- package/src/adapters/kiro-truncation.ts +1 -1
- package/src/adapters/kiro.ts +1 -1
- package/src/adapters/openai-chat.ts +12 -2
- package/src/adapters/openai-responses.ts +126 -3
- package/src/bridge.ts +164 -7
- package/src/{doctor.ts → cli/doctor.ts} +21 -4
- package/src/{cli-help.ts → cli/help.ts} +1 -1
- package/src/cli/index.ts +584 -0
- package/src/{init.ts → cli/init.ts} +6 -6
- package/src/{cli-models.ts → cli/models.ts} +2 -2
- package/src/{cli-provider.ts → cli/provider.ts} +12 -8
- package/src/{star-prompt.ts → cli/star-prompt.ts} +1 -1
- package/src/{cli-status.ts → cli/status.ts} +7 -7
- package/src/cli.ts +9 -575
- package/src/{codex-account-label.ts → codex/account-label.ts} +1 -1
- package/src/{codex-account-lifecycle.ts → codex/account-lifecycle.ts} +6 -6
- package/src/{codex-account-store.ts → codex/account-store.ts} +2 -2
- package/src/{codex-account-usability.ts → codex/account-usability.ts} +4 -4
- package/src/{codex-auth-api.ts → codex/auth-api.ts} +18 -18
- package/src/{codex-auth-collision.ts → codex/auth-collision.ts} +4 -4
- package/src/{codex-auth-context.ts → codex/auth-context.ts} +9 -9
- package/src/{codex-catalog.ts → codex/catalog.ts} +49 -20
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/{codex-history-provider.ts → codex/history-provider.ts} +111 -7
- package/src/{codex-home.ts → codex/home.ts} +1 -1
- package/src/{codex-inject.ts → codex/inject.ts} +204 -26
- package/src/{codex-journal.ts → codex/journal.ts} +2 -2
- package/src/{codex-main-account.ts → codex/main-account.ts} +2 -2
- package/src/{model-cache.ts → codex/model-cache.ts} +1 -1
- package/src/{codex-paths.ts → codex/paths.ts} +2 -2
- package/src/{codex-plugins-doctor.ts → codex/plugins-doctor.ts} +2 -2
- package/src/{codex-refresh.ts → codex/refresh.ts} +4 -4
- package/src/{codex-routing.ts → codex/routing.ts} +8 -8
- package/src/{codex-shim.ts → codex/shim.ts} +6 -5
- package/src/{codex-sync.ts → codex/sync.ts} +4 -4
- package/src/{codex-websocket-registry.ts → codex/websocket-registry.ts} +1 -1
- package/src/config.ts +2 -0
- package/src/generated/jawcode-model-metadata.ts +2 -0
- package/src/{bun-runtime.ts → lib/bun-runtime.ts} +1 -1
- package/src/{crash-guard.ts → lib/crash-guard.ts} +1 -1
- package/src/{process-control.ts → lib/process-control.ts} +1 -1
- package/src/{service-secrets.ts → lib/service-secrets.ts} +1 -1
- package/src/oauth/callback-server.ts +1 -1
- package/src/oauth/google-antigravity.ts +7 -4
- package/src/oauth/index.ts +67 -16
- package/src/oauth/login-cli.ts +2 -2
- package/src/oauth/store.ts +236 -20
- package/src/oauth/token-guardian.ts +24 -20
- package/src/oauth/types.ts +16 -0
- package/src/providers/api-keys.ts +121 -0
- package/src/{provider-context-cap.ts → providers/context-cap.ts} +1 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/key-failover.ts +145 -0
- package/src/{provider-label.ts → providers/label.ts} +1 -1
- package/src/{provider-quota.ts → providers/quota.ts} +12 -7
- package/src/providers/registry.ts +66 -4
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +89 -13
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +15 -3
- package/src/responses/state.ts +117 -2
- package/src/router.ts +2 -0
- package/src/server/auth-cors.ts +231 -0
- package/src/server/index.ts +523 -0
- package/src/server/lifecycle.ts +73 -0
- package/src/server/management-api.ts +628 -0
- package/src/{proxy-liveness.ts → server/proxy-liveness.ts} +1 -1
- package/src/server/relay.ts +534 -0
- package/src/server/request-decompress.ts +46 -0
- package/src/server/request-log.ts +310 -0
- package/src/server/responses.ts +775 -0
- package/src/{ws-bridge.ts → server/ws-bridge.ts} +44 -13
- package/src/service.ts +19 -11
- package/src/types.ts +27 -0
- package/src/{update.ts → update/index.ts} +5 -5
- package/src/{update-job.ts → update/job.ts} +7 -6
- package/src/{update-notify.ts → update/notify.ts} +3 -3
- package/src/{usage-debug.ts → usage/debug.ts} +3 -3
- package/src/{usage-log.ts → usage/log.ts} +3 -3
- package/src/{usage-summary.ts → usage/summary.ts} +3 -3
- package/src/{usage-totals.ts → usage/totals.ts} +1 -1
- package/src/vision/describe.ts +3 -3
- package/src/vision/index.ts +21 -1
- package/src/web-search/executor.ts +4 -4
- package/src/web-search/index.ts +1 -1
- package/src/web-search/loop.ts +84 -24
- package/gui/dist/assets/index-BcHhxo1I.css +0 -1
- package/gui/dist/assets/index-DCC1q_Jx.js +0 -15
- package/src/server.ts +0 -2501
- /package/src/{codex-account-runtime-state.ts → codex/account-runtime-state.ts} +0 -0
- /package/src/{codex-quota.ts → codex/quota.ts} +0 -0
- /package/src/{abort.ts → lib/abort.ts} +0 -0
- /package/src/{debug.ts → lib/debug.ts} +0 -0
- /package/src/{errors.ts → lib/errors.ts} +0 -0
- /package/src/{open-url.ts → lib/open-url.ts} +0 -0
- /package/src/{privacy.ts → lib/privacy.ts} +0 -0
- /package/src/{redact.ts → lib/redact.ts} +0 -0
- /package/src/{sidecar-tracker.ts → lib/sidecar-tracker.ts} +0 -0
- /package/src/{upstream-retry.ts → lib/upstream-retry.ts} +0 -0
- /package/src/{win-paths.ts → lib/win-paths.ts} +0 -0
- /package/src/{ports.ts → server/ports.ts} +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { formatErrorResponse } from "../bridge";
|
|
3
|
+
import {
|
|
4
|
+
codexAutoStartEnabled,
|
|
5
|
+
providerBaseUrlConfigError,
|
|
6
|
+
providerHeadersConfigError,
|
|
7
|
+
} from "../config";
|
|
8
|
+
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
9
|
+
|
|
10
|
+
let _corsOrigin = "http://localhost:10100";
|
|
11
|
+
export function setCorsOrigin(port: number): void { _corsOrigin = `http://localhost:${port}`; }
|
|
12
|
+
export function configuredPort(): string {
|
|
13
|
+
try { return new URL(_corsOrigin).port; } catch { return "10100"; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseHttpHost(value: string | null): { hostname: string; port: string } | null {
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(`http://${value}`);
|
|
20
|
+
return { hostname: parsed.hostname.toLowerCase(), port: parsed.port };
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isLoopbackRequestHost(value: string | null): boolean {
|
|
27
|
+
const parsed = parseHttpHost(value);
|
|
28
|
+
if (!parsed) return true;
|
|
29
|
+
if (!isLoopbackHostname(parsed.hostname)) return false;
|
|
30
|
+
return parsed.port === "" || parsed.port === configuredPort();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isLoopbackOriginValue(value: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = new URL(value);
|
|
36
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
37
|
+
return isLoopbackHostname(parsed.hostname);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isSameOriginAsRequest(req: Request, origin: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
return origin === new URL(req.url).origin;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean {
|
|
52
|
+
function isExtraAllowedOrigin(origin: string, cfg: OcxConfig): boolean {
|
|
53
|
+
if (!cfg.corsAllowOrigins?.length) return false;
|
|
54
|
+
return cfg.corsAllowOrigins.some(allowed => {
|
|
55
|
+
try {
|
|
56
|
+
return new URL(allowed).origin === new URL(origin).origin;
|
|
57
|
+
} catch {
|
|
58
|
+
return allowed === origin;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const origin = req.headers.get("Origin");
|
|
63
|
+
if (!isApiAuthRequired(config)) {
|
|
64
|
+
if (!isLoopbackRequestHost(req.headers.get("Host"))) return false;
|
|
65
|
+
return !origin || isLoopbackOriginValue(origin) || isExtraAllowedOrigin(origin, config);
|
|
66
|
+
}
|
|
67
|
+
return !origin || isLoopbackOriginValue(origin) || isSameOriginAsRequest(req, origin) || isExtraAllowedOrigin(origin, config);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function corsHeaders(req?: Request, config?: OcxConfig): Record<string, string> {
|
|
71
|
+
const origin = req?.headers.get("Origin");
|
|
72
|
+
const allowOrigin = origin && req && config && isAllowedRequestOrigin(req, config) ? origin : _corsOrigin;
|
|
73
|
+
return {
|
|
74
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
75
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
76
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-OpenCodex-API-Key",
|
|
77
|
+
"Vary": "Origin",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function withCors(response: Response, req: Request, config: OcxConfig): Response {
|
|
82
|
+
const headers = new Headers(response.headers);
|
|
83
|
+
for (const [name, value] of Object.entries(corsHeaders(req, config))) {
|
|
84
|
+
headers.set(name, value);
|
|
85
|
+
}
|
|
86
|
+
return new Response(response.body, {
|
|
87
|
+
status: response.status,
|
|
88
|
+
statusText: response.statusText,
|
|
89
|
+
headers,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function jsonResponse(data: unknown, status = 200, req?: Request, config?: OcxConfig): Response {
|
|
94
|
+
return new Response(JSON.stringify(data), {
|
|
95
|
+
status,
|
|
96
|
+
headers: { "Content-Type": "application/json", ...corsHeaders(req, config) },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function configuredApiAuthToken(_config: OcxConfig): string | undefined {
|
|
101
|
+
const token = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
102
|
+
return token || undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isLoopbackHostname(hostname: string | undefined): boolean {
|
|
106
|
+
const normalized = (hostname ?? "127.0.0.1").trim().toLowerCase();
|
|
107
|
+
return normalized === "" || normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function isApiAuthRequired(config: OcxConfig): boolean {
|
|
111
|
+
return !isLoopbackHostname(config.hostname);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function assertServerAuthConfig(config: OcxConfig): void {
|
|
115
|
+
if (isApiAuthRequired(config) && !configuredApiAuthToken(config)) {
|
|
116
|
+
throw new Error("OPENCODEX_API_AUTH_TOKEN is required when binding opencodex to a non-loopback hostname");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function hasValidApiAuth(req: Request, config: OcxConfig): boolean {
|
|
121
|
+
if (!isApiAuthRequired(config)) return true;
|
|
122
|
+
const actual = req.headers.get("x-opencodex-api-key")?.trim()
|
|
123
|
+
|| req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
124
|
+
if (!actual) return false;
|
|
125
|
+
// Check env-based token
|
|
126
|
+
const expected = configuredApiAuthToken(config);
|
|
127
|
+
if (expected) {
|
|
128
|
+
const enc = new TextEncoder();
|
|
129
|
+
const expectedBytes = enc.encode(expected);
|
|
130
|
+
const actualBytes = enc.encode(actual);
|
|
131
|
+
if (expectedBytes.length === actualBytes.length && timingSafeEqual(actualBytes, expectedBytes)) return true;
|
|
132
|
+
}
|
|
133
|
+
// Check config-based API keys
|
|
134
|
+
if (config.apiKeys?.length) {
|
|
135
|
+
const enc = new TextEncoder();
|
|
136
|
+
const actualBytes = enc.encode(actual);
|
|
137
|
+
for (const k of config.apiKeys) {
|
|
138
|
+
const keyBytes = enc.encode(k.key);
|
|
139
|
+
if (keyBytes.length === actualBytes.length && timingSafeEqual(actualBytes, keyBytes)) return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function requireApiAuth(req: Request, config: OcxConfig, kind: "management" | "data-plane"): Response | null {
|
|
146
|
+
if (hasValidApiAuth(req, config)) return null;
|
|
147
|
+
if (kind === "management") return jsonResponse({ error: "opencodex API key required" }, 401);
|
|
148
|
+
return formatErrorResponse(401, "authentication_error", "opencodex API key required");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function providerManagementConfigError(name: string, provider: OcxProviderConfig): string | null {
|
|
152
|
+
const baseUrlError = providerBaseUrlConfigError(provider.baseUrl);
|
|
153
|
+
if (baseUrlError) return `provider ${name} ${baseUrlError}`;
|
|
154
|
+
const headersError = providerHeadersConfigError(provider.headers);
|
|
155
|
+
if (headersError) return `provider ${name} ${headersError}`;
|
|
156
|
+
if (provider.authMode === "forward") {
|
|
157
|
+
const normalizedName = name.trim().toLowerCase();
|
|
158
|
+
const base = provider.baseUrl.replace(/\/+$/, "");
|
|
159
|
+
const isBuiltInChatGptForward = (normalizedName === "openai" || normalizedName === "chatgpt")
|
|
160
|
+
&& provider.adapter === "openai-responses"
|
|
161
|
+
&& base === "https://chatgpt.com/backend-api/codex";
|
|
162
|
+
if (isBuiltInChatGptForward) return null;
|
|
163
|
+
return `provider ${name} uses reserved authMode "forward"; configure ChatGPT passthrough via the built-in provider`;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function publicProviderBaseUrl(baseUrl: string): string {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = new URL(baseUrl.trim());
|
|
171
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return "(invalid URL)";
|
|
172
|
+
parsed.username = "";
|
|
173
|
+
parsed.password = "";
|
|
174
|
+
parsed.search = "";
|
|
175
|
+
parsed.hash = "";
|
|
176
|
+
return parsed.toString().replace(/\/$/, baseUrl.endsWith("/") ? "/" : "");
|
|
177
|
+
} catch {
|
|
178
|
+
return "(invalid URL)";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function copyIfDefined<K extends keyof OcxProviderConfig>(
|
|
183
|
+
out: Record<string, unknown>,
|
|
184
|
+
provider: OcxProviderConfig,
|
|
185
|
+
key: K,
|
|
186
|
+
): void {
|
|
187
|
+
const value = provider[key];
|
|
188
|
+
if (value !== undefined) out[key as string] = value as unknown;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function safeConfigDTO(config: OcxConfig): unknown {
|
|
192
|
+
const providers: Record<string, Record<string, unknown>> = {};
|
|
193
|
+
for (const [name, provider] of Object.entries(config.providers)) {
|
|
194
|
+
const dto: Record<string, unknown> = {
|
|
195
|
+
adapter: provider.adapter,
|
|
196
|
+
baseUrl: publicProviderBaseUrl(provider.baseUrl),
|
|
197
|
+
hasApiKey: !!provider.apiKey,
|
|
198
|
+
hasHeaders: !!provider.headers && Object.keys(provider.headers).length > 0,
|
|
199
|
+
};
|
|
200
|
+
for (const key of [
|
|
201
|
+
"defaultModel",
|
|
202
|
+
"disabled",
|
|
203
|
+
"authMode",
|
|
204
|
+
"liveModels",
|
|
205
|
+
"models",
|
|
206
|
+
"contextWindow",
|
|
207
|
+
"modelContextWindows",
|
|
208
|
+
"reasoningEfforts",
|
|
209
|
+
"modelReasoningEfforts",
|
|
210
|
+
"noVisionModels",
|
|
211
|
+
"noReasoningModels",
|
|
212
|
+
"noTemperatureModels",
|
|
213
|
+
"noTopPModels",
|
|
214
|
+
"noPenaltyModels",
|
|
215
|
+
"autoToolChoiceOnlyModels",
|
|
216
|
+
"preserveReasoningContentModels",
|
|
217
|
+
"escapeBuiltinToolNames",
|
|
218
|
+
] as const) {
|
|
219
|
+
copyIfDefined(dto, provider, key);
|
|
220
|
+
}
|
|
221
|
+
providers[name] = dto;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
port: config.port,
|
|
225
|
+
hostname: config.hostname ?? "127.0.0.1",
|
|
226
|
+
defaultProvider: config.defaultProvider,
|
|
227
|
+
codexAutoStart: codexAutoStartEnabled(config),
|
|
228
|
+
websockets: config.websockets,
|
|
229
|
+
providers,
|
|
230
|
+
};
|
|
231
|
+
}
|