@bitkyc08/opencodex 2.33.0 → 2.34.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 +3 -3
- package/gui/dist/assets/index-C4TMRloX.js +112 -0
- package/gui/dist/assets/index-DQ-Ie18T.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +29 -6
- package/src/adapters/cursor/tool-definitions.ts +11 -2
- package/src/adapters/cursor/tool-result-normalize.ts +32 -1
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +189 -37
- package/src/adapters/google-antigravity-replay.ts +102 -23
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +115 -16
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +334 -24
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -24
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +32 -4
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +46 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +107 -9
- package/src/generated/compatibility-version.json +191 -139
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/types.ts +15 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +160 -68
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +4 -0
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +77 -11
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/provider-routes.ts +6 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +41 -15
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +494 -114
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +2 -1
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/types/config.ts +53 -0
- package/src/types/provider.ts +64 -0
- package/src/types/request.ts +11 -3
- package/src/types/tools.ts +7 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +3 -0
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-23-Lf7jR.js +0 -102
- package/gui/dist/assets/index-DxJMDyOr.css +0 -1
package/src/types/config.ts
CHANGED
|
@@ -239,6 +239,11 @@ export interface OcxClientIntegrationsConfig {
|
|
|
239
239
|
"claude-desktop"?: boolean;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
export interface OcxConfigRebaseProvenance {
|
|
243
|
+
version: 1;
|
|
244
|
+
deletedTopLevelKeys: string[];
|
|
245
|
+
}
|
|
246
|
+
|
|
242
247
|
export interface OcxConfig {
|
|
243
248
|
port: number;
|
|
244
249
|
/** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
|
|
@@ -260,6 +265,22 @@ export interface OcxConfig {
|
|
|
260
265
|
managementUsageMaxReadBytes?: number;
|
|
261
266
|
providers: Record<string, OcxProviderConfig>;
|
|
262
267
|
defaultProvider: string;
|
|
268
|
+
/** Persisted state for newly discovered provider models (#2464). Absent keeps legacy "on" behavior. */
|
|
269
|
+
modelDiscovery?: {
|
|
270
|
+
newModelPolicy?: "on" | "off";
|
|
271
|
+
knownModels?: Record<string, {
|
|
272
|
+
ids: string[];
|
|
273
|
+
removed: string[];
|
|
274
|
+
updatedAt: string;
|
|
275
|
+
/** Consecutive successful discoveries in which an active id was absent. */
|
|
276
|
+
missing?: Record<string, number>;
|
|
277
|
+
}>;
|
|
278
|
+
recentArrivals?: Record<string, Array<{ id: string; at: string }>>;
|
|
279
|
+
};
|
|
280
|
+
/** Enable the shipped model alias patterns for providers without an override. */
|
|
281
|
+
defaultModelAliases?: boolean;
|
|
282
|
+
/** Explicit top-level deletion intent used by stale whole-config rebases. */
|
|
283
|
+
configRebaseProvenance?: OcxConfigRebaseProvenance | Record<string, unknown>;
|
|
263
284
|
/** OpenAI provider-contract migration marker (v2 = single `openai` provider with account mode). */
|
|
264
285
|
openaiProviderTierVersion?: 1 | 2;
|
|
265
286
|
/** One-time migration marker for Antigravity's static-catalog defaults. */
|
|
@@ -483,6 +504,13 @@ export interface OcxConfig {
|
|
|
483
504
|
* those are unset — Bun's fetch honors them for all outbound calls; localhost is excluded.
|
|
484
505
|
*/
|
|
485
506
|
proxy?: string;
|
|
507
|
+
/**
|
|
508
|
+
* Hosts that bypass `proxy` for OpenCodex's own outbound provider calls, merged into
|
|
509
|
+
* NO_PROXY at startup. Accepts a comma-separated string (NO_PROXY syntax) or an array.
|
|
510
|
+
* Loopback is always excluded regardless of this setting, and an inherited NO_PROXY is
|
|
511
|
+
* preserved — this ADDS entries, it never replaces the environment.
|
|
512
|
+
*/
|
|
513
|
+
noProxy?: string | string[];
|
|
486
514
|
/**
|
|
487
515
|
* Upstream stall timeout (seconds). After this many seconds of no upstream data, emits
|
|
488
516
|
* response.incomplete. Default 300. Min 1.
|
|
@@ -554,6 +582,15 @@ export interface OcxConfig {
|
|
|
554
582
|
* selector map remains visible for compatibility with hand-written configurations.
|
|
555
583
|
*/
|
|
556
584
|
codexAccountPickerEnabled?: boolean;
|
|
585
|
+
/**
|
|
586
|
+
* Show the GPT-5.3-Codex-Spark weekly window on Codex quota surfaces. Default false.
|
|
587
|
+
*
|
|
588
|
+
* Spark is a single-model window that reads 0% for most operators, and on a multi-account
|
|
589
|
+
* pool it doubles the bar count for information almost nobody acts on. Hidden by default and
|
|
590
|
+
* revealed by an explicit `true`; a malformed value reads as hidden rather than rejecting the
|
|
591
|
+
* whole config.
|
|
592
|
+
*/
|
|
593
|
+
showCodexSparkQuota?: boolean;
|
|
557
594
|
/** Active pool account id for next session. undefined = main (passthrough as-is). */
|
|
558
595
|
activeCodexAccountId?: string;
|
|
559
596
|
/** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
|
|
@@ -583,6 +620,22 @@ export interface OcxConfig {
|
|
|
583
620
|
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
|
584
621
|
stickyLimit?: number;
|
|
585
622
|
};
|
|
623
|
+
/**
|
|
624
|
+
* Generic OAuth multi-account 429 failover (#2568). Presence-driven by default.
|
|
625
|
+
*
|
|
626
|
+
* Rotates to another logged-in account of the SAME provider when one is rate-limited, for
|
|
627
|
+
* OAuth providers that have no pool of their own — xAI, Cursor, Kimi, GitHub Copilot,
|
|
628
|
+
* Antigravity, Nous. The Codex pool and the Anthropic pool own their own rotation and are
|
|
629
|
+
* excluded; this setting changes neither.
|
|
630
|
+
*
|
|
631
|
+
* With the key absent, rotation activates when a provider has 2 or more eligible stored
|
|
632
|
+
* accounts — the same consent rule API-key pools already apply to a 2+ key pool (#2568d). A
|
|
633
|
+
* single account is a strict no-op. Set `false` to keep strict single-account behaviour;
|
|
634
|
+
* `providers.<name>.oauthAccountFailover` overrides this per provider.
|
|
635
|
+
*/
|
|
636
|
+
oauthAccountFailover?: {
|
|
637
|
+
enabled?: boolean;
|
|
638
|
+
};
|
|
586
639
|
/** Virtual `combo/<id>` models spanning concrete provider/model targets (issue #133). */
|
|
587
640
|
combos?: Record<string, OcxComboConfig>;
|
|
588
641
|
/**
|
package/src/types/provider.ts
CHANGED
|
@@ -115,6 +115,15 @@ export interface TierObservationContext {
|
|
|
115
115
|
fastWire: FastWire | null;
|
|
116
116
|
demandDecision: "force-fast" | "force-default" | "inherit";
|
|
117
117
|
callerTier?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Whether the destination's echoed `service_tier` is authoritative about Fast scheduling.
|
|
120
|
+
*
|
|
121
|
+
* The ChatGPT-internal Codex backend returns `service_tier: "default"` on turns that were in
|
|
122
|
+
* fact scheduled as priority, so treating its echo as a downgrade produced a false
|
|
123
|
+
* `response-declined` on every Fast request (#2558). Absent means "assume authoritative",
|
|
124
|
+
* preserving the behaviour for the public API where the echo does mean what it says.
|
|
125
|
+
*/
|
|
126
|
+
responseTierAuthoritative?: boolean;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
export type TierDecision =
|
|
@@ -127,6 +136,12 @@ export type TierDecision =
|
|
|
127
136
|
* retries are allowed; OAuth/forward credentials and local runtimes are never replayed.
|
|
128
137
|
*/
|
|
129
138
|
export interface OcxProviderConfig {
|
|
139
|
+
/** Optional short provider namespace used only at request/catalog presentation time. */
|
|
140
|
+
alias?: string;
|
|
141
|
+
/** Native model id -> short, slash-free request alias. */
|
|
142
|
+
modelAliases?: Record<string, string>;
|
|
143
|
+
/** Override the global built-in model-alias switch for this provider. */
|
|
144
|
+
defaultAliases?: boolean;
|
|
130
145
|
adapter: string;
|
|
131
146
|
/**
|
|
132
147
|
* Codex tool calling mode for routed models.
|
|
@@ -266,6 +281,33 @@ export interface OcxProviderConfig {
|
|
|
266
281
|
* full set so the user can pick). See devlog issue_052_provider-model-allowlist.
|
|
267
282
|
*/
|
|
268
283
|
selectedModels?: string[];
|
|
284
|
+
/** Override for newly discovered models. Absent/"inherit" uses the install policy. */
|
|
285
|
+
newModelPolicy?: "on" | "off" | "inherit";
|
|
286
|
+
/**
|
|
287
|
+
* Model-preset marker for `selectedModels` (#2465). Absent means "all", exactly today's
|
|
288
|
+
* semantics — an existing provider is never narrowed by an upgrade.
|
|
289
|
+
*
|
|
290
|
+
* The preset is a SEED, not a lock: `selectedModels` holds concrete ids materialized from
|
|
291
|
+
* the shipped rules, so every existing consumer and older binaries keep working against a
|
|
292
|
+
* plain allowlist. Divergence is detected at the WRITE path rather than by diffing — any user
|
|
293
|
+
* edit while the mode is "preset" flips it to "custom", after which the proxy never
|
|
294
|
+
* re-materializes. That collapses upgrade reconciliation to a version compare.
|
|
295
|
+
*
|
|
296
|
+
* Deliberately distinct from `deriveProviderPresets`, which curates WHICH PROVIDERS to offer.
|
|
297
|
+
* This curates which MODELS a provider exposes; the code says "model preset" throughout.
|
|
298
|
+
*/
|
|
299
|
+
modelPreset?: {
|
|
300
|
+
mode: "preset" | "all" | "custom";
|
|
301
|
+
/** MODEL_PRESETS version materialized into `selectedModels`. */
|
|
302
|
+
appliedVersion?: number;
|
|
303
|
+
appliedAt?: string;
|
|
304
|
+
/**
|
|
305
|
+
* Set when materialization matched nothing and the provider fell back to "all". A preset
|
|
306
|
+
* must never write an empty allowlist, because empty means ALL and would silently
|
|
307
|
+
* un-curate; the fallback marker lets the next convergence retry.
|
|
308
|
+
*/
|
|
309
|
+
fallback?: "preset-empty";
|
|
310
|
+
};
|
|
269
311
|
/** Provider-wide fallback when context metadata is absent; otherwise caps the reported window. */
|
|
270
312
|
contextWindow?: number;
|
|
271
313
|
/** Per-model fallback when context metadata is absent; otherwise caps the reported window. */
|
|
@@ -309,6 +351,16 @@ export interface OcxProviderConfig {
|
|
|
309
351
|
* providers whose registry entry declares authKind "local" (management API enforces).
|
|
310
352
|
*/
|
|
311
353
|
authMode?: "key" | "forward" | "oauth" | "local";
|
|
354
|
+
/**
|
|
355
|
+
* Per-provider override for generic OAuth multi-account 429 failover (#2568).
|
|
356
|
+
*
|
|
357
|
+
* Rotation is presence-driven by default — 2+ logged-in accounts activate it — so this exists
|
|
358
|
+
* for the operator who accepts rotation on one provider and refuses it on another. An explicit
|
|
359
|
+
* boolean here beats the global `oauthAccountFailover` and beats presence.
|
|
360
|
+
*/
|
|
361
|
+
oauthAccountFailover?: {
|
|
362
|
+
enabled?: boolean;
|
|
363
|
+
};
|
|
312
364
|
/** Allow an explicitly key/oauth provider to run without a credential (for keyless local proxies). */
|
|
313
365
|
keyOptional?: boolean;
|
|
314
366
|
/**
|
|
@@ -341,6 +393,18 @@ export interface OcxProviderConfig {
|
|
|
341
393
|
* Responses backend rejects Codex summary-delivery fields for that model.
|
|
342
394
|
*/
|
|
343
395
|
modelSupportsReasoningSummaries?: Record<string, boolean>;
|
|
396
|
+
/**
|
|
397
|
+
* Model-specific Codex Responses verbosity capability. Set false when the upstream ignores
|
|
398
|
+
* `text.verbosity`; the catalog hides the no-op picker and the Responses adapter strips stale
|
|
399
|
+
* or caller-supplied values while preserving other `text` fields.
|
|
400
|
+
*/
|
|
401
|
+
modelSupportsVerbosity?: Record<string, boolean>;
|
|
402
|
+
/**
|
|
403
|
+
* Provider-wide Codex Responses verbosity capability, applied to models the per-model map
|
|
404
|
+
* does not enumerate (a live-discovered id, for example). Materialized from the registry at
|
|
405
|
+
* seed/enrich time so the catalog hint pass never has to read PROVIDER_REGISTRY.
|
|
406
|
+
*/
|
|
407
|
+
supportsVerbosity?: boolean;
|
|
344
408
|
/**
|
|
345
409
|
* Per-model wire value for Responses `stream_options.reasoning_summary_delivery`.
|
|
346
410
|
* Presence also advertises reasoning-summary support for that routed model.
|
package/src/types/request.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface OcxParsedRequest {
|
|
|
68
68
|
_cursorConversationId?: string;
|
|
69
69
|
/** Stable upstream client thread identity, used only to derive provider-scoped continuation ids. */
|
|
70
70
|
_clientThreadId?: string;
|
|
71
|
+
/** Cursor-only thread owner; may be an opaque process-local Desktop session/thread identity. */
|
|
72
|
+
_cursorClientThreadId?: string;
|
|
71
73
|
/** Conversation/provider/account/model-bound namespace for reasoning replay state. */
|
|
72
74
|
_reasoningReplayScope?: OcxReasoningReplayScopeRef;
|
|
73
75
|
/**
|
|
@@ -86,7 +88,7 @@ export interface OcxParsedRequest {
|
|
|
86
88
|
*/
|
|
87
89
|
_cursorIsolateConversation?: boolean;
|
|
88
90
|
/** Account-scoped, non-secret Kiro request metadata selected with the OAuth access token. */
|
|
89
|
-
_kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
|
|
91
|
+
_kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
|
|
90
92
|
/** Provider-private continuation metadata resolved from the Responses previous_response_id chain. */
|
|
91
93
|
_providerContinuation?: OcxProviderContinuationState;
|
|
92
94
|
/** Persisted continuation considered only after the final physical route is known. */
|
|
@@ -188,8 +190,14 @@ export interface OcxImageContent {
|
|
|
188
190
|
detail?: string;
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
export interface OcxVideoContent {
|
|
194
|
+
type: "video";
|
|
195
|
+
/** A base64 `data:` URL from an OpenAI-compatible `video_url` part. */
|
|
196
|
+
videoUrl: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** A user/developer message content part: text or native media. */
|
|
200
|
+
export type OcxContentPart = OcxTextContent | OcxImageContent | OcxVideoContent;
|
|
193
201
|
|
|
194
202
|
export interface OcxThinkingContent {
|
|
195
203
|
type: "thinking";
|
package/src/types/tools.ts
CHANGED
|
@@ -32,16 +32,17 @@ export function namespacedToolName(namespace: string | undefined, name: string):
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Codex
|
|
35
|
+
* Codex unified-exec name normalization.
|
|
36
36
|
*
|
|
37
37
|
* Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
|
|
38
38
|
* description mentions the nested `await tools.exec_command(...)` helper). Routed models —
|
|
39
39
|
* DeepSeek in particular — sometimes echo that helper name as the tool-call name, emitting
|
|
40
|
-
* `exec_command` instead of the declared `exec`. Accept
|
|
41
|
-
* when the request catalog actually declares `exec` and does not itself declare the
|
|
42
|
-
* name (an MCP server may legitimately advertise
|
|
40
|
+
* `exec_command` or `apply_patch` instead of the declared `exec`. Accept these nested helper
|
|
41
|
+
* names only when the request catalog actually declares `exec` and does not itself declare the
|
|
42
|
+
* emitted name (an MCP server may legitimately advertise one under its own namespace).
|
|
43
43
|
*/
|
|
44
44
|
const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
45
|
+
const CODE_MODE_HELPER_TOOL_NAMES = [...LEGACY_SHELL_BRIDGE_TOOL_NAMES, "apply_patch"] as const;
|
|
45
46
|
|
|
46
47
|
export function normalizeDeclaredToolName(
|
|
47
48
|
name: string,
|
|
@@ -49,13 +50,14 @@ export function normalizeDeclaredToolName(
|
|
|
49
50
|
): string {
|
|
50
51
|
if (!declared || !declared.has("exec")) return name;
|
|
51
52
|
if (declared.has(name)) return name;
|
|
53
|
+
if (name === "apply_patch") return "exec";
|
|
52
54
|
// When the catalog explicitly declares any legacy shell bridge name, the environment
|
|
53
55
|
// genuinely exposes that tool — turn normalization off so a call is never mis-routed
|
|
54
56
|
// to `exec`.
|
|
55
57
|
if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
|
|
56
58
|
return name;
|
|
57
59
|
}
|
|
58
|
-
return (
|
|
60
|
+
return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(name) ? "exec" : name;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function toolChoiceAliases(tool: Pick<OcxTool, "namespace" | "name">): string[] {
|
package/src/types.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface ExpectedPriceOverlay {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
42
|
-
const GPT56_SOL: Cost4 = { input:
|
|
42
|
+
const GPT56_SOL: Cost4 = { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 };
|
|
43
43
|
/**
|
|
44
44
|
* Daybreak aliases. `daybreak-*-latest` never appears in the pricing table itself — only its
|
|
45
45
|
* current snapshot does — so these tuples are the snapshot's published rates and carry
|
|
@@ -126,6 +126,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
126
126
|
// Daybreak aliases: priced as their current snapshots (red -> gpt-5.6-cyber,
|
|
127
127
|
// blue -> gpt-5.6-sol). The alias ids carry no rows of their own upstream, hence
|
|
128
128
|
// verified-derived. Blue deliberately reuses GPT56_SOL rather than duplicating the tuple.
|
|
129
|
+
// The `gpt-` selector is native to ChatGPT/Codex accounts. The bare aliases below belong to
|
|
130
|
+
// the separately billed API-key catalog; keep those namespaces disjoint so pricing metadata
|
|
131
|
+
// cannot make an unroutable provider/model pair look supported.
|
|
132
|
+
{ provider: "openai", modelId: "gpt-daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
129
133
|
{ provider: "openai-apikey", modelId: "daybreak-red-latest", cost4: DAYBREAK_RED, source: `alias of gpt-5.6-cyber ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
130
134
|
{ provider: "openai-apikey", modelId: "daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
131
135
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
@@ -223,6 +227,8 @@ export function findExpectedPriceOverlay(
|
|
|
223
227
|
/** OpenAI Fast price multipliers retained as a compatibility export. */
|
|
224
228
|
export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
|
|
225
229
|
"gpt-5.6-sol": 2,
|
|
230
|
+
"gpt-daybreak-blue-latest": 2,
|
|
231
|
+
"daybreak-blue-latest": 2,
|
|
226
232
|
// Post-price-cut Fast tables (https://openai.com/api-fast-mode/, 2026-08-05):
|
|
227
233
|
// Terra 4/24/0.40/5 and Luna 0.40/2.40/0.04/0.50 are both 2× the corrected
|
|
228
234
|
// standard tuples; the stale bases made these look like 1.6/0.4 (#907).
|
|
@@ -257,13 +263,19 @@ const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/pr
|
|
|
257
263
|
*/
|
|
258
264
|
export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
|
|
259
265
|
...["openai", "openai-apikey"].flatMap(provider =>
|
|
260
|
-
Object.entries(PRIORITY_MULTIPLIERS)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
Object.entries(PRIORITY_MULTIPLIERS)
|
|
267
|
+
// Daybreak's `gpt-` selector belongs to ChatGPT/Codex accounts; its bare selector belongs
|
|
268
|
+
// to the separately billed API-key catalog. All ordinary GPT ids remain valid on both.
|
|
269
|
+
.filter(([modelId]) => provider === "openai"
|
|
270
|
+
? modelId !== "daybreak-blue-latest"
|
|
271
|
+
: modelId !== "gpt-daybreak-blue-latest")
|
|
272
|
+
.map(([modelId, multiplier]): PriorityPricingRule => ({
|
|
273
|
+
provider,
|
|
274
|
+
modelId,
|
|
275
|
+
multiplier,
|
|
276
|
+
source: OPENAI_FAST_PRICING,
|
|
277
|
+
verifiedAt: "2026-08-05",
|
|
278
|
+
})),
|
|
267
279
|
),
|
|
268
280
|
...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
|
|
269
281
|
provider: "xai",
|
|
@@ -349,6 +361,29 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
349
361
|
verifiedAt: "2026-08-03",
|
|
350
362
|
})),
|
|
351
363
|
),
|
|
364
|
+
{
|
|
365
|
+
// The native Codex selector aliases gpt-5.6-sol and shares its 272k long-context tier.
|
|
366
|
+
provider: "openai",
|
|
367
|
+
modelId: "gpt-daybreak-blue-latest",
|
|
368
|
+
thresholdInputTokens: 272_000,
|
|
369
|
+
inclusive: false,
|
|
370
|
+
multiplier: OPENAI_LONG_CONTEXT,
|
|
371
|
+
confirmedPriorityRelation: "exclusive",
|
|
372
|
+
source: OPENAI_PRICING_DOC,
|
|
373
|
+
verifiedAt: "2026-08-11",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
// The bare selector is the separately billed API-key alias. Daybreak Red has no tier row:
|
|
377
|
+
// the cyber snapshot's four long-context cells are all "-" on the pricing page.
|
|
378
|
+
provider: "openai-apikey",
|
|
379
|
+
modelId: "daybreak-blue-latest",
|
|
380
|
+
thresholdInputTokens: 272_000,
|
|
381
|
+
inclusive: false,
|
|
382
|
+
multiplier: OPENAI_LONG_CONTEXT,
|
|
383
|
+
confirmedPriorityRelation: "exclusive",
|
|
384
|
+
source: OPENAI_PRICING_DOC,
|
|
385
|
+
verifiedAt: "2026-08-11",
|
|
386
|
+
},
|
|
352
387
|
{
|
|
353
388
|
provider: "xai",
|
|
354
389
|
modelId: "grok-4.5",
|
|
@@ -371,22 +406,6 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
371
406
|
source: "https://docs.x.ai/developers/pricing",
|
|
372
407
|
verifiedAt: "2026-08-18",
|
|
373
408
|
},
|
|
374
|
-
{
|
|
375
|
-
// daybreak-blue-latest aliases gpt-5.6-sol, which publishes the full long-context row
|
|
376
|
-
// ($10 / $1 / $12.50 / $45). Scoped to openai-apikey ON PURPOSE: Daybreak is not
|
|
377
|
-
// routable on the Codex-login `openai` provider, so adding the alias to the shared
|
|
378
|
-
// OPENAI_GPT56_CONTEXT_MODELS list (expanded across both providers above) would mint a
|
|
379
|
-
// tier for a provider/model pair that cannot exist.
|
|
380
|
-
// daybreak-red-latest has NO tier row: the cyber snapshot's four long-context cells are
|
|
381
|
-
// all "-" on the pricing page (verified 2026-08-11).
|
|
382
|
-
provider: "openai-apikey",
|
|
383
|
-
modelId: "daybreak-blue-latest",
|
|
384
|
-
thresholdInputTokens: 272_000,
|
|
385
|
-
inclusive: false,
|
|
386
|
-
multiplier: OPENAI_LONG_CONTEXT,
|
|
387
|
-
source: OPENAI_PRICING_DOC,
|
|
388
|
-
verifiedAt: "2026-08-11",
|
|
389
|
-
},
|
|
390
409
|
...["minimax", "minimax-cn"].map((provider): ContextTier => ({
|
|
391
410
|
provider,
|
|
392
411
|
modelId: "MiniMax-M3",
|
package/src/usage/log.ts
CHANGED
|
@@ -28,6 +28,7 @@ export type AttemptRecoveryKind =
|
|
|
28
28
|
| "key-429"
|
|
29
29
|
| "rate-limit-429"
|
|
30
30
|
| "anthropic-oauth-429"
|
|
31
|
+
| "oauth-account-429"
|
|
31
32
|
| "image-413"
|
|
32
33
|
| "opaque-blob-rejection"
|
|
33
34
|
| "empty-completion";
|
|
@@ -68,6 +69,7 @@ export interface PersistedUsageAttempt {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
export interface PersistedUsageEntry {
|
|
72
|
+
requestedAlias?: string;
|
|
71
73
|
requestId: string;
|
|
72
74
|
timestamp: number;
|
|
73
75
|
provider: string;
|
|
@@ -218,6 +220,7 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
|
|
|
218
220
|
"key-429",
|
|
219
221
|
"rate-limit-429",
|
|
220
222
|
"anthropic-oauth-429",
|
|
223
|
+
"oauth-account-429",
|
|
221
224
|
"image-413",
|
|
222
225
|
"opaque-blob-rejection",
|
|
223
226
|
"empty-completion",
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* Never throws; every error string passes redactSecretString.
|
|
10
10
|
*/
|
|
11
11
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
12
|
-
import { signalWithTimeout } from "../lib/abort";
|
|
12
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
13
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
13
14
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
14
15
|
import { redactSecretString } from "../lib/redact";
|
|
15
|
-
import type
|
|
16
|
+
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
16
17
|
import type { SidecarOutcome, SidecarSettings } from "./executor";
|
|
17
18
|
|
|
18
19
|
const EXA_SEARCH_URL = "https://api.exa.ai/search";
|
|
@@ -47,14 +48,44 @@ export async function runExaWebSearch(
|
|
|
47
48
|
}),
|
|
48
49
|
{ abortSignal: linkedSignal.signal, label: "exa-web-search-sidecar" },
|
|
49
50
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
52
|
+
try {
|
|
53
|
+
let bounded: Awaited<ReturnType<typeof readBoundedResponseBytes>> | null = null;
|
|
54
|
+
try {
|
|
55
|
+
bounded = await readBoundedResponseBytes(res, {
|
|
56
|
+
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
|
|
57
|
+
signal: linkedSignal.signal,
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
const reason = linkedSignal.signal.reason;
|
|
61
|
+
if (linkedSignal.signal.aborted && reason instanceof Error && reason.name === "TimeoutError") {
|
|
62
|
+
throw reason;
|
|
63
|
+
}
|
|
64
|
+
// Preserve the previous status-only/shapeless fallback for body read
|
|
65
|
+
// failures; fetch/header failures are still classified by the outer catch.
|
|
66
|
+
}
|
|
67
|
+
if (bounded?.oversized) {
|
|
68
|
+
const prefix = res.ok ? "exa sidecar response" : `exa sidecar HTTP ${res.status} response`;
|
|
69
|
+
return { text: "", sources: [], error: `${prefix} exceeded byte bound` };
|
|
70
|
+
}
|
|
71
|
+
// Response.text()/json() replace malformed UTF-8, so preserve that
|
|
72
|
+
// behavior while moving the byte bound ahead of decoding and parsing.
|
|
73
|
+
const text = bounded ? new TextDecoder().decode(bounded.bytes) : "";
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
// Scrub BEFORE truncating: slicing first can cut the literal key at the
|
|
76
|
+
// boundary, leaving an unscrubbable key prefix in the surviving text.
|
|
77
|
+
return { text: "", sources: [], error: `exa sidecar HTTP ${res.status}: ${scrub(text).slice(0, 200)}` };
|
|
78
|
+
}
|
|
79
|
+
let payload: unknown = null;
|
|
80
|
+
try {
|
|
81
|
+
payload = JSON.parse(text);
|
|
82
|
+
} catch {
|
|
83
|
+
// The mapper owns the stable malformed/empty JSON outcome.
|
|
84
|
+
}
|
|
85
|
+
return mapExaSearchResponse(payload);
|
|
86
|
+
} finally {
|
|
87
|
+
detachBodyGuard();
|
|
55
88
|
}
|
|
56
|
-
const payload = await res.json().catch(() => null);
|
|
57
|
-
return mapExaSearchResponse(payload);
|
|
58
89
|
} catch (e) {
|
|
59
90
|
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
60
91
|
console.warn(`[web-search] exa sidecar ${kind} (${Date.now() - t0}ms)`);
|
package/src/web-search/loop.ts
CHANGED
|
@@ -304,10 +304,11 @@ export interface WebSearchLoopDeps {
|
|
|
304
304
|
/** Called before each routed-model dispatch in the loop, for attempt telemetry. Same-target 429 replays pass the `rate-limit-429` recovery kind. */
|
|
305
305
|
onAttemptSend?: (recovery?: AttemptRecoveryKind) => void;
|
|
306
306
|
/**
|
|
307
|
-
* 429
|
|
308
|
-
* or null when the pool is exhausted
|
|
307
|
+
* 429 failover hook: rotate the provider's active credential and return a rebuilt adapter,
|
|
308
|
+
* or null when the pool is exhausted. Async hooks support OAuth refresh; existing synchronous
|
|
309
|
+
* key-pool hooks remain valid.
|
|
309
310
|
*/
|
|
310
|
-
on429?: (retryAfterHeader: string | null) => ProviderAdapter | null
|
|
311
|
+
on429?: (retryAfterHeader: string | null) => ProviderAdapter | null | Promise<ProviderAdapter | null>;
|
|
311
312
|
/** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
|
|
312
313
|
retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
|
|
313
314
|
/** Called only when the final bridged Responses stream reaches completed or incomplete. */
|
|
@@ -509,7 +510,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
509
510
|
// 429 key-failover parity with the normal routed path: rotate pool keys until one responds
|
|
510
511
|
// or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
|
|
511
512
|
while (prepared.response.status === 429 && deps.on429) {
|
|
512
|
-
const rotated = deps.on429(prepared.response.headers.get("retry-after"));
|
|
513
|
+
const rotated = await deps.on429(prepared.response.headers.get("retry-after"));
|
|
513
514
|
if (!rotated) break;
|
|
514
515
|
// Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
|
|
515
516
|
// it, but proceed immediately to the rotated fetch under the SAME deadline signal.
|