@bitkyc08/opencodex 2.52.0 → 2.53.0-preview.20260913
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-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/types/accounts.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface CodexAccountCredentialRecord {
|
|
|
29
29
|
credential?: CodexAccountCredentials;
|
|
30
30
|
generation: number;
|
|
31
31
|
refreshGrantFingerprint?: string;
|
|
32
|
+
/** Private non-secret publication identity, stable across same-account token refresh. */
|
|
33
|
+
quotaHistoryIdentity?: string;
|
|
32
34
|
deletedAt?: number;
|
|
33
35
|
replacedAt?: number;
|
|
34
36
|
lastCodexValidatedAt?: number;
|
package/src/types/config.ts
CHANGED
|
@@ -380,6 +380,8 @@ export interface OcxConfig {
|
|
|
380
380
|
privacy?: OcxPrivacyConfig;
|
|
381
381
|
/** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
|
|
382
382
|
emptyCompletionRetry?: boolean;
|
|
383
|
+
/** Suppress allowlisted client-facing Codex transport hints; provider enforcement is unchanged. */
|
|
384
|
+
dropCodexSafetyBuffering?: boolean;
|
|
383
385
|
/**
|
|
384
386
|
* Whether a login may open a browser on the machine running the proxy.
|
|
385
387
|
*
|
|
@@ -637,11 +639,18 @@ export interface OcxConfig {
|
|
|
637
639
|
* - "v2": force ALL models to v2 surface (override upstream pins)
|
|
638
640
|
*/
|
|
639
641
|
multiAgentMode?: "v1" | "default" | "v2";
|
|
642
|
+
/**
|
|
643
|
+
* Which revision of the sub-agent surface advisory this install has answered.
|
|
644
|
+
* Absent means it has answered none. Written by the dashboard, never by a mode change.
|
|
645
|
+
*/
|
|
646
|
+
multiAgentSurfaceAdvisoryVersion?: number;
|
|
640
647
|
/**
|
|
641
648
|
* When `multiAgentMode` is `"v2"`, keep ChatGPT-native catalog rows on v1.
|
|
642
649
|
* Routed parents get v2 tools; Sol/Terra can still spawn Grok/Claude (issue #92).
|
|
643
650
|
*/
|
|
644
651
|
keepNativeChatGptOnV1?: boolean;
|
|
652
|
+
/** Experimental plaintext delivery for native v2 collaboration messages; disabled unless true. */
|
|
653
|
+
plaintextV2AgentMessages?: boolean;
|
|
645
654
|
/** Experimental, default-off ChatGPT recovery for encrypted V2 routed tasks. */
|
|
646
655
|
agentTaskRecovery?: {
|
|
647
656
|
enabled?: boolean;
|
|
@@ -822,15 +831,6 @@ export interface OcxConfig {
|
|
|
822
831
|
* selector map remains visible for compatibility with hand-written configurations.
|
|
823
832
|
*/
|
|
824
833
|
codexAccountPickerEnabled?: boolean;
|
|
825
|
-
/**
|
|
826
|
-
* Show the GPT-5.3-Codex-Spark 5-hour and weekly windows on Codex quota surfaces. Default false.
|
|
827
|
-
*
|
|
828
|
-
* Spark is a single-model window that reads 0% for most operators, and on a multi-account
|
|
829
|
-
* pool it doubles the bar count for information almost nobody acts on. Hidden by default and
|
|
830
|
-
* revealed by an explicit `true`; a malformed value reads as hidden rather than rejecting the
|
|
831
|
-
* whole config.
|
|
832
|
-
*/
|
|
833
|
-
showCodexSparkQuota?: boolean;
|
|
834
834
|
/**
|
|
835
835
|
* Opt-in auto-redemption of a main-account Codex reset credit shortly before it expires
|
|
836
836
|
* (#822). Default off. `leadTimeMinutes` (1–60, default 10) is how long before
|
|
@@ -866,7 +866,7 @@ export interface OcxConfig {
|
|
|
866
866
|
/** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
|
|
867
867
|
autoSwitchThreshold?: number;
|
|
868
868
|
/** New-session account rotation strategy for the Codex pool. Default quota (today's behaviour). */
|
|
869
|
-
accountPoolStrategy?: OcxAccountPoolRotationStrategy;
|
|
869
|
+
accountPoolStrategy?: OcxAccountPoolRotationStrategy | "reset-first";
|
|
870
870
|
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
|
871
871
|
accountPoolStickyLimit?: number;
|
|
872
872
|
/** Consecutive non-2xx upstream responses before switching future new threads. Default 3. 0 = disabled. */
|
|
@@ -967,8 +967,9 @@ export type OcxComboDefaultEffort = "low" | "medium" | "high" | "xhigh" | "max"
|
|
|
967
967
|
* advertises no effort control (`reasoningEfforts: []`) empties the combo's picker.
|
|
968
968
|
* `adaptive` excludes those empty ladders from the published intersection, keeping the
|
|
969
969
|
* control usable for a mixed-capability group. Unknown (`undefined`) ladders stay
|
|
970
|
-
* wildcards in both modes.
|
|
971
|
-
*
|
|
970
|
+
* wildcards in both modes. An explicit empty ladder removes unsupported effort controls
|
|
971
|
+
* in either mode; adaptive dispatch also removes them before sending to an unknown target,
|
|
972
|
+
* while each known target still resolves its own effort.
|
|
972
973
|
*/
|
|
973
974
|
export type OcxComboReasoningEffortMode = "strict" | "adaptive";
|
|
974
975
|
|
package/src/types/provider.ts
CHANGED
|
@@ -227,6 +227,14 @@ export type TierDecision =
|
|
|
227
227
|
* One configured provider entry. `authMode` (default `"key"`) decides whether same-target 429
|
|
228
228
|
* retries are allowed; OAuth/forward credentials and local runtimes are never replayed.
|
|
229
229
|
*/
|
|
230
|
+
/** Explicit per-model operator declarations; absent axes keep legacy behavior. */
|
|
231
|
+
export interface ModelCapabilities {
|
|
232
|
+
inputModalities?: Array<"text" | "image" | "audio" | "video">;
|
|
233
|
+
/** Requested tier only; does not imply an upstream window or activate an unverified wire. */
|
|
234
|
+
contextTier?: "default" | "long_context";
|
|
235
|
+
video?: { processing?: "static" | "agentic" };
|
|
236
|
+
}
|
|
237
|
+
|
|
230
238
|
export interface OcxProviderConfig {
|
|
231
239
|
/** Optional short provider namespace used only at request/catalog presentation time. */
|
|
232
240
|
alias?: string;
|
|
@@ -478,6 +486,7 @@ export interface OcxProviderConfig {
|
|
|
478
486
|
modelContextWindows?: Record<string, number>;
|
|
479
487
|
/** Model-specific Codex catalog input modalities, e.g. ["text"] or ["text", "image"]. */
|
|
480
488
|
modelInputModalities?: Record<string, string[]>;
|
|
489
|
+
modelCapabilities?: Record<string, ModelCapabilities>;
|
|
481
490
|
/** Model-specific max input token limits. Values cap auto_compact_token_limit. */
|
|
482
491
|
modelMaxInputTokens?: Record<string, number>;
|
|
483
492
|
/**
|
|
@@ -501,6 +510,28 @@ export interface OcxProviderConfig {
|
|
|
501
510
|
* all-zero entry means "not billable here" and falls through to the catalogs.
|
|
502
511
|
*/
|
|
503
512
|
modelCosts?: Record<string, ProviderCostOverlay>;
|
|
513
|
+
/**
|
|
514
|
+
* Provider-wide auto-review (approval) model for routed models of this provider.
|
|
515
|
+
*
|
|
516
|
+
* The value is a catalog selector: either a bare model id of this provider
|
|
517
|
+
* (for example `deepseek-v4-flash`) or a full public slug (for example
|
|
518
|
+
* `opencode-go/deepseek-v4-flash`). During catalog synchronization the
|
|
519
|
+
* selector is resolved against the final catalog and stamped as
|
|
520
|
+
* `auto_review_model_override` on each routed row of this provider that has
|
|
521
|
+
* no per-model override. The root Codex `auto_review_model` remains the
|
|
522
|
+
* fallback for every row without a provider stamp. Null or blank clears the
|
|
523
|
+
* provider-wide stamp; see `autoReviewModelOverrides` for per-model targets.
|
|
524
|
+
*/
|
|
525
|
+
autoReviewModel?: string;
|
|
526
|
+
/**
|
|
527
|
+
* Per-model auto-review (approval) overrides for routed models of this
|
|
528
|
+
* provider. Keys are exact upstream model ids under this provider (either
|
|
529
|
+
* spelling of a slash-containing id is accepted). Each value is a catalog
|
|
530
|
+
* selector with the same meaning as `autoReviewModel`; an entry wins over
|
|
531
|
+
* the provider-wide value for its model. Null or blank entries remove the
|
|
532
|
+
* model from the map while preserving other entries.
|
|
533
|
+
*/
|
|
534
|
+
autoReviewModelOverrides?: Record<string, string>;
|
|
504
535
|
headers?: Record<string, string>;
|
|
505
536
|
/** Default provider-routing preferences for models sent through the canonical OpenRouter API. */
|
|
506
537
|
openRouterRouting?: OpenRouterProviderRouting;
|
|
@@ -773,6 +804,12 @@ export interface OcxProviderConfig {
|
|
|
773
804
|
* out explicitly (e.g. MiniMax, where low effort disables thinking).
|
|
774
805
|
*/
|
|
775
806
|
requiresReasoningPlaceholderModels?: string[];
|
|
807
|
+
/**
|
|
808
|
+
* Default to displaying provider-authored summaries when Responses summary is omitted.
|
|
809
|
+
* Explicit wire summary:"none" wins; false disables a seeded provider default.
|
|
810
|
+
* Raw reasoning is never relabeled as a summary.
|
|
811
|
+
*/
|
|
812
|
+
showThinkingSummary?: boolean;
|
|
776
813
|
/**
|
|
777
814
|
* Opt-in same-target 429 retry policy. Codex itself never retries 429 (it retries 5xx only,
|
|
778
815
|
* openai/codex#30471), and single-key pools have no failover, so the proxy waits and replays
|
package/src/types/request.ts
CHANGED
|
@@ -79,6 +79,8 @@ export interface OcxParsedRequest {
|
|
|
79
79
|
* prepareOpaqueBlobRecovery after an authoritative rejection; consumers strip replayed blobs.
|
|
80
80
|
*/
|
|
81
81
|
_stripReasoningEncryptedContent?: boolean;
|
|
82
|
+
/** Final-route opt-in: emit v2 collaboration message arguments as plaintext on ChatGPT. */
|
|
83
|
+
_plaintextV2AgentMessages?: boolean;
|
|
82
84
|
/**
|
|
83
85
|
* Optional authenticated tenant/operator namespace for Cursor thread→conversation derivation.
|
|
84
86
|
* When absent (single-operator local proxy), derivation stays local-scoped.
|
package/src/types/tools.ts
CHANGED
|
@@ -47,16 +47,17 @@ export function dottedToolName(namespace: string | undefined, name: string): str
|
|
|
47
47
|
*
|
|
48
48
|
* Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
|
|
49
49
|
* description mentions the nested `await tools.exec_command(...)` helper). Some routed providers
|
|
50
|
-
* echo that helper name as the tool-call name, emitting `exec_command`, `write_stdin`,
|
|
51
|
-
* `apply_patch` instead of the declared `exec`. Accept these nested helper names
|
|
52
|
-
* request catalog actually declares `exec` and does not itself declare the emitted
|
|
53
|
-
* server may legitimately advertise one under its own namespace).
|
|
50
|
+
* echo that helper name as the tool-call name, emitting `exec_command`, `write_stdin`,
|
|
51
|
+
* `apply_patch`, or `view_image` instead of the declared `exec`. Accept these nested helper names
|
|
52
|
+
* only when the request catalog actually declares `exec` and does not itself declare the emitted
|
|
53
|
+
* name (an MCP server may legitimately advertise one under its own namespace).
|
|
54
54
|
*/
|
|
55
55
|
const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
56
56
|
const CODE_MODE_HELPER_TOOL_NAMES = [
|
|
57
57
|
...LEGACY_SHELL_BRIDGE_TOOL_NAMES,
|
|
58
58
|
"write_stdin",
|
|
59
59
|
"apply_patch",
|
|
60
|
+
"view_image",
|
|
60
61
|
] as const;
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -71,7 +72,7 @@ export const CODE_MODE_EXEC_TOOL_NAME = "exec";
|
|
|
71
72
|
*
|
|
72
73
|
* Rewrites invented `default.<name>` prefixes back to a declared bare tool when that bare tool
|
|
73
74
|
* is declared and neither `default.<name>` nor `default__<name>` was explicitly declared (#4176).
|
|
74
|
-
* Also normalizes legacy helper names (`exec_command`, `shell_command`, `apply_patch`) to
|
|
75
|
+
* Also normalizes legacy helper names (`exec_command`, `shell_command`, `apply_patch`, `view_image`) to
|
|
75
76
|
* `exec` when code-mode `exec` is declared in the request catalog.
|
|
76
77
|
*
|
|
77
78
|
* @param name - The tool name emitted on the wire by the provider.
|
|
@@ -98,6 +99,17 @@ export function normalizeDeclaredToolName(
|
|
|
98
99
|
&& !declared.has("default__" + bare)
|
|
99
100
|
) {
|
|
100
101
|
candidate = bare;
|
|
102
|
+
} else if (
|
|
103
|
+
// Code mode never declares bare helper names; a provider that invents `default.`
|
|
104
|
+
// for one still means the nested helper. Strip the prefix so the helper list
|
|
105
|
+
// below can rewrite it to `exec` (#4412).
|
|
106
|
+
bare.length > 0
|
|
107
|
+
&& declared.has(CODE_MODE_EXEC_TOOL_NAME)
|
|
108
|
+
&& (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(bare)
|
|
109
|
+
&& !declared.has("default." + bare)
|
|
110
|
+
&& !declared.has("default__" + bare)
|
|
111
|
+
) {
|
|
112
|
+
candidate = bare;
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
115
|
if (!declared.has(CODE_MODE_EXEC_TOOL_NAME)) return candidate;
|
package/src/types.ts
CHANGED
|
@@ -70,6 +70,29 @@ const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWri
|
|
|
70
70
|
const KIMI_K27_CODE_HIGHSPEED: Cost4 = { input: 1.9, output: 8, cacheRead: 0.38, cacheWrite: 1.9 };
|
|
71
71
|
const KIMI_K26: Cost4 = { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 };
|
|
72
72
|
const KIMI_K25: Cost4 = { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 };
|
|
73
|
+
/*
|
|
74
|
+
* Z.AI GLM list prices (USD / 1M tokens), verified 2026-09-13 against
|
|
75
|
+
* https://docs.z.ai/guides/overview/pricing. Neither z.ai nor bigmodel.cn
|
|
76
|
+
* publishes a cache-write rate — both list cache storage as limited-time free,
|
|
77
|
+
* an open-beta promotion the vendor may change or end — so cacheWrite is 0 as a
|
|
78
|
+
* 2026-09-13 snapshot, not a guaranteed rate; re-check the pricing page before
|
|
79
|
+
* relying on it long-term. glm-4.5-flash and glm-4.7-flash are officially
|
|
80
|
+
* "Free" and deliberately get no rows: a zero-cost overlay is inert in the
|
|
81
|
+
* resolver, which requires a nonzero tuple. glm-5-turbo / glm-5v-turbo are
|
|
82
|
+
* published only in CNY on bigmodel.cn and stay unregistered — the same hold
|
|
83
|
+
* the xiaomi CNY rows took in devlog/_fin/260720_toks_speed_price_columns/003.
|
|
84
|
+
* glm-4.5 (0.6/2.2/0.11), glm-4.5-air (0.2/1.1/0.03) and glm-4.5v (0.6/1.8/0.11)
|
|
85
|
+
* are verified on the same page but no registered provider exposes them, so
|
|
86
|
+
* they have no constants here.
|
|
87
|
+
*/
|
|
88
|
+
const GLM_46: Cost4 = { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 };
|
|
89
|
+
const GLM_46V: Cost4 = { input: 0.3, output: 0.9, cacheRead: 0.05, cacheWrite: 0 };
|
|
90
|
+
const GLM_47: Cost4 = { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 };
|
|
91
|
+
const GLM_5: Cost4 = { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 };
|
|
92
|
+
const GLM_51: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 };
|
|
93
|
+
const GLM_52: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 };
|
|
94
|
+
const GLM_53: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 };
|
|
95
|
+
const GLM_53_FLASH: Cost4 = { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 };
|
|
73
96
|
const QWEN38_MAX: Cost4 = { input: 2, output: 6, cacheRead: 0, cacheWrite: 0 };
|
|
74
97
|
// Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate.
|
|
75
98
|
const CLAUDE_SONNET_46: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
@@ -104,6 +127,13 @@ const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-deta
|
|
|
104
127
|
// Kimi official tables publish input/output/cache-hit only; cacheWrite is mapped to the
|
|
105
128
|
// cache-miss input price (Kimi auto-caches with no separate write billing). 2026-07-20 re-verified.
|
|
106
129
|
const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (official table; cacheWrite derived = input, Kimi auto-cache has no write billing)";
|
|
130
|
+
// Z.AI publishes one USD table for the international surface; the Coding Plan
|
|
131
|
+
// subscription and the domestic bigmodel.cn endpoints bill differently
|
|
132
|
+
// (subscription quota / CNY tiers), so every GLM row below is verified-derived:
|
|
133
|
+
// the numbers are the verified z.ai list prices shown as estimates.
|
|
134
|
+
const ZAI_PRICING = "https://docs.z.ai/guides/overview/pricing (official USD table, 2026-09-13; cacheWrite=0 — cache storage is limited-time free on both z.ai and bigmodel.cn)";
|
|
135
|
+
const ZAI_CODING_PLAN_NOTE = "z.ai list price shown as estimate; GLM Coding Plan is subscription-billed";
|
|
136
|
+
const BIGMODEL_NOTE = "z.ai international list price shown as estimate; domestic bigmodel.cn billing is CNY tiered (docs.bigmodel.cn/cn/guide/start/pricing)";
|
|
107
137
|
// 260804: Qwen3.8-Max shipped as a stable model and Qwen published a per-token rate, which
|
|
108
138
|
// is the exit condition the previous Routeway reseller overlay named. Two caveats are
|
|
109
139
|
// deliberately in the source string rather than dropped: the figure comes from Qwen's own
|
|
@@ -113,6 +143,31 @@ const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (official table; cac
|
|
|
113
143
|
// under a vendor-price label would be a wrong value wearing a verified badge.
|
|
114
144
|
const QWEN38_MAX_PRICING = "https://qwen.ai/blog?id=qwen3.8 (Qwen release announcement; no Model Studio billing row yet; cache rates unpublished -> 0)";
|
|
115
145
|
|
|
146
|
+
/*
|
|
147
|
+
* Cognition/Devin list prices (USD / 1M tokens), verified 2026-09-13 against the
|
|
148
|
+
* official "AI Models" page — its embedded modelCostData table publishes
|
|
149
|
+
* input / cache-read / cache-write / output per model uid. Self-serve extra
|
|
150
|
+
* usage and enterprise ACU conversion both bill at these list rates, so the
|
|
151
|
+
* tuples are the vendor's own published numbers; every row still stays
|
|
152
|
+
* verified-derived because the surface itself is subscription/ACU, not a
|
|
153
|
+
* per-token API.
|
|
154
|
+
* Time-boxed promos are NOT baked in: SWE-2 shows $0 self-serve through
|
|
155
|
+
* 2026-10-08 and 75%-off enterprise through 2026-12-31, and the doc states the
|
|
156
|
+
* list rate is what applies afterward, so the list rate is the durable catalog
|
|
157
|
+
* value. swe-1-7 keeps its list rate for the same reason even though the
|
|
158
|
+
* self-serve column currently shows 0. gemini-3-8-flash is absent from the
|
|
159
|
+
* table entirely, so its row derives from Google's published rate instead.
|
|
160
|
+
*/
|
|
161
|
+
const DEVIN_SWE_2: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 };
|
|
162
|
+
const DEVIN_SWE_17: Cost4 = { input: 0.5, output: 2.5, cacheRead: 0.2, cacheWrite: 0 };
|
|
163
|
+
const DEVIN_SWE_17_LIGHTNING: Cost4 = { input: 2.5, output: 12.5, cacheRead: 1, cacheWrite: 0 };
|
|
164
|
+
const DEVIN_SONNET_5: Cost4 = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
165
|
+
const DEVIN_KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 };
|
|
166
|
+
const DEVIN_KIMI_K27: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 };
|
|
167
|
+
const DEVIN_GROK: Cost4 = { input: 2, output: 6, cacheRead: 0.3, cacheWrite: 0 };
|
|
168
|
+
const DEVIN_PRICING = "https://docs.devin.ai/desktop/models (official modelCostData table, 2026-09-13; list rates for self-serve overage / enterprise ACU conversion on a subscription surface)";
|
|
169
|
+
const DEVIN_SWE2_NOTE = "list rate; $0 self-serve through 2026-10-08 and 75%-off enterprise through 2026-12-31 are time-boxed promos, not baked in";
|
|
170
|
+
|
|
116
171
|
export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
117
172
|
{ provider: "openai-apikey", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: ASTRA_API_PRICING, verifiedAt: "2026-09-05", status: "verified" },
|
|
118
173
|
// Display estimates use API prices for both login and API-key routes, including cache writes.
|
|
@@ -236,6 +291,78 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
236
291
|
{ provider: "alibaba-token-plan-intl", modelId: "qwen3.8-max", cost4: QWEN38_MAX, source: QWEN38_MAX_PRICING, verifiedAt: "2026-08-04", status: "verified" },
|
|
237
292
|
// Cursor Auto router — Cursor's published fixed token price (verified).
|
|
238
293
|
{ provider: "cursor", modelId: "auto", cost4: { input: 1.25, output: 6, cacheRead: 0.25, cacheWrite: 1.25 }, source: "https://docs.cursor.com/account/pricing + https://cursor.com/blog/aug-2025-pricing", verifiedAt: "2026-07-20", status: "verified" },
|
|
294
|
+
// Z.AI GLM family — the zai bundle's rows are all-zero upstream, and the four
|
|
295
|
+
// provider surfaces below resolve overlays by exact provider id, so each one
|
|
296
|
+
// needs its own rows (same pattern as kimi/moonshot/kimi-code). All rows are
|
|
297
|
+
// verified-derived: the tuples are the verified z.ai USD list prices, while
|
|
298
|
+
// the Coding Plan rows are subscription products and zhipu-bigmodel is the
|
|
299
|
+
// domestic CNY-tiered PAYG — see ZAI_CODING_PLAN_NOTE / BIGMODEL_NOTE.
|
|
300
|
+
// zai (api.z.ai Coding Plan) exposes: glm-5.3, glm-5.3[1m], glm-5.3-flash,
|
|
301
|
+
// glm-5.2, glm-5.2[1m], glm-5.1, glm-5, glm-4.6.
|
|
302
|
+
{ provider: "zai", modelId: "glm-5.3", cost4: GLM_53, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
303
|
+
{ provider: "zai", modelId: "glm-5.3[1m]", cost4: GLM_53, source: `derived: glm-5.3 1M-context compat notation; ${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
304
|
+
{ provider: "zai", modelId: "glm-5.3-flash", cost4: GLM_53_FLASH, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
305
|
+
{ provider: "zai", modelId: "glm-5.2", cost4: GLM_52, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
306
|
+
{ provider: "zai", modelId: "glm-5.2[1m]", cost4: GLM_52, source: `derived: glm-5.2 1M-context compat notation; ${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
307
|
+
{ provider: "zai", modelId: "glm-5.1", cost4: GLM_51, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
308
|
+
{ provider: "zai", modelId: "glm-5", cost4: GLM_5, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
309
|
+
{ provider: "zai", modelId: "glm-4.6", cost4: GLM_46, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
310
|
+
// zhipu-bigmodel (open.bigmodel.cn PAYG) exposes: glm-4.6, glm-4.7,
|
|
311
|
+
// glm-4.7-flash (officially free — no row), glm-5, glm-5.1, glm-5.2, glm-5.3,
|
|
312
|
+
// glm-4.6v.
|
|
313
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-4.6", cost4: GLM_46, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
314
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-4.6v", cost4: GLM_46V, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
315
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-4.7", cost4: GLM_47, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
316
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-5", cost4: GLM_5, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
317
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-5.1", cost4: GLM_51, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
318
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-5.2", cost4: GLM_52, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
319
|
+
{ provider: "zhipu-bigmodel", modelId: "glm-5.3", cost4: GLM_53, source: `${BIGMODEL_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
320
|
+
// zhipu-bigmodel-coding exposes the same roster as the zai Coding Plan row.
|
|
321
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.3", cost4: GLM_53, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
322
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.3[1m]", cost4: GLM_53, source: `derived: glm-5.3 1M-context compat notation; ${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
323
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.3-flash", cost4: GLM_53_FLASH, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
324
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.2", cost4: GLM_52, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
325
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.2[1m]", cost4: GLM_52, source: `derived: glm-5.2 1M-context compat notation; ${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
326
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5.1", cost4: GLM_51, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
327
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-5", cost4: GLM_5, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
328
|
+
{ provider: "zhipu-bigmodel-coding", modelId: "glm-4.6", cost4: GLM_46, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
329
|
+
// zhipu-bigmodel-responses exposes glm-5.3, glm-5.3-flash, glm-5-turbo; the
|
|
330
|
+
// turbo id is CNY-only upstream and stays unregistered (see the GLM_* note).
|
|
331
|
+
{ provider: "zhipu-bigmodel-responses", modelId: "glm-5.3", cost4: GLM_53, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
332
|
+
{ provider: "zhipu-bigmodel-responses", modelId: "glm-5.3-flash", cost4: GLM_53_FLASH, source: `${ZAI_CODING_PLAN_NOTE}; ${ZAI_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
333
|
+
// Cognition/Devin — the two OAuth surfaces resolve by exact provider id, so
|
|
334
|
+
// each carries the roster its liveModels discovery can surface. swe-2 and
|
|
335
|
+
// swe-1-6 are listed on both even though each static seed names only one
|
|
336
|
+
// side: the live catalog is authoritative and drifts between them.
|
|
337
|
+
// gpt-5-6-sol uses the enterprise list column — the same table's self-serve
|
|
338
|
+
// column shows a discounted 1.2/6, and the doc calls the list rate the
|
|
339
|
+
// billing rate for overage. glm-5-2 likewise takes the nonzero list column.
|
|
340
|
+
{ provider: "devin-cli", modelId: "swe-2", cost4: DEVIN_SWE_2, source: `${DEVIN_SWE2_NOTE}; ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
341
|
+
{ provider: "devin-cli", modelId: "swe-1-7", cost4: DEVIN_SWE_17, source: `list rate; self-serve column currently shows 0 (unannounced promo); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
342
|
+
{ provider: "devin-cli", modelId: "swe-1-7-lightning", cost4: DEVIN_SWE_17_LIGHTNING, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
343
|
+
{ provider: "devin-cli", modelId: "swe-1-6", cost4: DEVIN_SWE_17, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
344
|
+
{ provider: "devin-cli", modelId: "gpt-5-6-sol", cost4: GPT56_SOL, source: `enterprise list column (self-serve shows discounted 1.2/6); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
345
|
+
{ provider: "devin-cli", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
346
|
+
{ provider: "devin-cli", modelId: "claude-opus-5", cost4: CLAUDE_OPUS_46, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
347
|
+
{ provider: "devin-cli", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
348
|
+
{ provider: "devin-cli", modelId: "claude-sonnet-5", cost4: DEVIN_SONNET_5, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
349
|
+
{ provider: "devin-cli", modelId: "glm-5-3", cost4: GLM_53, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
350
|
+
{ provider: "devin-cli", modelId: "kimi-k3", cost4: DEVIN_KIMI_K3, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
351
|
+
{ provider: "devin-cli", modelId: "gemini-3-8-flash", cost4: GEMINI_38_FLASH, source: `derived: absent from Devin's modelCostData table; Google published promotional rate through 2026-12-31 shown as estimate ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
352
|
+
{ provider: "devin-cli", modelId: "grok-4-6", cost4: DEVIN_GROK, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
353
|
+
{ provider: "devin", modelId: "swe-2", cost4: DEVIN_SWE_2, source: `${DEVIN_SWE2_NOTE}; ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
354
|
+
{ provider: "devin", modelId: "swe-1-7", cost4: DEVIN_SWE_17, source: `list rate; self-serve column currently shows 0 (unannounced promo); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
355
|
+
{ provider: "devin", modelId: "swe-1-7-lightning", cost4: DEVIN_SWE_17_LIGHTNING, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
356
|
+
{ provider: "devin", modelId: "swe-1-6", cost4: DEVIN_SWE_17, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
357
|
+
{ provider: "devin", modelId: "gpt-5-6-sol", cost4: GPT56_SOL, source: `enterprise list column (self-serve shows discounted 1.2/6); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
358
|
+
{ provider: "devin", modelId: "gpt-5-6-luna", cost4: GPT56_LUNA, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
359
|
+
{ provider: "devin", modelId: "gpt-5-6-terra", cost4: GPT56_TERRA, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
360
|
+
{ provider: "devin", modelId: "claude-opus-4-8", cost4: CLAUDE_OPUS_46, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
361
|
+
{ provider: "devin", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
362
|
+
{ provider: "devin", modelId: "claude-sonnet-5", cost4: DEVIN_SONNET_5, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
363
|
+
{ provider: "devin", modelId: "glm-5-2", cost4: GLM_52, source: `enterprise list column (self-serve shows an unannounced 0 promo); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
364
|
+
{ provider: "devin", modelId: "kimi-k2-7", cost4: DEVIN_KIMI_K27, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
365
|
+
{ provider: "devin", modelId: "grok-4-5", cost4: DEVIN_GROK, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
|
|
239
366
|
];
|
|
240
367
|
|
|
241
368
|
/**
|
package/src/usage/log.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { AttemptTierOutcome, OcxUsage } from "../types";
|
|
|
10
10
|
import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
|
|
11
11
|
import { ACCOUNT_LOG_LABEL_RE, CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
12
12
|
import { claudeCompatibilityReason, normalizeClaudeFeatureCodes, type ClaudeFeatureCode } from "../claude/compatibility";
|
|
13
|
+
import type { CodexWsStageRecord } from "../server/responses/codex-ws-wire";
|
|
13
14
|
|
|
14
15
|
export interface PersistedClaudeCompatibilityLog {
|
|
15
16
|
decision: "shadow";
|
|
@@ -70,8 +71,10 @@ export type AttemptRecoveryKind =
|
|
|
70
71
|
| "anthropic-oauth-429"
|
|
71
72
|
| "oauth-account-429"
|
|
72
73
|
| "image-413"
|
|
74
|
+
| "console-go-upload-retry"
|
|
73
75
|
| "opaque-blob-rejection"
|
|
74
|
-
| "empty-completion"
|
|
76
|
+
| "empty-completion"
|
|
77
|
+
| "reasoning-effort-downgrade";
|
|
75
78
|
|
|
76
79
|
/** Request-time upstream credential class, never a credential or account identifier. */
|
|
77
80
|
export type UsageCredentialSource = "grok-oauth" | "xai-api-key";
|
|
@@ -118,6 +121,14 @@ export interface PersistedUsageAttempt {
|
|
|
118
121
|
reasoningWireValue?: string | number | boolean;
|
|
119
122
|
/** Adapter-produced tier fact for this physical attempt; absent on pre-B0 rows. */
|
|
120
123
|
tierOutcome?: AttemptTierOutcome;
|
|
124
|
+
/**
|
|
125
|
+
* #4191: content-free stage record of a Codex WS upstream exchange that
|
|
126
|
+
* served this attempt (frame size, counters, close code, versions). Absent
|
|
127
|
+
* on HTTP-transport attempts and pre-instrumentation rows. Numbers,
|
|
128
|
+
* booleans, and semver strings only — never reason text, headers, or
|
|
129
|
+
* account identifiers.
|
|
130
|
+
*/
|
|
131
|
+
codexWsStage?: CodexWsStageRecord;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
export interface PersistedUsageEntry {
|
|
@@ -309,8 +320,10 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
|
|
|
309
320
|
"anthropic-oauth-429",
|
|
310
321
|
"oauth-account-429",
|
|
311
322
|
"image-413",
|
|
323
|
+
"console-go-upload-retry",
|
|
312
324
|
"opaque-blob-rejection",
|
|
313
325
|
"empty-completion",
|
|
326
|
+
"reasoning-effort-downgrade",
|
|
314
327
|
]);
|
|
315
328
|
const USAGE_STATUSES = new Set<UsageStatus>([
|
|
316
329
|
"reported",
|
|
@@ -436,6 +449,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
436
449
|
const tierOutcome = "tierOutcome" in attempt
|
|
437
450
|
? normalizeAttemptTierOutcome(attempt.tierOutcome)
|
|
438
451
|
: undefined;
|
|
452
|
+
const codexWsStage = "codexWsStage" in attempt
|
|
453
|
+
? normalizeCodexWsStageRecord(attempt.codexWsStage)
|
|
454
|
+
: undefined;
|
|
439
455
|
const recoveryKinds = Array.isArray(attempt.recoveryKinds)
|
|
440
456
|
? [...new Set(attempt.recoveryKinds.filter(
|
|
441
457
|
(value): value is AttemptRecoveryKind => typeof value === "string"
|
|
@@ -455,6 +471,7 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
455
471
|
durationMs: attempt.durationMs,
|
|
456
472
|
// Absent by default; only the literal `true` marker survives the round trip.
|
|
457
473
|
...(attempt.streamAborted === true ? { streamAborted: true } : {}),
|
|
474
|
+
...(attempt.locallyAnswered === true ? { locallyAnswered: true } : {}),
|
|
458
475
|
...(isNonNegativeFiniteNumber(attempt.firstOutputMs)
|
|
459
476
|
? { firstOutputMs: attempt.firstOutputMs }
|
|
460
477
|
: {}),
|
|
@@ -490,6 +507,46 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
490
507
|
: { reasoningWireValue: attempt.reasoningWireValue }
|
|
491
508
|
: {}),
|
|
492
509
|
...(tierOutcome ? { tierOutcome } : {}),
|
|
510
|
+
...(codexWsStage ? { codexWsStage } : {}),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* #4191: a persisted stage record is trusted only when every field matches the
|
|
516
|
+
* exchange's own shapes. Anything else — a hand-edited number as a string, an
|
|
517
|
+
* injected free-form field — drops the whole record rather than passing
|
|
518
|
+
* attacker text into the DTO.
|
|
519
|
+
*/
|
|
520
|
+
function normalizeCodexWsStageRecord(value: unknown): CodexWsStageRecord | undefined {
|
|
521
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
522
|
+
const stage = value as Record<string, unknown>;
|
|
523
|
+
for (const key of ["upstreamFrames", "controlFrames", "relayedEvents", "pings", "pongs"] as const) {
|
|
524
|
+
if (!isNonNegativeFiniteNumber(stage[key])) return undefined;
|
|
525
|
+
}
|
|
526
|
+
if (!(stage.requestBytes === null || isNonNegativeFiniteNumber(stage.requestBytes))) return undefined;
|
|
527
|
+
if (!(stage.firstFrameMs === null || isNonNegativeFiniteNumber(stage.firstFrameMs))) return undefined;
|
|
528
|
+
if (!(stage.elapsedMs === null || isNonNegativeFiniteNumber(stage.elapsedMs))) return undefined;
|
|
529
|
+
if (!(stage.closeCode === null || (typeof stage.closeCode === "number"
|
|
530
|
+
&& Number.isInteger(stage.closeCode) && stage.closeCode >= 1000 && stage.closeCode <= 4999))) {
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
if (typeof stage.sent !== "boolean" || typeof stage.reused !== "boolean") return undefined;
|
|
534
|
+
if (typeof stage.ocxVersion !== "string" || !stage.ocxVersion || stage.ocxVersion.length > 32) return undefined;
|
|
535
|
+
if (typeof stage.bunVersion !== "string" || !stage.bunVersion || stage.bunVersion.length > 32) return undefined;
|
|
536
|
+
return {
|
|
537
|
+
requestBytes: stage.requestBytes as number | null,
|
|
538
|
+
sent: stage.sent,
|
|
539
|
+
upstreamFrames: stage.upstreamFrames as number,
|
|
540
|
+
controlFrames: stage.controlFrames as number,
|
|
541
|
+
relayedEvents: stage.relayedEvents as number,
|
|
542
|
+
firstFrameMs: stage.firstFrameMs as number | null,
|
|
543
|
+
elapsedMs: stage.elapsedMs as number | null,
|
|
544
|
+
pings: stage.pings as number,
|
|
545
|
+
pongs: stage.pongs as number,
|
|
546
|
+
closeCode: stage.closeCode as number | null,
|
|
547
|
+
reused: stage.reused,
|
|
548
|
+
ocxVersion: stage.ocxVersion,
|
|
549
|
+
bunVersion: stage.bunVersion,
|
|
493
550
|
};
|
|
494
551
|
}
|
|
495
552
|
|
|
@@ -77,9 +77,12 @@ type EnrichedProviderCache = Map<string, OcxProviderConfig>;
|
|
|
77
77
|
* not a text-only model and must not be widened to image through the vision sidecar.
|
|
78
78
|
*/
|
|
79
79
|
export function isModelVisionSidecarConsumer(
|
|
80
|
-
provider: Pick<OcxProviderConfig, "noVisionModels" | "modelInputModalities">,
|
|
80
|
+
provider: Pick<OcxProviderConfig, "noVisionModels" | "modelInputModalities" | "modelCapabilities">,
|
|
81
81
|
modelId: string,
|
|
82
82
|
): boolean {
|
|
83
|
+
const declared = Object.hasOwn(provider.modelCapabilities ?? {}, modelId)
|
|
84
|
+
? provider.modelCapabilities?.[modelId]?.inputModalities : undefined;
|
|
85
|
+
if (declared !== undefined) return declared.includes("text") && !declared.includes("image");
|
|
83
86
|
if (modelInList(provider.noVisionModels, modelId)) return true;
|
|
84
87
|
const modalities = modelRecordValue(provider.modelInputModalities, modelId);
|
|
85
88
|
return Array.isArray(modalities) && modalities.includes("text") && !modalities.includes("image");
|
|
@@ -151,10 +154,18 @@ function modelAcceptsImageInputWithCache(
|
|
|
151
154
|
candidate: VisionCandidateModel,
|
|
152
155
|
cache: EnrichedProviderCache,
|
|
153
156
|
): boolean | undefined {
|
|
154
|
-
if (isVisionSidecarConsumerWithCache(config, candidate.provider, candidate.id, cache)) return false;
|
|
155
157
|
if (candidate.native === true || (candidate.provider === "openai" && SUPPORTED_NATIVE_OPENAI_SLUGS.has(candidate.id))) {
|
|
158
|
+
const nativeProvider = enrichedProviderForVision(config, candidate.provider, cache);
|
|
159
|
+
if (nativeProvider && isModelVisionSidecarConsumer({
|
|
160
|
+
noVisionModels: nativeProvider.noVisionModels, modelInputModalities: nativeProvider.modelInputModalities,
|
|
161
|
+
}, candidate.id)) return false;
|
|
156
162
|
return advertisesImageInput(nativeInputModalities(candidate.id)) ?? true;
|
|
157
163
|
}
|
|
164
|
+
if (isVisionSidecarConsumerWithCache(config, candidate.provider, candidate.id, cache)) return false;
|
|
165
|
+
const provider = enrichedProviderForVision(config, candidate.provider, cache);
|
|
166
|
+
const declared = Object.hasOwn(provider?.modelCapabilities ?? {}, candidate.id)
|
|
167
|
+
? provider?.modelCapabilities?.[candidate.id]?.inputModalities : undefined;
|
|
168
|
+
if (declared !== undefined) return declared.includes("image");
|
|
158
169
|
const fromRow = advertisesImageInput(candidate.inputModalities);
|
|
159
170
|
if (fromRow !== undefined) return fromRow;
|
|
160
171
|
return metadataImageInput(candidate.provider, candidate.id);
|
package/src/web-search/loop.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { AdapterEvent, OcxMessage, OcxParsedRequest, OcxProviderConfig, Ocx
|
|
|
3
3
|
import { namespacedToolName, toolChoiceToolPredicate } from "../types";
|
|
4
4
|
import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
|
|
5
5
|
import type { AttemptRecoveryKind } from "../usage/log";
|
|
6
|
+
import { isTruncatedStopReason } from "../responses/truncated-stop-reason";
|
|
6
7
|
import { bridgeToResponsesSSE } from "../bridge";
|
|
7
8
|
import { runWebSearch, type SidecarOutcome, type SidecarOutcomeRecorder, type SidecarSettings } from "./executor";
|
|
8
9
|
import { runAnthropicWebSearch } from "./anthropic-executor";
|
|
@@ -230,6 +231,24 @@ function forcedAnswerNudge(): OcxMessage {
|
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Transient developer-role nudge for the ONE recovery pass after a forced answer came back empty.
|
|
236
|
+
* The recovery also removes every tool, so the model has nothing to call and can only return text;
|
|
237
|
+
* this turn says so explicitly rather than relying on the removal alone. Like {@link forcedAnswerNudge}
|
|
238
|
+
* it is iteration-local and never touches the persisted `messages`.
|
|
239
|
+
*/
|
|
240
|
+
function forcedAnswerRetryNudge(): OcxMessage {
|
|
241
|
+
return {
|
|
242
|
+
role: "developer",
|
|
243
|
+
content:
|
|
244
|
+
"Your previous response contained no usable answer. Web search has finished for this turn and " +
|
|
245
|
+
"no tools are available for this response. Answer the user's question now in assistant text, " +
|
|
246
|
+
"using the web search results already gathered above. If those results are insufficient, say " +
|
|
247
|
+
"what is missing instead of returning an empty response.",
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
233
252
|
function jsonError(status: number, message: string): Response {
|
|
234
253
|
return new Response(JSON.stringify({ error: { message, type: "upstream_error", code: null } }), {
|
|
235
254
|
status,
|
|
@@ -370,7 +389,9 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
370
389
|
const signal = internalAbort.signal;
|
|
371
390
|
|
|
372
391
|
// Hard iteration bound (termination safety net); forceAnswer normally ends the loop sooner.
|
|
373
|
-
|
|
392
|
+
// One iteration beyond the forced answer is reserved for its empty-answer recovery below.
|
|
393
|
+
const HARD_CAP = maxSearches + 3;
|
|
394
|
+
let emptyAnswerRetries = 0;
|
|
374
395
|
const connectTimeoutMs = deps.connectTimeoutMs ?? 200_000;
|
|
375
396
|
const routedModelStallTimeoutMs = deps.routedModelStallTimeoutMs ?? 200_000;
|
|
376
397
|
|
|
@@ -407,12 +428,19 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
407
428
|
// ignores what the search found, which reads to the user as "the search did nothing". Nudge it
|
|
408
429
|
// (iteration-locally — never mutate the shared `messages`) to actually use the gathered results.
|
|
409
430
|
// Only when a REAL search ran (executedSearchCount, not empty-query/limit/repeat placeholders).
|
|
410
|
-
|
|
431
|
+
let iterMessages: OcxMessage[] = forceAnswer && executedSearchCount > 0
|
|
411
432
|
? [...messages, forcedAnswerNudge()]
|
|
412
433
|
: messages;
|
|
434
|
+
// #1001 follow-up: the recovery pass for an empty forced answer. Removing every tool leaves the
|
|
435
|
+
// model nothing to call, and the extra developer turn asks it for the text it just failed to
|
|
436
|
+
// produce. `toolChoice: "none"` is what drops those definitions in the adapter, so the retry
|
|
437
|
+
// cannot repeat the same empty or tool-shaped response.
|
|
438
|
+
const recoveringEmptyAnswer = forceAnswer && emptyAnswerRetries > 0;
|
|
439
|
+
if (recoveringEmptyAnswer) iterMessages = [...iterMessages, forcedAnswerRetryNudge()];
|
|
413
440
|
const iterParsed: OcxParsedRequest = {
|
|
414
441
|
...parsed, stream: true,
|
|
415
|
-
|
|
442
|
+
...(recoveringEmptyAnswer ? { options: { ...parsed.options, toolChoice: "none" as const } } : {}),
|
|
443
|
+
context: { ...parsed.context, messages: iterMessages, tools: recoveringEmptyAnswer ? [] : forceAnswer ? toolsNoWebSearch : allTools },
|
|
416
444
|
};
|
|
417
445
|
// One cumulative header deadline spans every pool-key 429 rotation in this model iteration.
|
|
418
446
|
// clear() stops only its timer after final headers; the direct turn signal remains attached to
|
|
@@ -847,9 +875,34 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
847
875
|
// An unterminated call flushes AFTER the terminal event, so find
|
|
848
876
|
// the terminal rather than assuming it is last (#1001).
|
|
849
877
|
const terminalEvent = split.passthrough.find(event => event.type === "done");
|
|
878
|
+
if (terminalEvent?.type === "done" && !split.hasMalformedToolCall
|
|
879
|
+
&& isTruncatedStopReason(terminalEvent.stopReason)) {
|
|
880
|
+
// A provider refusal or truncation is authoritative, even without text.
|
|
881
|
+
// Preserve it once; neither an empty-answer retry nor a generic 502 applies.
|
|
882
|
+
yield* replay(split.passthrough.slice(split.streamedPassthroughCount));
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
850
885
|
if (terminalEvent?.type === "done"
|
|
851
886
|
&& (split.hasMalformedToolCall
|
|
852
887
|
|| (!split.hasRealToolCall && !hasVisibleAssistantText(split.passthrough)))) {
|
|
888
|
+
// #1001 fixed the silent success by failing here. A malformed call still fails: it
|
|
889
|
+
// reports a protocol problem, and replaying it would only re-ask an unwell upstream.
|
|
890
|
+
// Silence is different — it is recoverable, so retry exactly once with the results
|
|
891
|
+
// already gathered before failing the turn.
|
|
892
|
+
console.warn("[web-search-loop] unusable forced answer", JSON.stringify({
|
|
893
|
+
model: parsed.modelId,
|
|
894
|
+
recoveryAttempt: emptyAnswerRetries,
|
|
895
|
+
searchCalls: split.calls.length,
|
|
896
|
+
malformed: split.hasMalformedToolCall,
|
|
897
|
+
stopReason: terminalEvent.stopReason,
|
|
898
|
+
eventTypes: [...new Set(split.passthrough.map(event => event.type))],
|
|
899
|
+
}));
|
|
900
|
+
if (!split.hasMalformedToolCall && !split.hasRealToolCall && emptyAnswerRetries === 0) {
|
|
901
|
+
emptyAnswerRetries++;
|
|
902
|
+
console.warn("[web-search-loop] empty forced answer — retrying once without tools");
|
|
903
|
+
yield { type: "heartbeat" };
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
853
906
|
throw new LoopError(502, "forced-answer pass produced no usable assistant output");
|
|
854
907
|
}
|
|
855
908
|
}
|