@bitkyc08/opencodex 2.59.0 → 2.60.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-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/request-builder.ts +7 -1
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +3 -1
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +50 -13
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +29 -5
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +37 -6
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +5 -1
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +15 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/bridge/sse.ts +3 -151
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/dispatch.ts +3 -3
- package/src/cli/doctor.ts +28 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +7 -2
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +2 -2
- package/src/client/runtime.ts +26 -2
- package/src/codex/account-store.ts +65 -0
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/retained-sync.ts +22 -26
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +20 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +29 -10
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing.ts +7 -1
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +18 -0
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/config/schema/leaf-validators.ts +12 -0
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +249 -169
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +50 -2
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +66 -18
- package/src/providers/model-rename-migration.ts +55 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +35 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +9 -0
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +5 -2
- package/src/server/claude-messages.ts +6 -3
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +16 -13
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +119 -10
- package/src/server/request-log.ts +55 -2
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/compact.ts +32 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/passthrough-delivery.ts +31 -14
- package/src/server/responses/passthrough-dispatch.ts +36 -9
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +67 -17
- package/src/server/responses/request-send-budget.ts +5 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +2 -2
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/config.ts +11 -3
- package/src/types/provider.ts +18 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +14 -0
- package/src/types.ts +1 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -338,7 +338,7 @@ export async function prepareResponsesTransport(
|
|
|
338
338
|
route.provider = current;
|
|
339
339
|
}
|
|
340
340
|
adapter = activeAdapter = runTurnAdapter = resolveSelectionAdapter(
|
|
341
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
341
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
342
342
|
);
|
|
343
343
|
invalidateSameTargetRequest();
|
|
344
344
|
return adapter;
|
|
@@ -619,7 +619,7 @@ export async function prepareResponsesTransport(
|
|
|
619
619
|
? resolveCopilotApiBaseUrl(sentOAuthSnapshot?.apiBaseUrl)
|
|
620
620
|
: undefined,
|
|
621
621
|
);
|
|
622
|
-
let adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
622
|
+
let adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy);
|
|
623
623
|
const stripClaudeMainAuth = options.stripClaudeMainAuthForNoncanonicalForward === true
|
|
624
624
|
&& !isCanonicalOpenAiForwardProvider(adapterProvider)
|
|
625
625
|
&& ((adapterProvider.adapter === "openai-responses" && adapterProvider.authMode === "forward")
|
|
@@ -79,7 +79,11 @@ export async function executeResponsesRunTurn(
|
|
|
79
79
|
>,
|
|
80
80
|
sendBudgetState: Pick<
|
|
81
81
|
ResponsesSendBudget,
|
|
82
|
-
|
|
82
|
+
| "adapterDispatchBudget"
|
|
83
|
+
| "noteAdapterPhysicalSend"
|
|
84
|
+
| "noteAdapterRecoveryWithheld"
|
|
85
|
+
| "reserveCredentialHop"
|
|
86
|
+
| "pendingHopPermit"
|
|
83
87
|
>,
|
|
84
88
|
completionPolicy: Pick<ResponsesCompletionPolicy, "emptyCompletionGuardEnabled">,
|
|
85
89
|
): Promise<Response> {
|
|
@@ -100,7 +104,12 @@ export async function executeResponsesRunTurn(
|
|
|
100
104
|
rememberKiroDeliveredFinalAnswer,
|
|
101
105
|
responseStateOptions,
|
|
102
106
|
} = requestState;
|
|
103
|
-
const {
|
|
107
|
+
const {
|
|
108
|
+
adapterDispatchBudget,
|
|
109
|
+
noteAdapterPhysicalSend,
|
|
110
|
+
noteAdapterRecoveryWithheld,
|
|
111
|
+
reserveCredentialHop,
|
|
112
|
+
} = sendBudgetState;
|
|
104
113
|
const { emptyCompletionGuardEnabled } = completionPolicy;
|
|
105
114
|
const {
|
|
106
115
|
cancelResponseCompletion,
|
|
@@ -146,7 +155,11 @@ export async function executeResponsesRunTurn(
|
|
|
146
155
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
147
156
|
}
|
|
148
157
|
await refreshRunTurnSelection();
|
|
149
|
-
|
|
158
|
+
// An adapter that reports its own sends accounts for the first one at the boundary that
|
|
159
|
+
// dispatches it. Logging here would claim a send that the adapter's own budget can still
|
|
160
|
+
// refuse, which is exactly what happens once earlier recovery has spent the allowance.
|
|
161
|
+
const reportsOwnSends = transportState.runTurnAdapter.reportsPhysicalSends === true;
|
|
162
|
+
if (!reportsOwnSends) transportState.noteRoutedAttemptSend(logCtx.usageLogInputTokens, recovery);
|
|
150
163
|
const runTurnProviderFetch = providerFetch(
|
|
151
164
|
route.provider,
|
|
152
165
|
options.codexWsRuntimeIdentity,
|
|
@@ -169,6 +182,14 @@ export async function executeResponsesRunTurn(
|
|
|
169
182
|
// The only way the request budget reaches a transport the adapter owns. Without it
|
|
170
183
|
// a Cursor turn's inner ladder was three physical sends the cap read as one.
|
|
171
184
|
...(adapterDispatchBudget ? { sendBudget: adapterDispatchBudget } : {}),
|
|
185
|
+
onPhysicalSend: send => noteAdapterPhysicalSend(
|
|
186
|
+
logCtx.usageLogInputTokens,
|
|
187
|
+
// The attempt's own recovery kind still labels its first send when the adapter
|
|
188
|
+
// does not supply one of its own.
|
|
189
|
+
{ ...send, ...(send.recovery ?? recovery ? { recovery: send.recovery ?? recovery } : {}) },
|
|
190
|
+
{ includeFirst: reportsOwnSends },
|
|
191
|
+
),
|
|
192
|
+
onRecoveryWithheld: noteAdapterRecoveryWithheld,
|
|
172
193
|
},
|
|
173
194
|
targetQueue.push,
|
|
174
195
|
);
|
|
@@ -273,6 +294,7 @@ export async function executeResponsesRunTurn(
|
|
|
273
294
|
route.modelId,
|
|
274
295
|
route.provider,
|
|
275
296
|
inboundWire,
|
|
297
|
+
route.staticPolicy,
|
|
276
298
|
);
|
|
277
299
|
const rotatedAdapter = resolveSelectionAdapter(rotatedProvider, config.cacheRetention);
|
|
278
300
|
if (!rotatedAdapter.runTurn) {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
clampImageMaxRounds,
|
|
18
18
|
} from "../../images";
|
|
19
19
|
import type { ProviderAdapter } from "../../adapters/base";
|
|
20
|
+
import type { OcxParsedRequest } from "../../types";
|
|
20
21
|
import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
|
|
21
22
|
import {
|
|
22
23
|
GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST,
|
|
@@ -155,6 +156,7 @@ export async function executeResponsesSidecars(
|
|
|
155
156
|
const rotateSidecarProviderOn429 = async (
|
|
156
157
|
retryAfter: string | null,
|
|
157
158
|
responseHeaders?: Headers,
|
|
159
|
+
retryParsed?: OcxParsedRequest,
|
|
158
160
|
): Promise<ProviderAdapter | null> => {
|
|
159
161
|
const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
|
|
160
162
|
retryAfter,
|
|
@@ -196,7 +198,7 @@ export async function executeResponsesSidecars(
|
|
|
196
198
|
try {
|
|
197
199
|
const snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId);
|
|
198
200
|
transportState.genericFailovers += 1;
|
|
199
|
-
if (!await applyFailoverSnapshot(snapshot)) {
|
|
201
|
+
if (!await applyFailoverSnapshot(snapshot, retryParsed)) {
|
|
200
202
|
hop.permit?.release();
|
|
201
203
|
return null;
|
|
202
204
|
}
|
|
@@ -254,9 +256,22 @@ export async function executeResponsesSidecars(
|
|
|
254
256
|
return null;
|
|
255
257
|
}
|
|
256
258
|
const rotatedAdapter = resolveSelectionAdapter(
|
|
257
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
259
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
258
260
|
config.cacheRetention,
|
|
259
261
|
);
|
|
262
|
+
// The sidecar loops build each attempt from an iteration-local shallow copy of parsed, so a
|
|
263
|
+
// rebind that lands only on the outer request never reaches the wire: the retry would pair
|
|
264
|
+
// the new bearer with the previous account's Kiro routing metadata and continuation identity.
|
|
265
|
+
// Rebind the exact request the rotated adapter will be built from (same pattern as the
|
|
266
|
+
// continuation loop's nextParsed); when the hook ran without one, the outer bind below stands.
|
|
267
|
+
if (retryParsed && retryParsed !== parsed) {
|
|
268
|
+
bindRouteReasoningReplayScope({
|
|
269
|
+
parsed: retryParsed,
|
|
270
|
+
providerName: route.providerName,
|
|
271
|
+
provider: route.provider,
|
|
272
|
+
adapterName: rotatedAdapter.name,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
260
275
|
bindRouteReasoningReplayScope({
|
|
261
276
|
parsed,
|
|
262
277
|
providerName: route.providerName,
|
|
@@ -29,33 +29,14 @@ export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_M
|
|
|
29
29
|
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Dial URL for a
|
|
33
|
-
*
|
|
34
|
-
* path
|
|
35
|
-
* /v1/responses path get the same fast lane. Plain HTTP remains on SSE because
|
|
36
|
-
* a provider WS handshake would otherwise send credentials and request data
|
|
37
|
-
* without transport encryption.
|
|
32
|
+
* Dial URL for a first-party Responses endpoint. The canonical ChatGPT backend
|
|
33
|
+
* keeps its constant; the api.openai.com Responses endpoint swaps https for wss
|
|
34
|
+
* on the same path. No other upstream may enter the WebSocket lane.
|
|
38
35
|
*/
|
|
39
36
|
function wsUpstreamUrlFor(httpUrl: string): string {
|
|
40
37
|
if (httpUrl === CODEX_RESPONSES_HTTP_URL) return CODEX_RESPONSES_WS_URL;
|
|
41
|
-
return httpUrl.replace(/^http(s?):/, "ws$1:");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* An operator-opted OpenAI-compatible upstream only joins the WS lane for
|
|
46
|
-
* Responses endpoints: the WebSocket path speaks the Responses event protocol,
|
|
47
|
-
* and every downstream consumer (adapter parsers, usage sniffing, SSE relay)
|
|
48
|
-
* assumes that wire. Other paths (chat completions, images, search) stay HTTP.
|
|
49
|
-
*/
|
|
50
|
-
function isResponsesWebsocketEligibleUrl(url: string): boolean {
|
|
51
|
-
let parsed: URL;
|
|
52
|
-
try {
|
|
53
|
-
parsed = new URL(url);
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
return parsed.protocol === "https:"
|
|
58
|
-
&& parsed.pathname.endsWith("/responses");
|
|
38
|
+
if (httpUrl === OPENAI_API_RESPONSES_URL) return httpUrl.replace(/^http(s?):/, "ws$1:");
|
|
39
|
+
throw new Error("unsupported Codex WebSocket upstream");
|
|
59
40
|
}
|
|
60
41
|
export type BunRuntimeIdentity = {
|
|
61
42
|
version: string;
|
|
@@ -108,8 +89,13 @@ export function shouldUseCodexWsUpstream(
|
|
|
108
89
|
): boolean {
|
|
109
90
|
if (!bunSupportsBoundedCodexWsRelay(runtime)) return false;
|
|
110
91
|
if (socks5ProxyFromEnv()) return false;
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
// Bun's client WebSocket API delivers only fully assembled messages and has
|
|
93
|
+
// no enforceable inbound payload limit. Keep arbitrary provider endpoints on
|
|
94
|
+
// bounded HTTP/SSE until the client can reject fragmented text and binary
|
|
95
|
+
// messages during ingestion rather than after allocation. The first-party
|
|
96
|
+
// api.openai.com lane still requires the operator opt-in.
|
|
97
|
+
if (url !== CODEX_RESPONSES_HTTP_URL
|
|
98
|
+
&& !(upstreamWebsocketConfigured && url === OPENAI_API_RESPONSES_URL)) return false;
|
|
113
99
|
if ((init?.method ?? "GET").toUpperCase() !== "POST") return false;
|
|
114
100
|
const body = init?.body;
|
|
115
101
|
if (typeof body !== "string") return false;
|
|
@@ -139,7 +125,8 @@ export function codexWsUpstreamFetch(
|
|
|
139
125
|
const prepared = prepareCodexWsRequest(url, init);
|
|
140
126
|
if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
|
|
141
127
|
init = prepared.httpInit;
|
|
142
|
-
if (
|
|
128
|
+
if ((url !== CODEX_RESPONSES_HTTP_URL && url !== OPENAI_API_RESPONSES_URL)
|
|
129
|
+
|| !bunSupportsBoundedCodexWsRelay(runtime)) {
|
|
143
130
|
return sseFallback(url, init);
|
|
144
131
|
}
|
|
145
132
|
const signal = init.signal ?? undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
2
|
import { mayBecomePatchEnvelope, normalizeApplyPatchDelimiters } from "../responses/apply-patch-envelope";
|
|
3
3
|
import { compileCodeModeHelperInput, resolveCodeModeHelperName } from "../responses/code-mode-helper-compat";
|
|
4
|
+
import { progressiveFreeformInput } from "../responses/progressive-freeform-input";
|
|
4
5
|
import { declaresCodeModeExec } from "../types/tools";
|
|
5
6
|
import {
|
|
6
7
|
customToolItemId,
|
|
@@ -15,50 +16,10 @@ import {
|
|
|
15
16
|
type SseBlockRewrite,
|
|
16
17
|
} from "./sse-payload-rewrite";
|
|
17
18
|
|
|
18
|
-
/** Exact compact prefix used by our upstream rewriter; progressive matching also
|
|
19
|
-
* tolerates insignificant JSON whitespace via FREEFORM_WRAP_PREFIX_RE. */
|
|
20
|
-
const FREEFORM_WRAP_PREFIX = '{"input":"';
|
|
21
|
-
const FREEFORM_WRAP_PREFIX_RE = /^\s*\{\s*"input"\s*:\s*"/;
|
|
22
|
-
|
|
23
19
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
24
20
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
/**
|
|
28
|
-
* Progressive decode of the freeform `{ "input": "…" }` wrapper.
|
|
29
|
-
* Returns null when the accumulated text does not (yet) match that wrapper so
|
|
30
|
-
* callers suppress deltas and rely on `response.custom_tool_call_input.done`.
|
|
31
|
-
*/
|
|
32
|
-
function partialCustomToolInput(argumentsText: string): string | null {
|
|
33
|
-
const match = FREEFORM_WRAP_PREFIX_RE.exec(argumentsText);
|
|
34
|
-
if (!match) return null;
|
|
35
|
-
const body = argumentsText.slice(match[0]!.length);
|
|
36
|
-
let output = "";
|
|
37
|
-
for (let index = 0; index < body.length; index++) {
|
|
38
|
-
const char = body[index];
|
|
39
|
-
if (char === '"') break;
|
|
40
|
-
if (char !== "\\") {
|
|
41
|
-
output += char;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const escaped = body[index + 1];
|
|
45
|
-
if (escaped === undefined) break;
|
|
46
|
-
index += 1;
|
|
47
|
-
if (escaped === "n") output += "\n";
|
|
48
|
-
else if (escaped === "t") output += "\t";
|
|
49
|
-
else if (escaped === "r") output += "\r";
|
|
50
|
-
else if (escaped === "b") output += "\b";
|
|
51
|
-
else if (escaped === "f") output += "\f";
|
|
52
|
-
else if (escaped === "u") {
|
|
53
|
-
const hex = body.slice(index + 1, index + 5);
|
|
54
|
-
if (hex.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) break;
|
|
55
|
-
output += String.fromCharCode(Number.parseInt(hex, 16));
|
|
56
|
-
index += 4;
|
|
57
|
-
} else output += escaped;
|
|
58
|
-
}
|
|
59
|
-
return output;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
23
|
function replaceSseEventName(block: string, type: string): string {
|
|
63
24
|
const newline = block.includes("\r\n") ? "\r\n" : "\n";
|
|
64
25
|
const lines = block.split(/\r?\n/);
|
|
@@ -332,21 +293,33 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
332
293
|
openCalls.set(upstreamItemId, open);
|
|
333
294
|
// A helper alias will become JavaScript at completion, never raw patch/JSON.
|
|
334
295
|
if (itemNames.get(upstreamItemId)?.aliased) return [];
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const fullInput =
|
|
296
|
+
const itemName = itemNames.get(upstreamItemId);
|
|
297
|
+
const ownsFreeformGrammar = itemName?.namespace === undefined
|
|
298
|
+
|| itemName?.namespace === "functions";
|
|
299
|
+
const fullInput = progressiveFreeformInput(
|
|
300
|
+
open.argumentsText,
|
|
301
|
+
ownsFreeformGrammar ? itemName?.name ?? "" : "",
|
|
302
|
+
);
|
|
339
303
|
if (fullInput === null) return [];
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
304
|
+
// This routed path historically holds every unrecognized JSON object until done.
|
|
305
|
+
// The shared decoder streams ordinary raw input, so retain the stricter routed rule
|
|
306
|
+
// when no recognized wrapper transformed the accumulated object.
|
|
307
|
+
if (fullInput === open.argumentsText && open.argumentsText.trimStart().startsWith("{")) return [];
|
|
308
|
+
// Hold a buffer that could still become a complete patch envelope, for either of the two
|
|
309
|
+
// reasons completion rewrites one. Both are the same rewind this path forbids, and both
|
|
310
|
+
// mirror `src/bridge/sse.ts`.
|
|
311
|
+
//
|
|
312
|
+
// `exec`: the done event recompiles such a body into an apply_patch helper call.
|
|
313
|
+
// `apply_patch`: `normalizeApplyPatchDelimiters` rewrites a decorated
|
|
314
|
+
// `*** Begin Patch ***` envelope at completion, so the decorated markers would be
|
|
315
|
+
// published and then replaced by the normalized ones. Before the shared decoder, this
|
|
316
|
+
// path held every non-canonical shape and so never reached that case; now that ordinary
|
|
317
|
+
// raw input streams, the second reason has to be stated explicitly.
|
|
318
|
+
const mayCompile = declaresCodeModeExec(declaredNames)
|
|
319
|
+
&& itemName?.namespace === undefined
|
|
320
|
+
&& itemName?.name === "exec";
|
|
321
|
+
const mayNormalize = ownsFreeformGrammar && itemName?.name === "apply_patch";
|
|
322
|
+
if ((mayCompile || mayNormalize) && mayBecomePatchEnvelope(fullInput)) return [];
|
|
350
323
|
if (!fullInput.startsWith(open.emittedInput) || fullInput.length === open.emittedInput.length) return [];
|
|
351
324
|
const inputDelta = fullInput.slice(open.emittedInput.length);
|
|
352
325
|
open.emittedInput = fullInput;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
collectAmbiguousDottedAliases,
|
|
3
|
+
dottedAliasIsUnambiguous,
|
|
4
|
+
isSchemaValidResponsesToolName,
|
|
5
|
+
wireToolInnerName,
|
|
6
|
+
} from "../responses/tool-name-aliases";
|
|
2
7
|
import {
|
|
3
8
|
dottedToolName,
|
|
4
9
|
NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES,
|
|
@@ -512,6 +517,31 @@ export function normalizeDefaultNamespaceInItem(
|
|
|
512
517
|
return { value: { ...item, name: bare }, changed: true };
|
|
513
518
|
}
|
|
514
519
|
}
|
|
520
|
+
// Authorization and emission were reading two different resolvers for the same question.
|
|
521
|
+
// `undeclaredNameInItem` resolves an emitted name through `normalizeDeclaredToolName`, which
|
|
522
|
+
// maps a `default.`-prefixed code-mode helper onto the declared `exec` (#4412) as well as a
|
|
523
|
+
// `default.`-prefixed bare tool (#4176); the rewrite above learned only the second case. So a
|
|
524
|
+
// routed Muse turn under a code-mode catalog had `default.view_image` ACCEPTED as `exec` and
|
|
525
|
+
// then relayed under the name the upstream schema rejects. Codex stored
|
|
526
|
+
// `{"type":"function_call","name":"default.view_image"}`, answered "unsupported call", and
|
|
527
|
+
// every later replay of that history — a side chat, a compaction — was refused on
|
|
528
|
+
// `input[N].name` for the lifetime of the conversation (#5095).
|
|
529
|
+
//
|
|
530
|
+
// Emit the name the guard authorized rather than a second, weaker opinion about it. The two
|
|
531
|
+
// must agree: a name good enough to admit is the name the client has to receive.
|
|
532
|
+
//
|
|
533
|
+
// Gated on the name actually being unusable, so this branch cannot touch a name the upstream
|
|
534
|
+
// accepts whatever the resolver would have said about it. The routed-custom-tool and
|
|
535
|
+
// namespace restores run earlier in the same chain and already rewrite the shapes they own,
|
|
536
|
+
// which leaves this as the boundary check for the names no restore claimed.
|
|
537
|
+
if (!isSchemaValidResponsesToolName(name)) {
|
|
538
|
+
const authorized = normalizeDeclaredToolName(name, declared, declaredBare);
|
|
539
|
+
if (
|
|
540
|
+
authorized !== name
|
|
541
|
+
&& declared.has(authorized)
|
|
542
|
+
&& isSchemaValidResponsesToolName(authorized)
|
|
543
|
+
) return { value: { ...item, name: authorized }, changed: true };
|
|
544
|
+
}
|
|
515
545
|
}
|
|
516
546
|
return { value: item, changed: false };
|
|
517
547
|
}
|
|
@@ -230,7 +230,7 @@ export function replaceSseDataPayload(block: string, payload: string): string {
|
|
|
230
230
|
continue;
|
|
231
231
|
}
|
|
232
232
|
if (!replaced) {
|
|
233
|
-
rewritten.push(`data: ${
|
|
233
|
+
rewritten.push(...payload.split(/\r?\n/).map(line => `data: ${line}`));
|
|
234
234
|
replaced = true;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
@@ -116,6 +116,88 @@ function Start-OcxCommand([string[]]$CommandArgs, [switch]$TrackExit) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function Parse-StartupHealthText([string]$Text) {
|
|
120
|
+
foreach ($line in ($Text -split "\r?\n")) {
|
|
121
|
+
$trimmed = $line.Trim()
|
|
122
|
+
if (-not $trimmed) { continue }
|
|
123
|
+
try {
|
|
124
|
+
$parsed = $trimmed | ConvertFrom-Json
|
|
125
|
+
if ($parsed.status -in @("native", "protected", "at-risk") -and ($parsed.rebootSafe -is [bool])) {
|
|
126
|
+
return $parsed
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
# A non-JSON line from the CLI (for example a diagnostic banner) is not a
|
|
130
|
+
# startup-health payload; keep scanning for the actual JSON result.
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return $null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Start-StartupHealthProbe {
|
|
138
|
+
# Startup safety is machine-local diagnostic state, so the tray asks the CLI to
|
|
139
|
+
# collect it directly (ocx __startup-health) instead of poking the management API,
|
|
140
|
+
# which is admin-token gated. The CLI collects the diagnostic straight from the OS,
|
|
141
|
+
# so it works without the proxy answering and without any credential.
|
|
142
|
+
#
|
|
143
|
+
# The child writes into redirected pipes that the tick reads asynchronously: only
|
|
144
|
+
# the completed Task<string> is ever touched, so a slow or hung diagnostic can never
|
|
145
|
+
# block the Windows Forms UI thread, and its lifetime is bounded by a kill in the
|
|
146
|
+
# tick that lets the next refresh replace it instead of stacking another process.
|
|
147
|
+
try {
|
|
148
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
149
|
+
$psi.FileName = $BunPath
|
|
150
|
+
$psi.Arguments = ((@($CliPath, "__startup-health") | ForEach-Object { ConvertTo-NativeArgument $_ }) -join " ")
|
|
151
|
+
$psi.UseShellExecute = $false
|
|
152
|
+
$psi.CreateNoWindow = $true
|
|
153
|
+
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
154
|
+
$psi.RedirectStandardOutput = $true
|
|
155
|
+
$psi.RedirectStandardError = $true
|
|
156
|
+
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
|
157
|
+
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
|
|
158
|
+
$psi.EnvironmentVariables["CODEX_HOME"] = $CodexHome
|
|
159
|
+
$psi.EnvironmentVariables["OPENCODEX_HOME"] = $OpenCodexHome
|
|
160
|
+
if ($BunRuntimeSource) {
|
|
161
|
+
$psi.EnvironmentVariables["OCX_BUN_RUNTIME_SOURCE"] = $BunRuntimeSource
|
|
162
|
+
$psi.EnvironmentVariables["OCX_BUN_RUNTIME_PATH"] = $BunPath
|
|
163
|
+
}
|
|
164
|
+
$process = [System.Diagnostics.Process]::Start($psi)
|
|
165
|
+
$script:startupProbeProcess = $process
|
|
166
|
+
# Drain both pipes asynchronously from the start so neither can fill and stall
|
|
167
|
+
# the child while the tick is only watching the output task's completion.
|
|
168
|
+
$script:startupProbeOutputTask = $process.StandardOutput.ReadToEndAsync()
|
|
169
|
+
$script:startupProbeErrorTask = $process.StandardError.ReadToEndAsync()
|
|
170
|
+
$script:startupProbeStarted = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
171
|
+
} catch {
|
|
172
|
+
Write-ActionLog "startup-health probe launch failed: $($_.Exception.GetType().Name)"
|
|
173
|
+
if ($null -ne $script:startupProbeProcess) {
|
|
174
|
+
# Process.Start succeeded but the async pipe setup failed: Dispose alone
|
|
175
|
+
# would leave the Bun child running, so terminate it before cleanup.
|
|
176
|
+
try {
|
|
177
|
+
$script:startupProbeProcess.Kill()
|
|
178
|
+
[void]$script:startupProbeProcess.WaitForExit(3000)
|
|
179
|
+
} catch {
|
|
180
|
+
Write-ActionLog "startup-health probe launch cleanup failed: $($_.Exception.GetType().Name)"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
Complete-StartupHealthProbe
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function Complete-StartupHealthProbe {
|
|
188
|
+
$process = $script:startupProbeProcess
|
|
189
|
+
$script:startupProbeProcess = $null
|
|
190
|
+
$script:startupProbeOutputTask = $null
|
|
191
|
+
$script:startupProbeErrorTask = $null
|
|
192
|
+
if ($null -ne $process) {
|
|
193
|
+
try {
|
|
194
|
+
$process.Dispose()
|
|
195
|
+
} catch {
|
|
196
|
+
Write-ActionLog "startup-health probe dispose failed: $($_.Exception.GetType().Name)"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
119
201
|
function Read-ListenTarget {
|
|
120
202
|
foreach ($path in @((Join-Path $OpenCodexHome "runtime-port.json"), (Join-Path $OpenCodexHome "config.json"))) {
|
|
121
203
|
try {
|
|
@@ -172,6 +254,15 @@ $exitItem = $menu.Items.Add("Exit Tray")
|
|
|
172
254
|
$script:online = $false
|
|
173
255
|
$script:port = 10100
|
|
174
256
|
$script:proxyPid = $null
|
|
257
|
+
$script:wasOnline = $false
|
|
258
|
+
$script:startupHealth = $null
|
|
259
|
+
$script:startupHealthCheckedAt = 0L
|
|
260
|
+
$script:startupRefreshMs = 20000L
|
|
261
|
+
$script:startupProbeProcess = $null
|
|
262
|
+
$script:startupProbeOutputTask = $null
|
|
263
|
+
$script:startupProbeErrorTask = $null
|
|
264
|
+
$script:startupProbeStarted = 0L
|
|
265
|
+
$script:startupProbeTimeoutMs = 30000L
|
|
175
266
|
$script:pendingAction = $null
|
|
176
267
|
$script:pendingStarted = 0L
|
|
177
268
|
$script:pendingDeadline = 0L
|
|
@@ -228,18 +319,66 @@ function Update-TrayState {
|
|
|
228
319
|
$pidMatches = $null -eq $target.pid -or [int]$target.pid -eq [int]$health.pid
|
|
229
320
|
$script:online = $null -ne $health -and $health.status -eq "ok" -and $health.service -eq "opencodex" -and [int]$health.port -eq $script:port -and $pidMatches
|
|
230
321
|
$script:proxyPid = if ($script:online) { [int]$health.pid } else { $null }
|
|
322
|
+
$cameOnline = $script:online -and -not $script:wasOnline
|
|
323
|
+
$script:wasOnline = $script:online
|
|
324
|
+
$now = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
325
|
+
if ($null -ne $script:startupProbeProcess) {
|
|
326
|
+
$probeExited = $false
|
|
327
|
+
try {
|
|
328
|
+
$probeExited = $script:startupProbeProcess.HasExited
|
|
329
|
+
} catch {
|
|
330
|
+
$probeExited = $true
|
|
331
|
+
}
|
|
332
|
+
$probeTimedOut = ($now - $script:startupProbeStarted) -gt $script:startupProbeTimeoutMs
|
|
333
|
+
if ($probeExited -and $null -ne $script:startupProbeOutputTask -and $script:startupProbeOutputTask.IsCompleted) {
|
|
334
|
+
$probeStartup = $null
|
|
335
|
+
try {
|
|
336
|
+
$probeStartup = Parse-StartupHealthText $script:startupProbeOutputTask.Result
|
|
337
|
+
} catch {
|
|
338
|
+
Write-ActionLog "startup-health probe read failed: $($_.Exception.GetType().Name)"
|
|
339
|
+
}
|
|
340
|
+
$script:startupHealth = $probeStartup
|
|
341
|
+
Complete-StartupHealthProbe
|
|
342
|
+
$script:startupHealthCheckedAt = $now
|
|
343
|
+
} elseif ($probeTimedOut) {
|
|
344
|
+
# A hung diagnostic must not survive its refresh slot: terminate it so the
|
|
345
|
+
# next refresh cannot stack another orphaned Bun process behind it.
|
|
346
|
+
Write-ActionLog "startup-health probe timed out; terminating it"
|
|
347
|
+
try {
|
|
348
|
+
$script:startupProbeProcess.Kill()
|
|
349
|
+
[void]$script:startupProbeProcess.WaitForExit(3000)
|
|
350
|
+
} catch {
|
|
351
|
+
Write-ActionLog "startup-health probe kill failed: $($_.Exception.GetType().Name)"
|
|
352
|
+
}
|
|
353
|
+
$script:startupHealth = $null
|
|
354
|
+
Complete-StartupHealthProbe
|
|
355
|
+
$script:startupHealthCheckedAt = $now
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
$refreshDue =
|
|
359
|
+
$script:startupHealthCheckedAt -eq 0 -or
|
|
360
|
+
($now - $script:startupHealthCheckedAt -gt $script:startupRefreshMs)
|
|
361
|
+
if ($script:online -and ($cameOnline -or $refreshDue) -and $null -eq $script:startupProbeProcess) {
|
|
362
|
+
# Record the attempt so launch failures and invalid results remain throttled.
|
|
363
|
+
$script:startupHealthCheckedAt = $now
|
|
364
|
+
Start-StartupHealthProbe
|
|
365
|
+
}
|
|
231
366
|
if ($script:online) {
|
|
232
367
|
$statusItem.Text = "Proxy: Online (port $($script:port))"
|
|
233
368
|
$notify.Text = "opencodex: Online"
|
|
234
369
|
$startItem.Enabled = $false
|
|
235
370
|
$stopItem.Enabled = $true
|
|
236
371
|
$restartItem.Enabled = $true
|
|
237
|
-
|
|
238
|
-
|
|
372
|
+
# The management API is admin-token gated, so the tray must not poke
|
|
373
|
+
# /api/startup-health for the icon. The probe lifecycle maintenance and the
|
|
374
|
+
# refresh gate above run on every tick (even while offline) so a hung
|
|
375
|
+
# diagnostic is cleaned up outside the online-only UI branch.
|
|
376
|
+
$startup = $script:startupHealth
|
|
377
|
+
if ($null -ne $startup) {
|
|
239
378
|
$label = if ($startup.status -eq "at-risk") { "At risk" } elseif ($startup.status -eq "protected") { "Protected" } else { "Native routing" }
|
|
240
379
|
$safetyItem.Text = "Restart safety: $label"
|
|
241
380
|
$notify.Icon = if ($startup.status -eq "at-risk") { $warningIcon } else { $onlineIcon }
|
|
242
|
-
}
|
|
381
|
+
} else {
|
|
243
382
|
$safetyItem.Text = "Restart safety: unavailable"
|
|
244
383
|
$notify.Icon = $warningIcon
|
|
245
384
|
}
|
|
@@ -360,6 +499,19 @@ try {
|
|
|
360
499
|
if ($null -ne $script:pendingProcess) {
|
|
361
500
|
try { $script:pendingProcess.Dispose() } catch { $null = $_ }
|
|
362
501
|
}
|
|
502
|
+
if ($null -ne $script:startupProbeProcess) {
|
|
503
|
+
# A probe still running when the tray shuts down would outlive it: Dispose()
|
|
504
|
+
# releases the handle but does not terminate the child, so kill the active
|
|
505
|
+
# probe, wait briefly for exit, then release the probe state.
|
|
506
|
+
try {
|
|
507
|
+
Write-ActionLog "terminating active startup-health probe on tray shutdown"
|
|
508
|
+
$script:startupProbeProcess.Kill()
|
|
509
|
+
[void]$script:startupProbeProcess.WaitForExit(3000)
|
|
510
|
+
} catch {
|
|
511
|
+
Write-ActionLog "startup-health probe shutdown kill failed: $($_.Exception.GetType().Name)"
|
|
512
|
+
}
|
|
513
|
+
Complete-StartupHealthProbe
|
|
514
|
+
}
|
|
363
515
|
$notify.Dispose()
|
|
364
516
|
foreach ($icon in $script:ownedIcons) { $icon.Dispose() }
|
|
365
517
|
$menu.Dispose()
|
package/src/types/config.ts
CHANGED
|
@@ -403,6 +403,8 @@ export interface OcxConfig {
|
|
|
403
403
|
client?: OcxClientConnectionConfig;
|
|
404
404
|
/** Operator-facing redaction policy for management and CLI projections. */
|
|
405
405
|
privacy?: OcxPrivacyConfig;
|
|
406
|
+
/** Opt-in process-local aggregate request metrics on the authenticated management plane. */
|
|
407
|
+
metricsExport?: { enabled?: boolean };
|
|
406
408
|
/** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
|
|
407
409
|
emptyCompletionRetry?: boolean;
|
|
408
410
|
/** Suppress allowlisted client-facing Codex transport hints; provider enforcement is unchanged. */
|
|
@@ -616,6 +618,12 @@ export interface OcxConfig {
|
|
|
616
618
|
subagentEffortCap?: string;
|
|
617
619
|
/** Global model effort overrides, after provider model/wide pins; none means omission. */
|
|
618
620
|
modelPinnedEfforts?: Record<string, string>;
|
|
621
|
+
compactionRouting?: {
|
|
622
|
+
model: string;
|
|
623
|
+
reasoningEffort?: string;
|
|
624
|
+
/** Compaction triggers this override covers; omission means `["manual"]`. */
|
|
625
|
+
triggers?: ("manual" | "auto")[];
|
|
626
|
+
};
|
|
619
627
|
/**
|
|
620
628
|
* Models hidden from Codex discovery without blocking direct proxy calls. Routed provider ids
|
|
621
629
|
* are excluded from the catalog + /v1/models entirely. Account-qualified native ids hide only
|
|
@@ -753,9 +761,9 @@ export interface OcxConfig {
|
|
|
753
761
|
* into ALL_PROXY, clear inherited HTTP(S)_PROXY, and use OpenCodex's SOCKS5 transport.
|
|
754
762
|
* Loopback stays in NO_PROXY.
|
|
755
763
|
* The literal `"auto"` reads the Windows WinINET static proxy (`ProxyEnable`/`ProxyServer`)
|
|
756
|
-
* once at process start
|
|
757
|
-
* or unreadable, it degrades to direct egress with
|
|
758
|
-
* changes are not followed.
|
|
764
|
+
* once at process start, preserving separate HTTP and HTTPS entries; on other platforms, or
|
|
765
|
+
* when the system proxy is off, SOCKS-only, or unreadable, it degrades to direct egress with
|
|
766
|
+
* one log line (#1525). PAC/WPAD and live changes are not followed.
|
|
759
767
|
*/
|
|
760
768
|
proxy?: string;
|
|
761
769
|
/**
|
package/src/types/provider.ts
CHANGED
|
@@ -356,6 +356,22 @@ export interface OcxProviderConfig {
|
|
|
356
356
|
* `ocxr1` envelopes are still stripped because no upstream can decrypt them.
|
|
357
357
|
*/
|
|
358
358
|
preserveResponsesReasoningContent?: boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Treat this provider's `modelReasoningEfforts` as authoritative at the wire, not only in the
|
|
361
|
+
* catalog. Adapters that ship their own per-model effort table (currently `command-code`)
|
|
362
|
+
* otherwise let that table win for models it knows, so a widened row is advertised in the
|
|
363
|
+
* picker and then stripped on the way out. Opt-in because presets are SEEDED with the shipped
|
|
364
|
+
* table: without a declared flag there is no way to tell an operator's row from a copy an
|
|
365
|
+
* older release persisted. A rung the upstream then refuses is returned as that error rather
|
|
366
|
+
* than silently retried without the effort, since the operator asked for it.
|
|
367
|
+
*/
|
|
368
|
+
modelReasoningEffortsAuthoritative?: boolean;
|
|
369
|
+
/**
|
|
370
|
+
* Drop replayed Responses `reasoning` items from input history before forwarding.
|
|
371
|
+
* Some OpenAI-compatible Responses upstreams accept tool-call replay but reject
|
|
372
|
+
* reasoning output items when they are sent back on a continuation.
|
|
373
|
+
*/
|
|
374
|
+
dropResponsesReasoningItems?: boolean;
|
|
359
375
|
/**
|
|
360
376
|
* Explicit opt-in for a relay that genuinely fronts OpenAI and can decode native
|
|
361
377
|
* compaction blobs. Absent or false degrades foreign blobs to an opaque note.
|
|
@@ -901,6 +917,8 @@ export interface OcxProviderConfig {
|
|
|
901
917
|
* "cloud-code-assist" = Google Antigravity (Cloud Code Assist) OAuth + CCA envelope.
|
|
902
918
|
*/
|
|
903
919
|
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
|
|
920
|
+
/** Google tool-schema compatibility policy. Omitted preserves compatible report-only behavior. */
|
|
921
|
+
googleToolSchemaPolicy?: "compatible" | "reject-lossy";
|
|
904
922
|
/** Vertex AI GCP project id (or GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env). */
|
|
905
923
|
project?: string;
|
|
906
924
|
/** Vertex AI location, e.g. "us-central1" or "global" (or GOOGLE_CLOUD_LOCATION env). */
|
package/src/types/request.ts
CHANGED
|
@@ -126,6 +126,8 @@ export interface OcxParsedRequest {
|
|
|
126
126
|
* (see src/responses/compaction.ts).
|
|
127
127
|
*/
|
|
128
128
|
_compactionRequest?: boolean;
|
|
129
|
+
/** Manual compaction moved to another provider: summarize portably even on a canonical ChatGPT target. */
|
|
130
|
+
_portableCompaction?: boolean;
|
|
129
131
|
/**
|
|
130
132
|
* True when the current request newly introduced a stored compaction summary/marker. Historical
|
|
131
133
|
* markers restored by previous_response_id expansion were already acknowledged and do not reset
|
package/src/types/tools.ts
CHANGED
|
@@ -67,6 +67,20 @@ const CODE_MODE_HELPER_TOOL_NAMES = [
|
|
|
67
67
|
*/
|
|
68
68
|
export const CODE_MODE_EXEC_TOOL_NAME = "exec";
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The nested-helper spellings, as a membership view of the same list.
|
|
72
|
+
*
|
|
73
|
+
* A code-mode catalog never DECLARES any of them — they exist only as `tools.<helper>(...)` inside
|
|
74
|
+
* `exec` — so a recorded call under one of these names can only have come from a provider echoing
|
|
75
|
+
* the helper, which is what makes the set usable as a bounded recovery vocabulary for stored
|
|
76
|
+
* history (#5095). Kept beside the tuple it is built from so the two can never drift; this is a
|
|
77
|
+
* different question from `NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES` below, which also covers `exec`
|
|
78
|
+
* itself because declaring THAT name is what turns normalization on.
|
|
79
|
+
*/
|
|
80
|
+
export const CODE_MODE_HELPER_WIRE_NAMES: ReadonlySet<string> = new Set<string>(
|
|
81
|
+
CODE_MODE_HELPER_TOOL_NAMES,
|
|
82
|
+
);
|
|
83
|
+
|
|
70
84
|
/**
|
|
71
85
|
* Spellings that may never be MANUFACTURED as a bare alias for a namespaced tool.
|
|
72
86
|
*
|