@bitkyc08/opencodex 2.23.0-preview.20260816 → 2.24.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/gui/dist/assets/index-CFqJKF2L.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- package/src/adapters/anthropic.ts +39 -7
- package/src/adapters/cursor/tool-definitions.ts +48 -0
- package/src/adapters/google.ts +18 -12
- package/src/adapters/openai-chat.ts +106 -13
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +3 -0
- package/src/bridge.ts +16 -5
- package/src/chat/inbound.ts +5 -11
- package/src/claude/context-windows.ts +20 -4
- package/src/claude/desktop-3p.ts +11 -6
- package/src/claude/inbound.ts +39 -1
- package/src/claude/model-info.ts +28 -8
- package/src/cli/account-api.ts +5 -1
- package/src/cli/claude-desktop.ts +3 -0
- package/src/cli/config-command.ts +37 -14
- package/src/codex/app-server-restart-service.ts +1 -1
- package/src/codex/auth-api.ts +5 -0
- package/src/codex/auth-context.ts +43 -2
- package/src/codex/catalog/metadata.ts +135 -12
- package/src/codex/catalog/native-models.ts +32 -2
- package/src/codex/catalog/parsing.ts +61 -13
- package/src/codex/catalog/provider-fetch.ts +37 -7
- package/src/codex/catalog/sync.ts +49 -25
- package/src/codex/catalog-refresh-status.ts +21 -3
- package/src/codex/catalog.ts +1 -1
- package/src/codex/convergence-types.ts +23 -2
- package/src/codex/desired-state.ts +1 -1
- package/src/codex/inject.ts +38 -7
- package/src/codex/injected-marker.ts +28 -0
- package/src/codex/journal.ts +40 -1
- package/src/codex/management-convergence.ts +55 -2
- package/src/codex/quota-rejection.ts +61 -1
- package/src/codex/quota.ts +60 -6
- package/src/codex/routing.ts +30 -3
- package/src/combos/failover.ts +20 -0
- package/src/config.ts +271 -4
- package/src/generated/compatibility-version.json +90 -78
- package/src/grok/sync.ts +3 -1
- package/src/lab/artifacts/sanitize.ts +1 -1
- package/src/lab/live/manifest.ts +1 -1
- package/src/lib/codex-restart-contract.ts +1 -1
- package/src/lib/config-ownership.ts +1 -0
- package/src/lib/errors.ts +9 -0
- package/src/lib/lab-activation.ts +1 -1
- package/src/lib/optional-shutdown-hooks.ts +1 -1
- package/src/lib/pinned-http.ts +7 -2
- package/src/lib/windows-elevation.ts +3 -3
- package/src/providers/quota.ts +10 -4
- package/src/providers/registry.ts +2 -2
- package/src/responses/parser.ts +42 -7
- package/src/responses/provider-opaque-metadata.ts +1 -1
- package/src/responses/thought-signature-replay.ts +261 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/provider-slot.ts +1 -1
- package/src/routing/evaluator.ts +12 -2
- package/src/routing/health.ts +16 -5
- package/src/routing/history/schema.ts +1 -1
- package/src/routing/trace.ts +1 -1
- package/src/server/auth-cors.ts +96 -21
- package/src/server/chat-completions.ts +6 -2
- package/src/server/chat-native.ts +32 -6
- package/src/server/index.ts +5 -3
- package/src/server/management/agent-settings-routes.ts +26 -4
- package/src/server/management/config-routes.ts +79 -2
- package/src/server/management/context.ts +1 -1
- package/src/server/management/model-rows.ts +5 -0
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/provider-routes.ts +19 -0
- package/src/server/management/shared.ts +3 -3
- package/src/server/management-api.ts +13 -6
- package/src/server/passive-route-linker.ts +1 -1
- package/src/server/relay.ts +16 -0
- package/src/server/responses/compact.ts +10 -3
- package/src/server/responses/core.ts +160 -33
- package/src/server/responses/fetch-helpers.ts +34 -2
- package/src/server/responses/input-admission.ts +17 -9
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/system-env.ts +4 -2
- package/src/service.ts +22 -7
- package/src/types.ts +35 -1
- package/gui/dist/assets/index-Ch-YtWdA.js +0 -102
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import { isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
34
34
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
35
35
|
import { modelInList, namespacedToolName } from "../../types";
|
|
36
|
-
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
|
|
36
|
+
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage, UpstreamHttpVersion } from "../../types";
|
|
37
37
|
import {
|
|
38
38
|
forceRefreshOAuthAccessSnapshot,
|
|
39
39
|
getOAuthCredentialApiBaseUrl,
|
|
@@ -149,6 +149,38 @@ export interface ProviderFetchOptions {
|
|
|
149
149
|
modelId?: string;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Bun's fetch accepts a non-standard `protocol` init to pin the HTTP version
|
|
154
|
+
* (BunFetchRequestInit.protocol). The DOM lib types do not include it, so the
|
|
155
|
+
* value is carried on an intersection and stripped before non-Bun callers.
|
|
156
|
+
* The accepted values are the shared UPSTREAM_HTTP_VERSION_VALUES enum from types.
|
|
157
|
+
*/
|
|
158
|
+
const UPSTREAM_HTTP_VERSION_PROTOCOL: Record<Exclude<UpstreamHttpVersion, "auto">, string> = {
|
|
159
|
+
"http1.1": "http1.1",
|
|
160
|
+
h1: "h1",
|
|
161
|
+
http2: "http2",
|
|
162
|
+
h2: "h2",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Attach Bun's `protocol` pin when the provider opted into a fixed HTTP version. */
|
|
166
|
+
export function withUpstreamHttpVersion(
|
|
167
|
+
input: Parameters<typeof globalThis.fetch>[0],
|
|
168
|
+
init: RequestInit | undefined,
|
|
169
|
+
provider: OcxProviderConfig,
|
|
170
|
+
): RequestInit | undefined {
|
|
171
|
+
const version = provider.upstreamHttpVersion;
|
|
172
|
+
if (!version || version === "auto") return init;
|
|
173
|
+
// Bun's protocol pin requires an https: target; local/plaintext upstreams keep
|
|
174
|
+
// their existing transport untouched.
|
|
175
|
+
const target = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
176
|
+
try {
|
|
177
|
+
if (new URL(target).protocol !== "https:") return init;
|
|
178
|
+
} catch {
|
|
179
|
+
return init;
|
|
180
|
+
}
|
|
181
|
+
return { ...(init ?? {}), protocol: UPSTREAM_HTTP_VERSION_PROTOCOL[version] } as RequestInit;
|
|
182
|
+
}
|
|
183
|
+
|
|
152
184
|
export function providerFetch(
|
|
153
185
|
provider: OcxProviderConfig,
|
|
154
186
|
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
@@ -162,7 +194,7 @@ export function providerFetch(
|
|
|
162
194
|
if (typeof input === "string" && init && shouldUseCodexWsUpstream(input, init, runtime)) {
|
|
163
195
|
return codexWsUpstreamFetch(input, init, base, runtime);
|
|
164
196
|
}
|
|
165
|
-
return base(input, init);
|
|
197
|
+
return base(input, withUpstreamHttpVersion(input, init, provider));
|
|
166
198
|
};
|
|
167
199
|
const waitForPacing = (signal?: AbortSignal) => options.providerName
|
|
168
200
|
? waitForProviderRequestSlot(options.providerName, provider, options.modelId, signal)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* catches the pathological case and stays out of the way otherwise. Every uncertainty
|
|
11
11
|
* resolves toward admitting.
|
|
12
12
|
*/
|
|
13
|
-
import { nativeOpenAiContextWindow } from "../../codex/catalog/metadata";
|
|
13
|
+
import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens } from "../../codex/catalog/metadata";
|
|
14
14
|
import { estimateTokens } from "../../lib/token-estimate";
|
|
15
15
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
16
16
|
import type { OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
@@ -129,6 +129,9 @@ export function resolveInputCeiling(
|
|
|
129
129
|
provider: OcxProviderConfig,
|
|
130
130
|
providerName: string,
|
|
131
131
|
modelId: string,
|
|
132
|
+
// Operator cap for the canonical native provider. Passed in rather than read from a
|
|
133
|
+
// config here so this stays pure: no filesystem, no catalog, no registry scan.
|
|
134
|
+
nativeContextCap?: number,
|
|
132
135
|
): number | null {
|
|
133
136
|
const configured = positive(provider.modelContextWindows?.[modelId]) ?? positive(provider.contextWindow);
|
|
134
137
|
|
|
@@ -137,18 +140,22 @@ export function resolveInputCeiling(
|
|
|
137
140
|
// transport-mismatched custom provider named "openai" is preserved verbatim by routing
|
|
138
141
|
// and must not inherit built-in native limits, and a routed `provider/model` id is not a
|
|
139
142
|
// native slug. Static maps only — no catalog read.
|
|
140
|
-
const
|
|
141
|
-
&& providerName === OPENAI_CODEX_PROVIDER_ID
|
|
143
|
+
const canonicalNativeBare = providerName === OPENAI_CODEX_PROVIDER_ID
|
|
142
144
|
&& isCanonicalOpenAiForwardProvider(provider)
|
|
143
|
-
&& !modelId.includes("/")
|
|
144
|
-
|
|
145
|
+
&& !modelId.includes("/");
|
|
146
|
+
const native = configured === null && canonicalNativeBare
|
|
147
|
+
? positive(nativeOpenAiContextWindow(modelId, nativeContextCap))
|
|
145
148
|
: null;
|
|
149
|
+
// The model's own measured input ceiling applies even when the operator configured a
|
|
150
|
+
// window: a 1,050,000 override must not raise the gate above the 922,000 the upstream
|
|
151
|
+
// actually accepts. Computed independently of `configured` for exactly that reason.
|
|
152
|
+
const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeContextCap)) : null;
|
|
146
153
|
|
|
147
154
|
const window = configured ?? native;
|
|
148
155
|
// modelMaxInputTokens is an input-only cap, so it can only tighten the window.
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
return
|
|
156
|
+
const configuredMaxInput = positive(provider.modelMaxInputTokens?.[modelId]);
|
|
157
|
+
const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null);
|
|
158
|
+
return limits.length === 0 ? null : Math.min(...limits);
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
/**
|
|
@@ -161,8 +168,9 @@ export function checkInputAdmission(
|
|
|
161
168
|
provider: OcxProviderConfig,
|
|
162
169
|
providerName: string,
|
|
163
170
|
modelId: string,
|
|
171
|
+
nativeContextCap?: number,
|
|
164
172
|
): InputAdmissionResult {
|
|
165
|
-
const ceiling = resolveInputCeiling(provider, providerName, modelId);
|
|
173
|
+
const ceiling = resolveInputCeiling(provider, providerName, modelId, nativeContextCap);
|
|
166
174
|
if (ceiling === null) return { admitted: true, estimatedTokens: 0, ceiling: null };
|
|
167
175
|
const estimatedTokens = estimateInputTokens(parsed, modelId);
|
|
168
176
|
return { admitted: estimatedTokens <= ceiling * ADMISSION_TOLERANCE, estimatedTokens, ceiling };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { namespacedToolName } from "../types";
|
|
2
|
+
import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Item types the CLIENT executes by name. Hosted calls (`web_search_call`,
|
|
6
|
+
* `image_generation_call`, `local_shell_call`, `tool_search_call`, …) are run upstream or
|
|
7
|
+
* carry no tool name, so they are never matched against the request catalog.
|
|
8
|
+
*/
|
|
9
|
+
const CLIENT_EXECUTED_CALL_TYPES = new Set(["function_call", "custom_tool_call"]);
|
|
10
|
+
|
|
11
|
+
/** An upstream-supplied name reaches the error message; keep it bounded. */
|
|
12
|
+
const MAX_REPORTED_NAME_CHARS = 100;
|
|
13
|
+
|
|
14
|
+
export const UNDECLARED_TOOL_CALL_ERROR_CODE = "undeclared_tool_call";
|
|
15
|
+
|
|
16
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function addWireToolName(names: Set<string>, tool: unknown, namespace?: string): void {
|
|
21
|
+
if (!isPlainObject(tool) || typeof tool.name !== "string" || tool.name.length === 0) return;
|
|
22
|
+
names.add(tool.name);
|
|
23
|
+
// Codex routes MCP calls by an explicit `namespace` field, so the same tool is reachable
|
|
24
|
+
// as a bare inner name or as the flattened form; accept both rather than guess which
|
|
25
|
+
// coordinate system this provider echoes back.
|
|
26
|
+
if (namespace) names.add(namespacedToolName(namespace, tool.name));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function addWireToolSpecs(names: Set<string>, specs: unknown): void {
|
|
30
|
+
if (!Array.isArray(specs)) return;
|
|
31
|
+
for (const spec of specs) {
|
|
32
|
+
if (!isPlainObject(spec)) continue;
|
|
33
|
+
if (spec.type === "namespace" && Array.isArray(spec.tools)) {
|
|
34
|
+
const namespace = typeof spec.name === "string" ? spec.name : undefined;
|
|
35
|
+
for (const inner of spec.tools) addWireToolName(names, inner, namespace);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
addWireToolName(names, spec);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tool names the OUTBOUND Responses body actually declared.
|
|
44
|
+
*
|
|
45
|
+
* This reads the body that goes upstream rather than the parsed internal tool list: the
|
|
46
|
+
* passthrough forwards wire shapes (namespaced MCP groups, `additional_tools` items carried
|
|
47
|
+
* inside `input`, routed custom-tool rewrites) that the internal list flattens or renames, and
|
|
48
|
+
* only the wire names can be compared against what the provider echoes back.
|
|
49
|
+
*/
|
|
50
|
+
export function collectDeclaredWireToolNames(body: unknown): Set<string> {
|
|
51
|
+
const names = new Set<string>();
|
|
52
|
+
if (!isPlainObject(body)) return names;
|
|
53
|
+
addWireToolSpecs(names, body.tools);
|
|
54
|
+
if (Array.isArray(body.input)) {
|
|
55
|
+
for (const item of body.input) {
|
|
56
|
+
if (isPlainObject(item) && item.type === "additional_tools") addWireToolSpecs(names, item.tools);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return names;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function undeclaredNameInItem(item: unknown, declared: ReadonlySet<string>): string | undefined {
|
|
63
|
+
if (!isPlainObject(item)) return undefined;
|
|
64
|
+
if (typeof item.type !== "string" || !CLIENT_EXECUTED_CALL_TYPES.has(item.type)) return undefined;
|
|
65
|
+
const name = item.name;
|
|
66
|
+
if (typeof name !== "string" || name.length === 0) return undefined;
|
|
67
|
+
if (declared.has(name)) return undefined;
|
|
68
|
+
if (typeof item.namespace === "string" && declared.has(namespacedToolName(item.namespace, name))) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return name;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** First undeclared client tool named by a Responses SSE payload, or undefined. */
|
|
75
|
+
export function undeclaredToolCallName(
|
|
76
|
+
payload: unknown,
|
|
77
|
+
declared: ReadonlySet<string>,
|
|
78
|
+
): string | undefined {
|
|
79
|
+
if (!isPlainObject(payload)) return undefined;
|
|
80
|
+
if (payload.type === "response.output_item.added" || payload.type === "response.output_item.done") {
|
|
81
|
+
return undeclaredNameInItem(payload.item, declared);
|
|
82
|
+
}
|
|
83
|
+
// Sparse gateways skip incremental items and only ever ship the terminal snapshot.
|
|
84
|
+
if (payload.type === "response.completed" || payload.type === "response.incomplete") {
|
|
85
|
+
return undeclaredToolCallNameInResponse(payload.response, declared);
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** First undeclared client tool in a Responses object's `output` array, or undefined. */
|
|
91
|
+
export function undeclaredToolCallNameInResponse(
|
|
92
|
+
response: unknown,
|
|
93
|
+
declared: ReadonlySet<string>,
|
|
94
|
+
): string | undefined {
|
|
95
|
+
if (!isPlainObject(response) || !Array.isArray(response.output)) return undefined;
|
|
96
|
+
for (const item of response.output) {
|
|
97
|
+
const name = undeclaredNameInItem(item, declared);
|
|
98
|
+
if (name !== undefined) return name;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function undeclaredToolCallMessage(name: string): string {
|
|
104
|
+
const reported = name.slice(0, MAX_REPORTED_NAME_CHARS);
|
|
105
|
+
return `routed provider emitted undeclared client tool "${reported}"; only request-declared tools may be called`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function failedBlocks(name: string, newline: string): readonly string[] {
|
|
109
|
+
const failure = {
|
|
110
|
+
type: "upstream_error",
|
|
111
|
+
code: UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
112
|
+
message: undeclaredToolCallMessage(name),
|
|
113
|
+
};
|
|
114
|
+
const payload = JSON.stringify({
|
|
115
|
+
type: "response.failed",
|
|
116
|
+
response: { status: "failed", error: failure, last_error: failure },
|
|
117
|
+
});
|
|
118
|
+
return [`event: response.failed${newline}data: ${payload}`, "data: [DONE]"];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fail closed when a routed provider calls a tool the request never declared (#1700).
|
|
123
|
+
*
|
|
124
|
+
* The bridged paths already refuse such a call (`declaredToolNames` in src/bridge.ts), but the
|
|
125
|
+
* native Responses passthrough relayed it verbatim: Codex received a `function_call` for a tool
|
|
126
|
+
* it has no top-level handler for — `apply_patch`, which under code mode exists only as a nested
|
|
127
|
+
* `tools.apply_patch(...)` helper inside `exec` — and the turn surfaced as a bare `aborted` with
|
|
128
|
+
* no output and no explanation. Replacing the offending event with an explicit `response.failed`
|
|
129
|
+
* turns that silent dead end into a compatibility error naming the tool.
|
|
130
|
+
*
|
|
131
|
+
* Everything after the trip is dropped so a later `response.completed` cannot contradict the
|
|
132
|
+
* terminal already sent. Non-JSON and non-item blocks pass through untouched.
|
|
133
|
+
*/
|
|
134
|
+
export function createUndeclaredToolCallGuardBlockRewrite(
|
|
135
|
+
declared: ReadonlySet<string>,
|
|
136
|
+
): SseBlockRewrite {
|
|
137
|
+
let tripped = false;
|
|
138
|
+
return (block: string) => {
|
|
139
|
+
if (tripped) return [];
|
|
140
|
+
const payload = sseDataPayload(block);
|
|
141
|
+
if (payload === null || payload === "[DONE]") return [block];
|
|
142
|
+
let parsed: unknown;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(payload);
|
|
145
|
+
} catch {
|
|
146
|
+
return [block];
|
|
147
|
+
}
|
|
148
|
+
const name = undeclaredToolCallName(parsed, declared);
|
|
149
|
+
if (name === undefined) return [block];
|
|
150
|
+
tripped = true;
|
|
151
|
+
return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
|
|
152
|
+
};
|
|
153
|
+
}
|
package/src/server/system-env.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { PROXY_MARKER, defaultAuthDetectDeps, detectClaudeAuth, ownAdmissionToke
|
|
|
7
7
|
import { resolveClaudeAuthMode } from "../claude/auth-mode";
|
|
8
8
|
import type { OcxConfig } from "../types";
|
|
9
9
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
10
|
+
import { providerContextCap } from "../providers/context-cap";
|
|
11
|
+
import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Does the opencodex dummy marker belong in the system environment?
|
|
@@ -237,10 +239,10 @@ async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMod
|
|
|
237
239
|
const windows = await boundedContextWindows(async () => {
|
|
238
240
|
const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
|
|
239
241
|
try {
|
|
240
|
-
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config));
|
|
242
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config), providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
|
|
241
243
|
} catch (error) {
|
|
242
244
|
if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
|
|
243
|
-
return buildClaudeContextWindows([...visibleNativeSlugs(config)], []);
|
|
245
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], [], providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
|
|
244
246
|
}
|
|
245
247
|
throw error;
|
|
246
248
|
}
|
package/src/service.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { execFileSync, execSync, spawnSync } from "node:child_process";
|
|
|
9
9
|
import { findLiveProxy, proxyIdentityAt, SERVICE_STOP_LIVENESS } from "./server/proxy-liveness";
|
|
10
10
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { homedir, tmpdir } from "node:os";
|
|
12
|
-
import { dirname, join, resolve, win32 } from "node:path";
|
|
12
|
+
import { dirname, join, posix, resolve, win32 } from "node:path";
|
|
13
13
|
import { expandUserPath, getConfigDir, readPid, removePid, removeRuntimePort, verifyPidIdentity } from "./config";
|
|
14
14
|
import { loadConfig } from "./config";
|
|
15
15
|
import { restoreNativeCodex, restoreNativeCodexAsync } from "./codex/inject";
|
|
@@ -113,12 +113,19 @@ function currentCodexSqliteHomeAbsolute(target: "native" | "windows" = "native")
|
|
|
113
113
|
const raw = process.env.CODEX_SQLITE_HOME?.trim();
|
|
114
114
|
if (!raw) return undefined;
|
|
115
115
|
const expanded = expandUserPath(raw);
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
// Service artifacts can be rendered by cross-platform tests and repair tooling, so an
|
|
117
|
+
// already-absolute path for the TARGET platform is preserved rather than re-anchored
|
|
118
|
+
// against the writing host. `resolve()` is host-relative in both directions: on a POSIX
|
|
119
|
+
// host it turns `C:\data` into `<cwd>/C:\data`, and on a Windows host it turns `/tmp/x`
|
|
120
|
+
// into `D:\tmp\x` — neither is a path the target can use. A relative value still resolves,
|
|
121
|
+
// because a service unit has no meaningful working directory.
|
|
122
|
+
//
|
|
123
|
+
// CODEX_HOME and OPENCODEX_HOME are carried through literally, so without this the same
|
|
124
|
+
// generated file disagreed with itself about two variables holding the same kind of value.
|
|
125
|
+
if (target === "windows") {
|
|
126
|
+
return win32.isAbsolute(expanded) ? win32.normalize(expanded) : resolve(expanded);
|
|
127
|
+
}
|
|
128
|
+
return posix.isAbsolute(expanded) ? posix.normalize(expanded) : resolve(expanded);
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
function currentOpenCodexHome(): string {
|
|
@@ -1538,6 +1545,14 @@ export function buildWindowsServiceScript(entry = cliEntry(), port = resolveServ
|
|
|
1538
1545
|
'>>"%OCX_SERVICE_LOG%" echo opencodex_home="%OPENCODEX_HOME%"',
|
|
1539
1546
|
'>>"%OCX_SERVICE_LOG%" echo codex_home="%CODEX_HOME%"',
|
|
1540
1547
|
'>>"%OCX_SERVICE_LOG%" echo token_file="%OCX_API_TOKEN_FILE%"',
|
|
1548
|
+
'if not exist "%OCX_BUN%" (',
|
|
1549
|
+
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] installation is incomplete: bundled Bun is missing; reinstall opencodex, then run ocx service repair',
|
|
1550
|
+
" exit /b 3",
|
|
1551
|
+
")",
|
|
1552
|
+
'if not exist "%OCX_CLI%" (',
|
|
1553
|
+
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] installation is incomplete: CLI entry is missing; reinstall opencodex, then run ocx service repair',
|
|
1554
|
+
" exit /b 3",
|
|
1555
|
+
")",
|
|
1541
1556
|
`"%OCX_BUN%" "%OCX_CLI%" start --port ${port} >>"%OCX_SERVICE_LOG%" 2>&1`,
|
|
1542
1557
|
"if %ERRORLEVEL% NEQ 0 (",
|
|
1543
1558
|
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] child exited with code %ERRORLEVEL%; restarting in 5s',
|
package/src/types.ts
CHANGED
|
@@ -457,6 +457,17 @@ export interface OcxClaudeCodeConfig {
|
|
|
457
457
|
smallFastModel?: string;
|
|
458
458
|
/** Inbound model id remaps: exact id first, then date-stripped (`-\d{8}$`). */
|
|
459
459
|
modelMap?: Record<string, string>;
|
|
460
|
+
/**
|
|
461
|
+
* Explicit classifier model for Claude Code Auto Mode safety checks (e.g. "RelayA/claude-opus-5").
|
|
462
|
+
* When unset, bare classifier requests check modelMap, then same-provider affinity from
|
|
463
|
+
* `claudeCode.model`, then compatible Anthropic-adapter providers, and finally fallbacks.
|
|
464
|
+
*/
|
|
465
|
+
classifierModel?: string;
|
|
466
|
+
/**
|
|
467
|
+
* Ordered fallback candidates for Claude Code Auto Mode classifier routing when the primary
|
|
468
|
+
* classifier route is not available.
|
|
469
|
+
*/
|
|
470
|
+
classifierFallbacks?: string[];
|
|
460
471
|
/**
|
|
461
472
|
* Inject ANTHROPIC_BASE_URL etc. into the macOS user domain via `launchctl setenv`
|
|
462
473
|
* so plain `claude` commands route through the proxy without `ocx claude`. Reverted
|
|
@@ -514,7 +525,7 @@ export interface OcxClaudeCodeConfig {
|
|
|
514
525
|
* (the legacy DISABLE_COMPACT pair takes rule-1 precedence in the CLI).
|
|
515
526
|
*/
|
|
516
527
|
autoContext?: boolean;
|
|
517
|
-
/** Compact-window tokens for auto-context. Default
|
|
528
|
+
/** Compact-window tokens for auto-context. Default 829_800 (AUTO_COMPACT_WINDOW_DEFAULT). */
|
|
518
529
|
autoCompactWindow?: number;
|
|
519
530
|
/**
|
|
520
531
|
* Bundled-skill content elision for ROUTED (non-Anthropic) models (devlog 260712
|
|
@@ -1375,6 +1386,14 @@ export interface OcxProviderConfig {
|
|
|
1375
1386
|
* link-local, or unique-local upstreams. Metadata endpoints remain blocked.
|
|
1376
1387
|
*/
|
|
1377
1388
|
allowPrivateNetwork?: boolean;
|
|
1389
|
+
/**
|
|
1390
|
+
* Pin the HTTP version used for upstream provider requests. Bun's fetch negotiates
|
|
1391
|
+
* HTTP/2 via TLS ALPN by default; some Cloudflare-fronted SSE endpoints hang on
|
|
1392
|
+
* HTTP/2 streaming responses (issue #1668). "http1.1" / "h1" forces HTTP/1.1,
|
|
1393
|
+
* "http2" / "h2" forces HTTP/2. Absent or "auto" keeps Bun's default negotiation
|
|
1394
|
+
* (current behavior unchanged). Only meaningful for https: base URLs.
|
|
1395
|
+
*/
|
|
1396
|
+
upstreamHttpVersion?: UpstreamHttpVersion;
|
|
1378
1397
|
/** Keep provider settings on disk but exclude it from routing and model/catalog listings. */
|
|
1379
1398
|
disabled?: boolean;
|
|
1380
1399
|
/**
|
|
@@ -1668,6 +1687,21 @@ export interface OcxProviderConfig {
|
|
|
1668
1687
|
nativeLocalExec?: "off" | "codex-sandbox" | "on";
|
|
1669
1688
|
}
|
|
1670
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Accepted values for the per-provider upstream HTTP-version pin (#1668). Shared by the
|
|
1692
|
+
* zod load schema, the management write boundary (POST/PATCH), and the fetch runtime, so
|
|
1693
|
+
* a value that one boundary accepts can never be rejected by another.
|
|
1694
|
+
*/
|
|
1695
|
+
export const UPSTREAM_HTTP_VERSION_VALUES = [
|
|
1696
|
+
"auto",
|
|
1697
|
+
"http1.1",
|
|
1698
|
+
"h1",
|
|
1699
|
+
"http2",
|
|
1700
|
+
"h2",
|
|
1701
|
+
] as const;
|
|
1702
|
+
|
|
1703
|
+
export type UpstreamHttpVersion = (typeof UPSTREAM_HTTP_VERSION_VALUES)[number];
|
|
1704
|
+
|
|
1671
1705
|
export const REASONING_SUMMARY_DELIVERY_VALUES = [
|
|
1672
1706
|
"sequential",
|
|
1673
1707
|
"sequential_cutoff",
|