@yansigit/opencodex 2.31.3 → 2.33.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 +2 -2
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-DKLr4LTE.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/adapters/anthropic.ts +25 -13
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +5 -1
- package/src/adapters/command-code.ts +42 -10
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- package/src/adapters/cursor/protobuf-events.ts +158 -7
- package/src/adapters/cursor/protobuf-request.ts +33 -15
- package/src/adapters/cursor/request-builder.ts +4 -3
- package/src/adapters/cursor/tool-definitions.ts +27 -1
- package/src/adapters/cursor/types.ts +4 -3
- package/src/adapters/cursor.ts +9 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +154 -33
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +4 -1
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +133 -24
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +26 -13
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +20 -6
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +8 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +25 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -0
- package/src/codex/catalog/effort.ts +42 -12
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +593 -0
- package/src/codex/catalog/parsing.ts +71 -27
- package/src/codex/catalog/provider-fetch.ts +189 -33
- package/src/codex/catalog/sync.ts +6 -5
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desktop-app-restart.ts +342 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/shim.ts +56 -3
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +214 -0
- package/src/config.ts +153 -814
- package/src/generated/compatibility-version.json +232 -148
- package/src/images/loop.ts +37 -6
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +15 -3
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +59 -13
- package/src/providers/registry.ts +4 -2
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +14 -2
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +8 -2
- package/src/server/auth-cors.ts +37 -7
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +102 -21
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +206 -16
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +35 -6
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +36 -22
- package/src/server/responses/core.ts +584 -247
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +20 -102
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/service.ts +9 -5
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +34 -1
- package/src/types/tools.ts +114 -11
- package/src/types.ts +7 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
- package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
package/src/bridge.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "./types";
|
|
9
9
|
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
10
10
|
import { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors";
|
|
11
|
+
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
11
12
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
12
13
|
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
13
14
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
awaitThoughtSignatureDurability,
|
|
19
20
|
} from "./responses/thought-signature-replay";
|
|
20
21
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
22
|
+
import { normalizeDeclaredToolName } from "./types";
|
|
21
23
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
22
24
|
import { appendSafeWebSearchSource, safeWebSearchSources } from "./web-search/sources";
|
|
23
25
|
import {
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
type TranslatorBudget,
|
|
28
30
|
type TranslatorBufferKind,
|
|
29
31
|
} from "./lib/translator-budget";
|
|
32
|
+
import { debugFingerprint, debugStreamDiagnostic, type DebugStreamDiagnosticContext } from "./lib/debug";
|
|
30
33
|
|
|
31
34
|
function uuid(): string {
|
|
32
35
|
return crypto.randomUUID().replace(/-/g, "");
|
|
@@ -117,7 +120,9 @@ function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>
|
|
|
117
120
|
}
|
|
118
121
|
const fallback = adapterFailureFromMessage(event.message);
|
|
119
122
|
let httpStatus = event.status ?? fallback.httpStatus;
|
|
120
|
-
const error =
|
|
123
|
+
const error = httpStatus === 400 && event.errorType === "upstream_error"
|
|
124
|
+
? { message: event.message, type: "upstream_error", code: event.code ?? null }
|
|
125
|
+
: classifyError(httpStatus, event.errorType ?? fallback.error.type, event.message);
|
|
121
126
|
if (event.errorType !== undefined) error.type = event.errorType;
|
|
122
127
|
if (event.code !== undefined) error.code = event.code;
|
|
123
128
|
// Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502.
|
|
@@ -165,6 +170,88 @@ interface OutputItem {
|
|
|
165
170
|
|
|
166
171
|
export type ResponsesTerminalStatus = "completed" | "failed" | "incomplete";
|
|
167
172
|
|
|
173
|
+
export interface BridgeDiagnosticSequence { value: number }
|
|
174
|
+
|
|
175
|
+
export interface BridgeDiagnosticContext extends DebugStreamDiagnosticContext {
|
|
176
|
+
sequence?: BridgeDiagnosticSequence;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function adapterEventDiagnosticDetails(event: AdapterEvent): Record<string, unknown> {
|
|
180
|
+
switch (event.type) {
|
|
181
|
+
case "text_delta":
|
|
182
|
+
return { byteLength: Buffer.byteLength(event.text), fingerprint: debugFingerprint(event.text) };
|
|
183
|
+
case "thinking_delta":
|
|
184
|
+
return { byteLength: Buffer.byteLength(event.thinking), fingerprint: debugFingerprint(event.thinking) };
|
|
185
|
+
case "reasoning_raw_delta":
|
|
186
|
+
return { byteLength: Buffer.byteLength(event.text), fingerprint: debugFingerprint(event.text) };
|
|
187
|
+
case "thinking_signature":
|
|
188
|
+
case "redacted_thinking":
|
|
189
|
+
case "kiro_redacted_reasoning": {
|
|
190
|
+
const content = event.type === "thinking_signature" ? event.signature : event.data;
|
|
191
|
+
return { byteLength: Buffer.byteLength(content), fingerprint: debugFingerprint(content) };
|
|
192
|
+
}
|
|
193
|
+
case "tool_call_delta":
|
|
194
|
+
return { byteLength: Buffer.byteLength(event.arguments), fingerprint: debugFingerprint(event.arguments) };
|
|
195
|
+
case "tool_call_start":
|
|
196
|
+
return {
|
|
197
|
+
idByteLength: Buffer.byteLength(event.id),
|
|
198
|
+
idFingerprint: debugFingerprint(event.id),
|
|
199
|
+
nameByteLength: Buffer.byteLength(event.name),
|
|
200
|
+
nameFingerprint: debugFingerprint(event.name),
|
|
201
|
+
};
|
|
202
|
+
case "web_search_call_begin":
|
|
203
|
+
return { idByteLength: Buffer.byteLength(event.id), idFingerprint: debugFingerprint(event.id) };
|
|
204
|
+
case "web_search_call_end": {
|
|
205
|
+
const queries = JSON.stringify(event.queries);
|
|
206
|
+
return {
|
|
207
|
+
idByteLength: Buffer.byteLength(event.id),
|
|
208
|
+
idFingerprint: debugFingerprint(event.id),
|
|
209
|
+
byteLength: Buffer.byteLength(queries),
|
|
210
|
+
fingerprint: debugFingerprint(queries),
|
|
211
|
+
status: event.status,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
case "error":
|
|
215
|
+
return {
|
|
216
|
+
byteLength: Buffer.byteLength(event.message),
|
|
217
|
+
fingerprint: debugFingerprint(event.message),
|
|
218
|
+
...(event.status !== undefined ? { status: event.status } : {}),
|
|
219
|
+
...(event.code !== undefined
|
|
220
|
+
? { codeByteLength: Buffer.byteLength(event.code), codeFingerprint: debugFingerprint(event.code) }
|
|
221
|
+
: {}),
|
|
222
|
+
...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
223
|
+
};
|
|
224
|
+
case "incomplete":
|
|
225
|
+
return {
|
|
226
|
+
...(event.message !== undefined ? { byteLength: Buffer.byteLength(event.message), fingerprint: debugFingerprint(event.message) } : {}),
|
|
227
|
+
reasonByteLength: Buffer.byteLength(event.reason),
|
|
228
|
+
reasonFingerprint: debugFingerprint(event.reason),
|
|
229
|
+
...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
230
|
+
};
|
|
231
|
+
case "done":
|
|
232
|
+
return {
|
|
233
|
+
...(event.stopReason !== undefined
|
|
234
|
+
? { stopReasonByteLength: Buffer.byteLength(event.stopReason), stopReasonFingerprint: debugFingerprint(event.stopReason) }
|
|
235
|
+
: {}),
|
|
236
|
+
...(event.endTurn !== undefined ? { endTurn: event.endTurn } : {}),
|
|
237
|
+
};
|
|
238
|
+
default:
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Emit one adapter-stage diagnostic while preserving one sequence across sidecar iterations. */
|
|
244
|
+
export function diagnoseAdapterEvent(context: BridgeDiagnosticContext, event: AdapterEvent): void {
|
|
245
|
+
const sequence = context.sequence ??= { value: 0 };
|
|
246
|
+
debugStreamDiagnostic(
|
|
247
|
+
context,
|
|
248
|
+
"adapter",
|
|
249
|
+
++sequence.value,
|
|
250
|
+
event.type,
|
|
251
|
+
adapterEventDiagnosticDetails(event),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
168
255
|
export function bridgeToResponsesSSE(
|
|
169
256
|
events: AsyncIterable<AdapterEvent>,
|
|
170
257
|
modelId: string,
|
|
@@ -225,17 +312,19 @@ export function bridgeToResponsesSSE(
|
|
|
225
312
|
setInterval: (handler: () => void, ms: number) => unknown;
|
|
226
313
|
clearInterval: (id: unknown) => void;
|
|
227
314
|
};
|
|
315
|
+
/** Internal, opt-in structural stream diagnostics. */
|
|
316
|
+
diagnostic?: BridgeDiagnosticContext;
|
|
228
317
|
},
|
|
229
318
|
): ReadableStream<Uint8Array> {
|
|
230
319
|
const replayCacheScope = options?.replayCacheScope;
|
|
231
320
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
232
321
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
233
|
-
// Freeform/custom tools (apply_patch) carry their body in `input`; the
|
|
234
|
-
// function with `{input:string}`, so unwrap it here when relaying back
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
322
|
+
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
323
|
+
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
324
|
+
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
325
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
326
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
327
|
+
);
|
|
239
328
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
240
329
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
241
330
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -325,6 +414,7 @@ export function bridgeToResponsesSSE(
|
|
|
325
414
|
};
|
|
326
415
|
const responseId = options?.responseId ?? `resp_${uuid()}`;
|
|
327
416
|
let seq = 0;
|
|
417
|
+
let diagnosticSequence = 0;
|
|
328
418
|
// Set once the client is gone (cancel) or an enqueue throws on a torn-down controller, so we
|
|
329
419
|
// never enqueue again and never throw a second time inside start() — the RC2 double-throw that
|
|
330
420
|
// otherwise surfaced as proxy-side stream noise on every client disconnect.
|
|
@@ -626,6 +716,7 @@ export function bridgeToResponsesSSE(
|
|
|
626
716
|
const argsStr = coerceIntegerToolArguments(
|
|
627
717
|
currentToolCall.args || "{}",
|
|
628
718
|
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
719
|
+
currentToolCall.namespace === undefined ? currentToolCall.name : undefined,
|
|
629
720
|
);
|
|
630
721
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
631
722
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
@@ -636,7 +727,8 @@ export function bridgeToResponsesSSE(
|
|
|
636
727
|
if (currentToolCall.freeform) {
|
|
637
728
|
emit("response.custom_tool_call_input.done", {
|
|
638
729
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
639
|
-
|
|
730
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
731
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
|
|
640
732
|
});
|
|
641
733
|
}
|
|
642
734
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
@@ -652,7 +744,8 @@ export function bridgeToResponsesSSE(
|
|
|
652
744
|
? {
|
|
653
745
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
654
746
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
655
|
-
|
|
747
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
748
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
|
|
656
749
|
}
|
|
657
750
|
: {
|
|
658
751
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -691,7 +784,8 @@ export function bridgeToResponsesSSE(
|
|
|
691
784
|
? {
|
|
692
785
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
693
786
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
694
|
-
|
|
787
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
788
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
|
|
695
789
|
}
|
|
696
790
|
: {
|
|
697
791
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -853,6 +947,17 @@ export function bridgeToResponsesSSE(
|
|
|
853
947
|
}
|
|
854
948
|
if (next.done) { upstreamDone = true; break; }
|
|
855
949
|
const event = next.value;
|
|
950
|
+
if (options?.diagnostic) {
|
|
951
|
+
debugStreamDiagnostic(
|
|
952
|
+
options.diagnostic,
|
|
953
|
+
"bridge",
|
|
954
|
+
options.diagnostic.sequence
|
|
955
|
+
? ++options.diagnostic.sequence.value
|
|
956
|
+
: ++diagnosticSequence,
|
|
957
|
+
event.type,
|
|
958
|
+
adapterEventDiagnosticDetails(event),
|
|
959
|
+
);
|
|
960
|
+
}
|
|
856
961
|
let terminalEvent = false;
|
|
857
962
|
// Invisible adapter heartbeats (and buffered web-search progress) count as upstream
|
|
858
963
|
// liveness only — they must not suppress wire keepalives that re-arm Codex idle timers.
|
|
@@ -1036,13 +1141,14 @@ export function bridgeToResponsesSSE(
|
|
|
1036
1141
|
rememberReasoningForCall(event.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1037
1142
|
}
|
|
1038
1143
|
if (currentToolCall) closeCurrentToolCall();
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1144
|
+
const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
|
|
1145
|
+
const mapped = toolNsMap?.get(effectiveName);
|
|
1146
|
+
const realName = mapped?.name ?? effectiveName;
|
|
1147
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1042
1148
|
const failure = responseError(
|
|
1043
1149
|
502,
|
|
1044
1150
|
"upstream_error",
|
|
1045
|
-
`routed provider emitted undeclared client tool "${
|
|
1151
|
+
`routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1046
1152
|
);
|
|
1047
1153
|
emit("response.failed", {
|
|
1048
1154
|
response: {
|
|
@@ -1064,7 +1170,7 @@ export function bridgeToResponsesSSE(
|
|
|
1064
1170
|
const item = toolSearch
|
|
1065
1171
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
1066
1172
|
: freeform
|
|
1067
|
-
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
1173
|
+
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
1068
1174
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
1069
1175
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1070
1176
|
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
@@ -1567,10 +1673,9 @@ function buildResponseJSONWithBudget(
|
|
|
1567
1673
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1568
1674
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1569
1675
|
|
|
1570
|
-
const freeformInput = (args: string): string =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
};
|
|
1676
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
1677
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
1678
|
+
);
|
|
1574
1679
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1575
1680
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1576
1681
|
};
|
|
@@ -1656,6 +1761,7 @@ function buildResponseJSONWithBudget(
|
|
|
1656
1761
|
const coercedArgs = coerceIntegerToolArguments(
|
|
1657
1762
|
currentToolCallArgs,
|
|
1658
1763
|
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1764
|
+
ns === undefined ? realName : undefined,
|
|
1659
1765
|
);
|
|
1660
1766
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1661
1767
|
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
@@ -1670,7 +1776,8 @@ function buildResponseJSONWithBudget(
|
|
|
1670
1776
|
pushOutput({
|
|
1671
1777
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1672
1778
|
call_id: currentToolCallId, name: realName,
|
|
1673
|
-
|
|
1779
|
+
...(ns ? { namespace: ns } : {}),
|
|
1780
|
+
input: freeformInput(currentToolCallArgs, realName, ns), status,
|
|
1674
1781
|
});
|
|
1675
1782
|
} else {
|
|
1676
1783
|
pushOutput({
|
|
@@ -1777,7 +1884,7 @@ function buildResponseJSONWithBudget(
|
|
|
1777
1884
|
));
|
|
1778
1885
|
}
|
|
1779
1886
|
break;
|
|
1780
|
-
case "tool_call_start":
|
|
1887
|
+
case "tool_call_start": {
|
|
1781
1888
|
if (currentText) flushText("commentary");
|
|
1782
1889
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1783
1890
|
if (currentRawReasoning) flushRawReasoning();
|
|
@@ -1785,10 +1892,11 @@ function buildResponseJSONWithBudget(
|
|
|
1785
1892
|
rememberReasoningForCall(e.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1786
1893
|
}
|
|
1787
1894
|
flushToolCall();
|
|
1788
|
-
|
|
1895
|
+
const effectiveName = normalizeDeclaredToolName(e.name, options?.declaredToolNames);
|
|
1896
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1789
1897
|
errorEvent = {
|
|
1790
1898
|
type: "error",
|
|
1791
|
-
message: `routed provider emitted undeclared client tool "${
|
|
1899
|
+
message: `routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1792
1900
|
status: 502,
|
|
1793
1901
|
errorType: "upstream_error",
|
|
1794
1902
|
};
|
|
@@ -1796,11 +1904,12 @@ function buildResponseJSONWithBudget(
|
|
|
1796
1904
|
}
|
|
1797
1905
|
currentToolCallId = e.id;
|
|
1798
1906
|
budget?.openCall(e.id);
|
|
1799
|
-
currentToolCallName =
|
|
1907
|
+
currentToolCallName = effectiveName;
|
|
1800
1908
|
currentToolCallArgs = "";
|
|
1801
1909
|
currentToolCallArgsBytes = 0;
|
|
1802
1910
|
currentToolCallProviderMetadata = e.providerMetadata;
|
|
1803
1911
|
break;
|
|
1912
|
+
}
|
|
1804
1913
|
case "tool_call_delta":
|
|
1805
1914
|
{
|
|
1806
1915
|
({ value: currentToolCallArgs, bytes: currentToolCallArgsBytes } = appendBatchString(
|
|
@@ -118,17 +118,24 @@ export function buildClaudeContextWindows(
|
|
|
118
118
|
put(desktop3pAlias("native", slug), window);
|
|
119
119
|
put(aliasForNative(slug), window);
|
|
120
120
|
}
|
|
121
|
+
// Anthropic passthrough guard (audit 021 #3): canonical claude ids ride the
|
|
122
|
+
// subscription passthrough — marking a sub-1M one would strap [1m]/1M-beta onto
|
|
123
|
+
// a model that cannot host it. Register anthropic rows only at >=1M.
|
|
124
|
+
const registrable = routedModels.filter(
|
|
125
|
+
m =>
|
|
126
|
+
typeof m.contextWindow === "number" &&
|
|
127
|
+
m.contextWindow > 0 &&
|
|
128
|
+
!(m.provider === "anthropic" && m.contextWindow < ONE_MILLION),
|
|
129
|
+
);
|
|
121
130
|
// Bare routed ids are registered only when unambiguous across providers (audit
|
|
122
|
-
// 021 #5) — natives are registered first, so a native slug always wins the bare
|
|
131
|
+
// 021 #5) — natives are registered first, so a native slug always wins the bare
|
|
132
|
+
// key. Counted over the rows that can actually claim the key: a row this loop
|
|
133
|
+
// skips contributes no window, so letting it veto the bare key withholds an
|
|
134
|
+
// answer that was never in doubt.
|
|
123
135
|
const bareCounts = new Map<string, number>();
|
|
124
|
-
for (const m of
|
|
125
|
-
for (const m of
|
|
126
|
-
const window = m.contextWindow;
|
|
127
|
-
if (typeof window !== "number" || window <= 0) continue;
|
|
128
|
-
// Anthropic passthrough guard (audit 021 #3): canonical claude ids ride the
|
|
129
|
-
// subscription passthrough — marking a sub-1M one would strap [1m]/1M-beta onto
|
|
130
|
-
// a model that cannot host it. Register anthropic rows only at >=1M.
|
|
131
|
-
if (m.provider === "anthropic" && window < ONE_MILLION) continue;
|
|
136
|
+
for (const m of registrable) bareCounts.set(m.id, (bareCounts.get(m.id) ?? 0) + 1);
|
|
137
|
+
for (const m of registrable) {
|
|
138
|
+
const window = m.contextWindow as number;
|
|
132
139
|
put(`${m.provider}/${m.id}`, window);
|
|
133
140
|
put(desktop3pAlias(m.provider, m.id), window);
|
|
134
141
|
put(aliasForRoute(m.provider, m.id), window);
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -203,7 +203,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
203
203
|
return 0;
|
|
204
204
|
},
|
|
205
205
|
sync: async deps => {
|
|
206
|
-
const
|
|
206
|
+
const syncArgs = deps.args.slice(1);
|
|
207
|
+
const restartCodex = syncArgs.includes("--restart-codex");
|
|
208
|
+
// Separate flag on purpose: --restart-codex promises app-server-only scope,
|
|
209
|
+
// and quitting the desktop app ends live conversations.
|
|
210
|
+
const restartDesktopApp = syncArgs.includes("--restart-desktop-app");
|
|
207
211
|
const live = await deps.findLiveProxy();
|
|
208
212
|
const synced = await syncModelsToCodex(
|
|
209
213
|
live?.port,
|
|
@@ -229,6 +233,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
229
233
|
// exactly when a long-lived app-server is holding the stale list.
|
|
230
234
|
if (synced.catalogWritten || synced.cacheSynced) {
|
|
231
235
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
236
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
232
237
|
}
|
|
233
238
|
// `ocx sync` is a direct CLI path; it does not call the management
|
|
234
239
|
// `/api/sync` route. Refresh the already-connected MCode block here too,
|
|
@@ -259,7 +264,9 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
259
264
|
return await cmdV2(deps.args.slice(1), {}, async () => (await deps.findLiveProxy())?.port);
|
|
260
265
|
},
|
|
261
266
|
"sync-cache": async deps => {
|
|
262
|
-
const
|
|
267
|
+
const cacheArgs = deps.args.slice(1);
|
|
268
|
+
const restartCodex = cacheArgs.includes("--restart-codex");
|
|
269
|
+
const restartDesktopApp = cacheArgs.includes("--restart-desktop-app");
|
|
263
270
|
const { withCatalogWriteSerialization } = await import("../codex/catalog-write-serialization");
|
|
264
271
|
const { invalidateCodexModelsCacheWithPermit } = await import("../codex/catalog/sync");
|
|
265
272
|
const { getCodexHome } = await import("../codex/paths");
|
|
@@ -270,6 +277,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
270
277
|
// Only warn/restart when models_cache was actually rewritten from a readable catalog.
|
|
271
278
|
if (invalidated.kind === "completed" && invalidated.value) {
|
|
272
279
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
280
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
273
281
|
} else if (desiredDisabled) {
|
|
274
282
|
console.log("Codex integration is OFF; cache sync skipped (no catalog or cache write).");
|
|
275
283
|
}
|
|
@@ -583,3 +591,43 @@ export async function dispatchCommand(head: CliHead, deps: CliDispatchDeps): Pro
|
|
|
583
591
|
}
|
|
584
592
|
return await runner(deps);
|
|
585
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Report the outcome of an opt-in desktop-app restart. Kept next to the two
|
|
597
|
+
* callers so `sync` and `sync-cache` cannot drift in what they tell the user.
|
|
598
|
+
*/
|
|
599
|
+
async function handleDesktopAppRestart(log: Pick<Console, "log" | "error">): Promise<void> {
|
|
600
|
+
const { restartCodexDesktopApp } = await import("../codex/desktop-app-restart");
|
|
601
|
+
const result = restartCodexDesktopApp();
|
|
602
|
+
switch (result.reason) {
|
|
603
|
+
case "windows_only":
|
|
604
|
+
log.error("--restart-desktop-app is supported on Windows only; nothing was stopped.");
|
|
605
|
+
return;
|
|
606
|
+
case "package_discovery_failed":
|
|
607
|
+
log.error(
|
|
608
|
+
"Could not identify the installed Codex desktop package. Quit and relaunch the desktop app "
|
|
609
|
+
+ "manually to refresh the model picker.",
|
|
610
|
+
);
|
|
611
|
+
return;
|
|
612
|
+
case "self_ancestry":
|
|
613
|
+
log.error(
|
|
614
|
+
"Refusing to restart the desktop app because this command is running inside it. "
|
|
615
|
+
+ "Run 'ocx sync --restart-desktop-app' from an external terminal instead.",
|
|
616
|
+
);
|
|
617
|
+
return;
|
|
618
|
+
case "no_targets":
|
|
619
|
+
log.log("Codex desktop app is not running; nothing to restart.");
|
|
620
|
+
return;
|
|
621
|
+
case "targets_survived":
|
|
622
|
+
log.error(
|
|
623
|
+
`Codex desktop app PID(s) ${result.surviving.join(", ")} did not exit, so it was not relaunched. `
|
|
624
|
+
+ "Quit the desktop app manually to refresh the model picker.",
|
|
625
|
+
);
|
|
626
|
+
return;
|
|
627
|
+
default:
|
|
628
|
+
if (result.relaunch === "started") {
|
|
629
|
+
log.log("Codex desktop app restarted; its model picker will re-read the catalog.");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
package/src/cli/doctor.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
|
-
import { getConfigDir, getConfigPath, readConfigDiagnostics,
|
|
13
|
+
import { getConfigDir, getConfigPath, readConfigDiagnostics, resolveEnvValue } from "../config";
|
|
14
|
+
import { readPid } from "../config/process-state";
|
|
14
15
|
import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
|
|
15
16
|
import { BUN_RUNTIME_SOURCES } from "../lib/bun-runtime";
|
|
16
17
|
import type { BunRuntimeSource } from "../lib/bun-runtime";
|
|
@@ -41,7 +42,7 @@ import {
|
|
|
41
42
|
resolveEffectiveUserIdentity,
|
|
42
43
|
} from "../codex/user-identity";
|
|
43
44
|
import { collectProjectCodexConfigWarnings, formatProjectCodexConfigWarningsForDoctor } from "../codex/project-config-warnings";
|
|
44
|
-
import { collectStartupHealth, startupHealthSummary } from "../codex/autostart-health";
|
|
45
|
+
import { collectStartupHealth, formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
|
|
45
46
|
import {
|
|
46
47
|
displayCodexRuntimePath,
|
|
47
48
|
loadLastEffortClamp,
|
|
@@ -982,7 +983,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
982
983
|
const startup = collectStartupHealth(doctorConfig);
|
|
983
984
|
console.log("\nCodex restart safety");
|
|
984
985
|
console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
|
|
985
|
-
console.log(`
|
|
986
|
+
console.log(` ${formatStartupRoutingDetail(startup)}`);
|
|
986
987
|
|
|
987
988
|
console.log("\nCodex runtime selection");
|
|
988
989
|
{
|
|
@@ -1072,10 +1073,10 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1072
1073
|
console.log(` ${probe.ok ? "ok " : "-- "} ${WHAM_USAGE_URL}`);
|
|
1073
1074
|
console.log(` ${detail}, ${probe.durationMs}ms, ${probe.authenticated ? "authenticated" : "unauthenticated"}`);
|
|
1074
1075
|
|
|
1075
|
-
// Design B upgrade visibility:
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
console.log("\nCodex history
|
|
1076
|
+
// Design B upgrade visibility: only the backup manifest authorizes restoring provider
|
|
1077
|
+
// metadata. Bare routed rows have unknown provenance and remain unchanged. This read-only
|
|
1078
|
+
// probe reports manifest work and database readability; it never mutates.
|
|
1079
|
+
console.log("\nCodex history metadata restore");
|
|
1079
1080
|
// The history failure messages point here; make the visit worthwhile by
|
|
1080
1081
|
// probing the coordinator namespace the locks live in. The probe exercises
|
|
1081
1082
|
// identity, runtime-root, and permission checks without taking any lock or
|
|
@@ -1096,11 +1097,17 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1096
1097
|
for (const line of formatCoordinatorDoctorLines(inspectCodexCoordinator())) console.log(line);
|
|
1097
1098
|
const pending = countPendingOpencodexHistory();
|
|
1098
1099
|
if (pending.failed) {
|
|
1099
|
-
|
|
1100
|
+
if (pending.failureReason === "busy") {
|
|
1101
|
+
console.log(" -- history database, backup manifest, or rollout file is busy — exact metadata restore is pending");
|
|
1102
|
+
} else if (pending.failureReason === "permission") {
|
|
1103
|
+
console.log(" -- state DB or backup manifest access was denied — restore state unknown");
|
|
1104
|
+
} else {
|
|
1105
|
+
console.log(" -- backup manifest or restore target failed integrity checks — manual review required");
|
|
1106
|
+
}
|
|
1100
1107
|
} else if (pending.pendingRows === 0 && pending.backupEntries === 0) {
|
|
1101
|
-
console.log(" ok no
|
|
1108
|
+
console.log(" ok no manifest-backed provider metadata pending; untracked routed history is unchanged");
|
|
1102
1109
|
} else {
|
|
1103
|
-
console.log(` -- ${pending.
|
|
1110
|
+
console.log(` -- ${pending.backupEntries} backup manifest entr${pending.backupEntries === 1 ? "y" : "ies"} pending exact metadata restore`);
|
|
1104
1111
|
}
|
|
1105
1112
|
|
|
1106
1113
|
console.log("\nProject Codex configs");
|
|
@@ -1148,7 +1155,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1148
1155
|
const { collectCodexAppServerCatalogState } = await import("../codex/app-server-processes");
|
|
1149
1156
|
const catalogState = collectCodexAppServerCatalogState();
|
|
1150
1157
|
if (catalogState.state === "stale") {
|
|
1151
|
-
console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`)`);
|
|
1158
|
+
console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`; on Windows the desktop app may need \`ocx sync --restart-desktop-app\`)`);
|
|
1152
1159
|
} else if (catalogState.state === "unknown") {
|
|
1153
1160
|
console.log(" [WARN] Could not verify whether the running Codex app-server's model catalog is current (start time or catalog unreadable). Action: if the model list looks stale, restart Codex");
|
|
1154
1161
|
} else if (catalogState.state === "fresh") {
|
|
@@ -1185,8 +1192,14 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1185
1192
|
}
|
|
1186
1193
|
}
|
|
1187
1194
|
}
|
|
1188
|
-
if (pending.failed
|
|
1189
|
-
hints.push("
|
|
1195
|
+
if (pending.failed && pending.failureReason === "busy") {
|
|
1196
|
+
hints.push("Backed-up history metadata is pending or its state is unreadable. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
|
|
1197
|
+
} else if (pending.failed && pending.failureReason === "permission") {
|
|
1198
|
+
hints.push("Backed-up history metadata could not be inspected because access was denied. Fix access to the reported Codex history paths, then run 'ocx sync'; repeated retries do not repair permissions.");
|
|
1199
|
+
} else if (pending.failed) {
|
|
1200
|
+
hints.push("The history manifest or its target is invalid or changed. Preserve both, inspect the manifest/database/rollout identity, and do not repeatedly run 'ocx sync' until the mismatch is understood. Untracked routed history is not relabeled.");
|
|
1201
|
+
} else if (pending.backupEntries > 0) {
|
|
1202
|
+
hints.push("Backed-up history metadata is pending. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
|
|
1190
1203
|
}
|
|
1191
1204
|
if (dual.dualInstall && !dual.effectiveIsWindowsMount) {
|
|
1192
1205
|
hints.push(`Codex is installed on BOTH WSL and Windows. Each side keeps its own ~/.codex (logins, config, catalog are separate); ocx here manages the Linux one. To share a single home, set CODEX_HOME=${dual.windowsCodexHomes[0] ?? `${dual.automountRoot}/c/Users/<you>/.codex`} in WSL (drvfs file locking is less reliable).`);
|
package/src/cli/help.ts
CHANGED
|
@@ -24,8 +24,8 @@ Usage:
|
|
|
24
24
|
ocx stop Stop the proxy AND restore native Codex (plain codex works again)
|
|
25
25
|
ocx restore Restore native Codex without stopping (alias: eject)
|
|
26
26
|
ocx restore back Re-point codex at the running proxy (undo restore)
|
|
27
|
-
ocx recover-history --legacy-openai
|
|
28
|
-
|
|
27
|
+
ocx recover-history --legacy-openai --yes
|
|
28
|
+
Force all user-message opencodex rows to OpenAI (legacy recovery)
|
|
29
29
|
ocx uninstall Remove service/shim/config and restore native Codex (alias: remove)
|
|
30
30
|
ocx service [sub] Run as a background service (default: install/update/start)
|
|
31
31
|
ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall|remove)
|
|
@@ -57,7 +57,8 @@ Usage:
|
|
|
57
57
|
ocx observe <sub> Logs, usage, storage, memory, and debug data
|
|
58
58
|
ocx route <sub> Routing features (combo, policy)
|
|
59
59
|
ocx logs [filters] Alias of ocx observe logs
|
|
60
|
-
ocx usage [--range <7d|30d|all>]
|
|
60
|
+
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
|
|
61
|
+
Token and estimated-cost report (alias of ocx observe usage)
|
|
61
62
|
ocx storage [--json] Alias of ocx observe storage
|
|
62
63
|
ocx memory [--json] Alias of ocx observe memory
|
|
63
64
|
ocx api-key <sub> Alias of ocx access key
|
package/src/cli/index.ts
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
codexAutoStartEnabled,
|
|
13
13
|
getConfigDir,
|
|
14
14
|
loadConfig,
|
|
15
|
+
saveConfig,
|
|
16
|
+
} from "../config";
|
|
17
|
+
import {
|
|
15
18
|
readPid,
|
|
16
19
|
readPidFileValue,
|
|
17
20
|
readRuntimePort,
|
|
@@ -19,11 +22,10 @@ import {
|
|
|
19
22
|
removePidIfValueIs,
|
|
20
23
|
removeRuntimePort,
|
|
21
24
|
removeRuntimePortIfPidIs,
|
|
22
|
-
saveConfig,
|
|
23
25
|
writePid,
|
|
24
26
|
writeRuntimePort,
|
|
25
|
-
} from "../config";
|
|
26
|
-
import { collectStatus } from "./status";
|
|
27
|
+
} from "../config/process-state";
|
|
28
|
+
import { collectStatus, unusedProxyWarningLines } from "./status";
|
|
27
29
|
|
|
28
30
|
import {
|
|
29
31
|
discoverStableProxyForRestart,
|
|
@@ -44,7 +46,7 @@ import { runCli } from "./root";
|
|
|
44
46
|
import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
|
|
45
47
|
import { loadServiceTokenFromFile } from "../lib/service-secrets";
|
|
46
48
|
import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
|
|
47
|
-
import { startupHealthSummary } from "../codex/autostart-health";
|
|
49
|
+
import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
|
|
48
50
|
import { drainAndShutdown, isRecyclingForExit, startServer } from "../server";
|
|
49
51
|
import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
|
|
50
52
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
@@ -847,6 +849,12 @@ async function handleStatus() {
|
|
|
847
849
|
console.log(`❌ Proxy: ${status.proxyLabel}`);
|
|
848
850
|
}
|
|
849
851
|
console.log(` Health: ${status.healthLabel}`);
|
|
852
|
+
for (const line of unusedProxyWarningLines({
|
|
853
|
+
proxyUp: Boolean(status.json.proxy.pid || status.json.proxy.health.ok),
|
|
854
|
+
routingKind: status.json.startup.routingKind,
|
|
855
|
+
})) {
|
|
856
|
+
console.log(` ${line}`);
|
|
857
|
+
}
|
|
850
858
|
if (!(status.json.proxy.pid || status.json.proxy.health.ok)) {
|
|
851
859
|
console.log(" ↳ Not running — Codex/Claude requests will fail with connection errors.");
|
|
852
860
|
// The service summary a few lines below already tells a registered-but-not-serving
|
|
@@ -866,6 +874,7 @@ async function handleStatus() {
|
|
|
866
874
|
console.log(` Default provider: ${status.json.defaultProvider}`);
|
|
867
875
|
console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}`);
|
|
868
876
|
console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}`);
|
|
877
|
+
console.log(` ${formatStartupRoutingDetail(status.json.startup)}`);
|
|
869
878
|
console.log(` Service: ${status.json.service.summary}`);
|
|
870
879
|
console.log(` ${status.json.codexShim.summary}`);
|
|
871
880
|
console.log(` Codex runtime: ${status.json.codexRuntime.path}`);
|
|
@@ -906,8 +915,13 @@ async function handleStatus() {
|
|
|
906
915
|
|
|
907
916
|
async function handleRecoverHistory() {
|
|
908
917
|
if (args[1] !== "--legacy-openai") {
|
|
909
|
-
console.error("Usage: ocx recover-history --legacy-openai");
|
|
910
|
-
console.error("
|
|
918
|
+
console.error("Usage: ocx recover-history --legacy-openai --yes");
|
|
919
|
+
console.error("This force-relabels every user-message opencodex row to OpenAI, including legitimate dedicated-provider history. Back up first and use it only for pre-backup legacy recovery.");
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
console.error("WARNING: this force-relabels every user-message opencodex row to OpenAI, normalizes exec to cli, and includes legitimate dedicated-provider history.");
|
|
923
|
+
if (args.length !== 3 || args[2] !== "--yes") {
|
|
924
|
+
console.error("Re-run with explicit confirmation: ocx recover-history --legacy-openai --yes");
|
|
911
925
|
process.exit(1);
|
|
912
926
|
}
|
|
913
927
|
// Manifest-independent legacy ejection, serialized like every other history
|
package/src/cli/models.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { syncModelsToCodex } from "../codex/sync";
|
|
7
7
|
import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
canonicalizeReasoningEfforts,
|
|
10
|
+
configuredReasoningEfforts,
|
|
11
|
+
isDeclaredReasoningEffort,
|
|
12
|
+
modelRecordValue,
|
|
13
|
+
} from "../reasoning-effort";
|
|
9
14
|
import { encodedModelIdCollides, routedSlug, slugEquals } from "../providers/slug-codec";
|
|
10
15
|
import { knownModelIdsForProvider } from "../router";
|
|
11
16
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
@@ -91,7 +96,6 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
91
96
|
const seen = new Set<string>();
|
|
92
97
|
const contextWindows = prov.modelContextWindows ?? {};
|
|
93
98
|
const inputModalities = prov.modelInputModalities ?? {};
|
|
94
|
-
const reasoningEfforts = prov.modelReasoningEfforts ?? {};
|
|
95
99
|
const globalContext = prov.contextWindow ?? null;
|
|
96
100
|
|
|
97
101
|
const addModel = (model: string, isDefault: boolean) => {
|
|
@@ -107,7 +111,13 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
107
111
|
// an exact `gpt-oss:120b` entry that lists "image", and the proxy rejects the image.
|
|
108
112
|
const noVision = modelInList(prov.noVisionModels, model);
|
|
109
113
|
const modalities = noVision ? ["text"] : (modelRecordValue(inputModalities, model) ?? null);
|
|
110
|
-
|
|
114
|
+
// Same reason, for the ladder: `configuredReasoningEfforts` is what the catalog
|
|
115
|
+
// (`provider-fetch`) and the effort cap (`effort-policy`) resolve through, and it
|
|
116
|
+
// does three things this expression did not — it returns [] for a noReasoningModels
|
|
117
|
+
// match, drops levels Codex does not declare, and re-adds tiers the wire map proves
|
|
118
|
+
// the model emits. Restating two of its five lines here reported a ladder the proxy
|
|
119
|
+
// strips, and unsanitized junk as a supported level.
|
|
120
|
+
const efforts = configuredReasoningEfforts(prov, model) ?? null;
|
|
111
121
|
|
|
112
122
|
entries.push({
|
|
113
123
|
provider: provName,
|