@bitkyc08/opencodex 2.54.0-preview.20260914 → 2.55.0-preview.20260914
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-B4VYfZcY.js → index-DH2PUHqr.js} +10 -10
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +57 -0
- package/src/adapters/anthropic-image-normalize.ts +28 -1
- package/src/adapters/anthropic.ts +68 -6
- package/src/adapters/base.ts +8 -0
- package/src/adapters/coding-agent/protocol.ts +41 -16
- package/src/adapters/cursor/cursor-errors.ts +1 -1
- package/src/adapters/cursor/live-transport.ts +5 -1
- package/src/adapters/cursor/native-exec-fs.ts +10 -10
- package/src/adapters/cursor/native-exec-network.ts +2 -2
- package/src/adapters/cursor/native-exec-shell.ts +13 -12
- package/src/adapters/cursor/native-exec.ts +51 -10
- package/src/adapters/cursor/policy-error.ts +75 -0
- package/src/adapters/cursor/protobuf-request.ts +105 -1
- package/src/adapters/devin/cloud-direct/catalog.ts +34 -2
- package/src/adapters/devin/live-models.ts +33 -2
- package/src/adapters/google-wire-compiler.ts +8 -0
- package/src/adapters/google.ts +46 -0
- package/src/adapters/input-media-guard.ts +45 -0
- package/src/adapters/kiro/adapter.ts +8 -0
- package/src/adapters/kiro/payload.ts +28 -6
- package/src/adapters/kiro-events.ts +25 -6
- package/src/adapters/kiro-images.ts +30 -0
- package/src/adapters/kiro-retry.ts +8 -0
- package/src/adapters/openai-chat.ts +33 -4
- package/src/adapters/openai-responses.ts +26 -0
- package/src/adapters/registry.ts +4 -0
- package/src/bridge.ts +163 -116
- package/src/chat/image-parts.ts +151 -0
- package/src/chat/inbound.ts +70 -33
- package/src/cli/connect.ts +30 -9
- package/src/cli/dispatch.ts +7 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/runtime-api.ts +25 -0
- package/src/cli/status.ts +21 -19
- package/src/cli/system-restart-client.ts +25 -0
- package/src/clients/config-export.ts +14 -4
- package/src/codex/app-server-processes.ts +25 -0
- package/src/codex/auth-context.ts +8 -0
- package/src/codex/autostart-health.ts +36 -2
- package/src/codex/catalog/provider-fetch.ts +41 -0
- package/src/codex/catalog-auto-refresh.ts +182 -0
- package/src/codex/catalog-refresh-status.ts +93 -0
- package/src/codex/history-provider.ts +55 -0
- package/src/codex/model-entitlements.ts +78 -0
- package/src/codex/native-profile-processes.ts +114 -15
- package/src/codex/prompt-text-probe.ts +274 -41
- package/src/codex/routing-adoption.ts +189 -0
- package/src/codex/routing.ts +520 -48
- package/src/codex/runtime.ts +249 -7
- package/src/combos/failover.ts +45 -0
- package/src/config.ts +124 -4
- package/src/generated/compatibility-version.json +110 -74
- package/src/generated/model-metadata.ts +1 -0
- package/src/lib/request-execution-budget.ts +202 -0
- package/src/lib/upstream-retry.ts +95 -8
- package/src/lib/workflow-budget.ts +172 -0
- package/src/oauth/devin.ts +57 -12
- package/src/providers/quota.ts +37 -6
- package/src/providers/registry.ts +53 -6
- package/src/responses/input-media.ts +65 -0
- package/src/responses/parser-content.ts +42 -0
- package/src/responses/schema.ts +12 -2
- package/src/server/audio-live.ts +1 -2
- package/src/server/audio-transcriptions.ts +1 -2
- package/src/server/auth-cors.ts +1 -1
- package/src/server/background-lifecycle.ts +18 -0
- package/src/server/chat-completions.ts +23 -8
- package/src/server/chat-native.ts +17 -17
- package/src/server/index.ts +24 -0
- package/src/server/management/request-history-routes.ts +5 -0
- package/src/server/request-log.ts +8 -2
- package/src/server/responses/compact.ts +51 -3
- package/src/server/responses/core.ts +238 -28
- package/src/server/search.ts +7 -9
- package/src/types/config.ts +51 -6
- package/src/usage/log.ts +37 -0
- package/src/vision/eligibility.ts +37 -4
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +45 -10
- package/src/web-search/alpha-search.ts +324 -0
- package/src/web-search/index.ts +13 -22
- package/src/web-search/passthrough-bridge.ts +195 -22
- package/src/web-search/sidecar-providers.ts +22 -0
|
@@ -48,6 +48,7 @@ const DATA: Record<string, readonly Row[]> = {
|
|
|
48
48
|
"mistral": [["codestral-latest",256000,4096,"text",0,null,0.3,0.9,0,0],["devstral-2512",262144,262144,"text",0,null,0.4,2,0,0],["devstral-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-medium-2507",128000,128000,"text",0,null,0.4,2,0,0],["devstral-medium-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-small-2505",128000,128000,"text",0,null,0.1,0.3,0,0],["devstral-small-2507",128000,128000,"text",0,null,0.1,0.3,0,0],["labs-devstral-small-2512",256000,256000,"text,image",0,null,0,0,0,0],["magistral-medium-latest",128000,16384,"text",1,null,2,5,0,0],["magistral-small",128000,128000,"text",1,null,0.5,1.5,0,0],["ministral-3b-latest",128000,128000,"text",0,null,0.04,0.04,0,0],["ministral-8b-latest",128000,128000,"text",0,null,0.1,0.1,0,0],["mistral-large-2411",131072,16384,"text",0,null,2,6,0,0],["mistral-large-2512",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-large-latest",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-medium-2505",131072,131072,"text,image",0,null,0.4,2,0,0],["mistral-medium-2508",262144,262144,"text,image",0,null,0.4,2,0,0],["mistral-medium-2604",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-medium-latest",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["mistral-small-2506",128000,16384,"text,image",0,null,0.1,0.3,0,0],["mistral-small-2603",256000,256000,"text,image",1,null,0.15,0.6,0,0],["mistral-small-latest",256000,256000,"text,image",1,null,0.15,0.6,0,0],["open-mistral-7b",8000,8000,"text",0,null,0.25,0.25,0,0],["open-mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["open-mixtral-8x22b",64000,64000,"text",0,null,2,6,0,0],["open-mixtral-8x7b",32000,32000,"text",0,null,0.7,0.7,0,0],["pixtral-12b",128000,128000,"text,image",0,null,0.15,0.15,0,0],["pixtral-large-latest",128000,128000,"text,image",0,null,2,6,0,0]],
|
|
49
49
|
"moonshot": [["kimi-k2.5",262144,65536,"text,image",1,null,0,0,0,0]],
|
|
50
50
|
"openai": [["codex-mini-latest",200000,100000,"text",1,null,1.5,6,0.375,0],["gpt-4",8192,8192,"text",0,null,30,60,0,0],["gpt-4-turbo",128000,4096,"text,image",0,null,10,30,0,0],["gpt-4.1",1047576,32768,"text,image",0,null,2,8,0.5,0],["gpt-4.1-mini",1047576,32768,"text,image",0,null,0.4,1.6,0.1,0],["gpt-4.1-nano",1047576,32768,"text,image",0,null,0.1,0.4,0.025,0],["gpt-4o",128000,16384,"text,image",0,null,2.5,10,1.25,0],["gpt-4o-2024-05-13",128000,4096,"text,image",0,null,5,15,0,0],["gpt-4o-2024-08-06",128000,16384,"text,image",0,null,2.5,10,1.25,0],["gpt-4o-2024-11-20",128000,16384,"text,image",0,null,2.5,10,1.25,0],["gpt-4o-mini",128000,16384,"text,image",0,null,0.15,0.6,0.075,0],["gpt-5",400000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5-chat-latest",128000,16384,"text,image",0,null,1.25,10,0.125,0],["gpt-5-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5-mini",400000,128000,"text,image",1,null,0.25,2,0.025,0],["gpt-5-nano",400000,128000,"text,image",1,null,0.05,0.4,0.005,0],["gpt-5-pro",400000,272000,"text,image",1,null,15,120,0,0],["gpt-5.1",400000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-chat-latest",128000,16384,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-codex-max",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-codex-mini",272000,128000,"text,image",1,null,0.25,2,0.025,0],["gpt-5.2",400000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.2-chat-latest",128000,16384,"text,image",1,null,1.75,14,0.175,0],["gpt-5.2-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.2-pro",400000,128000,"text,image",1,null,21,168,0,0],["gpt-5.3-chat-latest",128000,16384,"text,image",0,null,1.75,14,0.175,0],["gpt-5.3-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.3-codex-spark",128000,32000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.4",1050000,128000,"text,image",1,null,2.5,15,0.25,0],["gpt-5.4-mini",400000,128000,"text,image",1,null,0.75,4.5,0.075,0],["gpt-5.4-nano",400000,128000,"text,image",1,null,0.2,1.25,0.02,0],["gpt-5.4-pro",1050000,128000,"text,image",1,null,30,180,0,0],["gpt-5.5",1050000,128000,"text,image",1,null,5,30,0.5,0],["gpt-5.5-pro",1050000,128000,"text,image",1,null,30,180,0,0],["gpt-5.6",373000,128000,"text,image",1,null,5,30,0.5,6.25],["gpt-5.6-luna",373000,128000,"text,image",1,null,0.2,1.2,0.02,0.25],["gpt-5.6-sol",373000,128000,"text,image",1,null,5,30,0.5,6.25],["gpt-5.6-terra",373000,128000,"text,image",1,null,2,12,0.2,2.5],["gpt-realtime-2.1",128000,32000,"text,image",1,null,4,24,0.4,0],["o1",200000,100000,"text,image",1,null,15,60,7.5,0],["o1-pro",200000,100000,"text,image",1,null,150,600,0,0],["o3",200000,100000,"text,image",1,null,2,8,0.5,0],["o3-deep-research",200000,100000,"text,image",1,null,10,40,2.5,0],["o3-mini",200000,100000,"text",1,null,1.1,4.4,0.55,0],["o3-pro",200000,100000,"text,image",1,null,20,80,0,0],["o4-mini",200000,100000,"text,image",1,null,1.1,4.4,0.275,0],["o4-mini-deep-research",200000,100000,"text,image",1,null,2,8,0.5,0]],
|
|
51
|
+
"openai-codex": [["codex-auto-review",1000000,128000,"text,image",1,null,0,0,0,0],["gpt-5",400000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5-codex-mini",272000,128000,"text,image",1,null,0,0,0,0],["gpt-5.1",400000,128000,"text,image",1,null,1.25,10,0.13,0],["gpt-5.1-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-codex-max",272000,128000,"text,image",1,null,1.25,10,0.125,0],["gpt-5.1-codex-mini",272000,128000,"text,image",1,null,0.25,2,0.025,0],["gpt-5.2",272000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.2-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.3-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["gpt-5.3-codex-spark",128000,128000,"text",1,null,1.75,14,0.175,0],["gpt-5.4",1000000,128000,"text,image",1,null,2.5,15,0.25,0],["gpt-5.4-mini",272000,128000,"text,image",1,null,0.75,4.5,0.075,0],["gpt-5.4-nano",272000,128000,"text,image",1,null,0.2,1.25,0.02,0],["gpt-5.5",272000,128000,"text,image",1,null,5,30,0.5,0],["gpt-5.6-luna",373000,128000,"text,image",1,null,0.2,1.2,0.02,0.25],["gpt-5.6-sol",373000,128000,"text,image",1,null,5,30,0.5,6.25],["gpt-5.6-terra",373000,128000,"text,image",1,null,2,12,0.2,2.5]],
|
|
51
52
|
"opencode-go": [["deepseek-v4-flash",1000000,384000,"text",1,null,0.14,0.28,0.0028,0],["deepseek-v4-pro",1000000,384000,"text",1,null,1.74,3.48,0.0145,0],["glm-5",204800,131072,"text",1,null,1,3.2,0.2,0],["glm-5.1",200000,131072,"text",1,null,1.4,4.4,0.26,0],["glm-5.2",1000000,131072,"text",1,null,1.4,4.4,0.26,0],["glm-5.3",1000000,131072,"text",1,null,1.4,4.4,0.26,0],["grok-4.5",500000,500000,"text,image",1,null,2,6,0.5,0],["grok-4.6",500000,500000,"text,image",1,null,2,6,0.5,0],["hy3",256000,64000,"text",1,null,0.14,0.58,0.035,0],["kimi-k2.5",262144,262144,"text,image",1,null,0.3,1.9,0,0],["kimi-k2.6",262144,262144,"text,image",1,null,0.95,4,0.2,0],["kimi-k2.7-code",262144,262144,"text,image",1,null,0.95,4,0.19,0],["kimi-k3",1048576,131072,"text,image",1,null,3,15,0.3,0],["mimo-v2-omni",262144,131072,"text,image",1,null,0.4,2,0.08,0],["mimo-v2-pro",1048576,131072,"text",1,null,1,3,0.2,0],["mimo-v2.5",1048576,131072,"text,image",1,null,0.14,0.28,0.0028,0],["mimo-v2.5-pro",1048576,131072,"text",1,null,1.74,3.48,0.0145,0],["minimax-m2.5",204800,131072,"text",1,null,0.3,1.2,0.06,0.375],["minimax-m2.7",204800,131072,"text",1,null,0.3,1.2,0.06,0.375],["minimax-m3",512000,128000,"text,image",1,null,0.3,1.2,0.06,0],["qwen3.5-plus",1000000,65536,"text,image",1,null,0.4,2.4,0,0],["qwen3.6-plus",1000000,65536,"text,image",1,null,2,6,0.2,2.5],["qwen3.7-max",1000000,65536,"text",1,null,2.5,7.5,0.5,3.125],["qwen3.7-plus",1000000,64000,"text,image",1,null,1.2,4.8,0.12,1.5]],
|
|
52
53
|
"openrouter": [["~anthropic/claude-fable-latest",1000000,128000,"text,image",1,null,10,50,1,12.5],["~anthropic/claude-haiku-latest",200000,64000,"text,image",1,null,1,5,0.09999999999999999,1.25],["~anthropic/claude-opus-latest",1000000,128000,"text,image",1,null,5,25,0.5,6.25],["~anthropic/claude-sonnet-latest",1000000,128000,"text,image",1,null,2,10,0.19999999999999998,2.5],["~google/gemini-flash-latest",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0.08333333333333334],["~google/gemini-pro-latest",1048576,65536,"text,image",1,null,2,12,0.19999999999999998,0.375],["~moonshotai/kimi-latest",1048576,8888,"text,image",1,null,3,15,0.3,0],["~openai/gpt-latest",1050000,128000,"text,image",1,null,5,30,0.5,6.25],["~openai/gpt-mini-latest",400000,128000,"text,image",1,null,0.75,4.5,0.075,0],["~x-ai/grok-latest",500000,8888,"text,image",1,null,2,6,0.3,0],["ai21/jamba-large-1.7",256000,4096,"text",0,null,2,8,0,0],["aion-labs/aion-2.0",131072,32768,"text",1,null,0.7999999999999999,1.5999999999999999,0.19999999999999998,0],["aion-labs/aion-3.0",131072,32768,"text",1,null,3,6,0.75,0],["aion-labs/aion-3.0-mini",131072,32768,"text",1,null,0.7,1.4,0.18,0],["alibaba/tongyi-deepresearch-30b-a3b",131072,131072,"text",1,null,0.09,0.44999999999999996,0.09,0],["allenai/olmo-3.1-32b-instruct",65536,16384,"text",0,null,0.19999999999999998,0.6,0,0],["amazon/nova-2-lite-v1",1000000,65535,"text,image",1,null,0.3,2.5,0,0],["amazon/nova-lite-v1",300000,5120,"text,image",0,null,0.06,0.24,0,0],["amazon/nova-micro-v1",128000,5120,"text",0,null,0.035,0.14,0,0],["amazon/nova-premier-v1",1000000,32000,"text,image",0,null,2.5,12.5,0.625,0],["amazon/nova-pro-v1",300000,5120,"text,image",0,null,0.7999999999999999,3.1999999999999997,0,0],["anthropic/claude-3-haiku",200000,4096,"text,image",0,null,0.25,1.25,0.03,0.3],["anthropic/claude-3.5-haiku",200000,8192,"text,image",0,null,0.7999999999999999,4,0.08,1],["anthropic/claude-3.5-sonnet",200000,8192,"text,image",0,null,6,30,0.6,7.5],["anthropic/claude-3.7-sonnet",200000,128000,"text,image",1,null,3,15,0.3,3.75],["anthropic/claude-3.7-sonnet:thinking",200000,64000,"text,image",1,null,3,15,0.3,3.75],["anthropic/claude-fable-5",1000000,128000,"text,image",1,null,10,50,1,12.5],["anthropic/claude-haiku-4.5",200000,64000,"text,image",0,null,1,5,0.09999999999999999,1.25],["anthropic/claude-opus-4",200000,32000,"text,image",1,null,15,75,1.5,18.75],["anthropic/claude-opus-4.1",200000,32000,"text,image",1,null,15,75,1.5,18.75],["anthropic/claude-opus-4.5",200000,64000,"text,image",1,null,5,25,0.5,6.25],["anthropic/claude-opus-4.6",1000000,128000,"text,image",1,null,5,25,0.5,6.25],["anthropic/claude-opus-4.6-fast",1000000,128000,"text,image",1,null,30,150,3,37.5],["anthropic/claude-opus-4.7",1000000,128000,"text,image",1,null,5,25,0.5,6.25],["anthropic/claude-opus-4.7-fast",1000000,128000,"text,image",1,null,30,150,3,37.5],["anthropic/claude-opus-4.8",1000000,128000,"text,image",1,null,5,25,0.5,6.25],["anthropic/claude-opus-4.8-fast",1000000,128000,"text,image",1,null,10,50,1,12.5],["anthropic/claude-opus-5",1000000,128000,"text,image",1,null,5,25,0.5,6.25],["anthropic/claude-opus-5-fast",1000000,128000,"text,image",1,null,10,50,1,12.5],["anthropic/claude-sonnet-4",1000000,64000,"text,image",1,null,3,15,0.3,3.75],["anthropic/claude-sonnet-4.5",1000000,64000,"text,image",1,null,3,15,0.3,3.75],["anthropic/claude-sonnet-4.6",1000000,128000,"text,image",1,null,3,15,0.3,3.75],["anthropic/claude-sonnet-5",1000000,128000,"text,image",1,null,2,10,0.19999999999999998,2.5],["arcee-ai/trinity-large-preview",131000,8888,"text",0,null,0.15,0.44999999999999996,0,0],["arcee-ai/trinity-large-preview:free",131000,8888,"text",0,null,0,0,0,0],["arcee-ai/trinity-large-thinking",262144,262144,"text",1,null,0.22,0.85,0.06,0],["arcee-ai/trinity-large-thinking:free",262144,80000,"text",1,null,0,0,0,0],["arcee-ai/trinity-mini",131072,131072,"text",1,null,0.045,0.15,0,0],["arcee-ai/trinity-mini:free",131072,8888,"text",1,null,0,0,0,0],["arcee-ai/virtuoso-large",131072,64000,"text",0,null,0.75,1.2,0,0],["auto",2000000,30000,"text,image",1,null,0,0,0,0],["baidu/cobuddy:free",131072,65536,"text",1,null,0,0,0,0],["baidu/ernie-4.5-21b-a3b",131072,8000,"text",0,null,0.07,0.28,0,0],["baidu/ernie-4.5-vl-28b-a3b",131072,8000,"text,image",1,null,0.14,0.56,0,0],["bytedance-seed/seed-1.6",262144,32768,"text,image",1,null,0.25,2,0,0],["bytedance-seed/seed-1.6-flash",262144,32768,"text,image",1,null,0.075,0.3,0,0],["bytedance-seed/seed-2.0-lite",262144,131072,"text,image",1,null,0.25,2,0,0],["bytedance-seed/seed-2.0-mini",262144,131072,"text,image",1,null,0.09999999999999999,0.39999999999999997,0,0],["cohere/command-r-08-2024",128000,4000,"text",0,null,0.15,0.6,0,0],["cohere/command-r-plus-08-2024",128000,4000,"text",0,null,2.5,10,0,0],["cohere/north-mini-code:free",256000,64000,"text",1,null,0,0,0,0],["deepseek/deepseek-chat",163840,16000,"text",0,null,0.20020000000000002,0.8000999999999999,0.15,0],["deepseek/deepseek-chat-v3-0324",163840,65536,"text",1,null,0.27,1.12,0.135,0],["deepseek/deepseek-chat-v3.1",163840,32768,"text",1,null,0.25,0.95,0.13,0],["deepseek/deepseek-r1",163840,16000,"text",1,null,0.7,2.5,0,0],["deepseek/deepseek-r1-0528",163840,32768,"text",1,null,0.5,2.1500000000000004,0.35,0],["deepseek/deepseek-v3.1-terminus",163840,32768,"text",1,null,0.27,1,0.135,0],["deepseek/deepseek-v3.1-terminus:exacto",163840,8888,"text",1,null,0.21,0.7899999999999999,0.16799999999999998,0],["deepseek/deepseek-v3.2",163840,65536,"text",1,null,0.26899999999999996,0.39999999999999997,0.13449999999999998,0],["deepseek/deepseek-v3.2-exp",163840,65536,"text",1,null,0.27,0.41,0,0],["deepseek/deepseek-v4-flash",1048576,384000,"text",1,null,0.09380000000000001,0.18760000000000002,0.01876,0],["deepseek/deepseek-v4-flash:free",1048576,384000,"text",1,null,0,0,0,0],["deepseek/deepseek-v4-pro",1048576,384000,"text",1,null,0.435,0.87,0.003625,0],["essentialai/rnj-1-instruct",32768,8888,"text",0,null,0.15,0.15,0,0],["google/gemini-2.0-flash-001",1048576,8192,"text,image",0,null,0.09999999999999999,0.39999999999999997,0.024999999999999998,0.08333333333333334],["google/gemini-2.0-flash-lite-001",1048576,8192,"text,image",0,null,0.075,0.3,0,0],["google/gemini-2.5-flash",1048576,65535,"text,image",1,null,0.3,2.5,0.03,0.08333333333333334],["google/gemini-2.5-flash-lite",1048576,65535,"text,image",0,null,0.09999999999999999,0.39999999999999997,0.01,0.08333333333333334],["google/gemini-2.5-flash-lite-preview-09-2025",1048576,65535,"text,image",1,null,0.09999999999999999,0.39999999999999997,0.01,0.08333333333333334],["google/gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0.08333333333333334],["google/gemini-2.5-pro",1048576,65536,"text,image",1,null,1.25,10,0.125,0.375],["google/gemini-2.5-pro-preview",1048576,65536,"text,image",1,null,1.25,10,0.125,0.375],["google/gemini-2.5-pro-preview-05-06",1048576,65535,"text,image",1,null,1.25,10,0.125,0.375],["google/gemini-3-flash-preview",1048576,65535,"text,image",1,null,0.5,3,0.049999999999999996,0.08333333333333334],["google/gemini-3-pro-image",131072,32768,"text,image",1,null,2,12,0.19999999999999998,0.375],["google/gemini-3-pro-preview",1048000,64000,"text,image",1,null,2,12,0.19999999999999998,0.375],["google/gemini-3.1-flash-lite",1048576,65536,"text,image",1,null,0.25,1.5,0.024999999999999998,0.08333333333333334],["google/gemini-3.1-flash-lite-preview",1048576,65536,"text,image",0,null,0.25,1.5,0.024999999999999998,0.08333333333333334],["google/gemini-3.1-pro-preview",1048576,65536,"text,image",1,null,2,12,0.19999999999999998,0.375],["google/gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1,null,2,12,0.19999999999999998,0.375],["google/gemini-3.5-flash",1048576,65536,"text,image",1,null,1.5,9,0.15,0.08333333333333334],["google/gemini-3.5-flash-lite",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0.08333333333333334],["google/gemini-3.6-flash",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0.08333333333333334],["google/gemma-3-12b-it",131072,16384,"text,image",0,null,0.049999999999999996,0.15,0,0],["google/gemma-3-27b-it",262144,131072,"text,image",1,null,0.08,0.44999999999999996,0.04,0],["google/gemma-3-27b-it:free",131072,8192,"text,image",0,null,0,0,0,0],["google/gemma-4-26b-a4b-it",262144,262144,"text,image",1,null,0.12,0.35,0.049999999999999996,0],["google/gemma-4-26b-a4b-it:free",262144,32768,"text,image",1,null,0,0,0,0],["google/gemma-4-31b-it",262144,262144,"text,image",1,null,0.14,0.39999999999999997,0.09,0],["google/gemma-4-31b-it:free",262144,32768,"text,image",1,null,0,0,0,0],["ibm-granite/granite-4.1-8b",131072,131072,"text",0,null,0.049999999999999996,0.09999999999999999,0.049999999999999996,0],["inception/mercury",128000,32000,"text",0,null,0.25,0.75,0.024999999999999998,0],["inception/mercury-2",128000,50000,"text",1,null,0.25,0.75,0.024999999999999998,0],["inception/mercury-coder",128000,32000,"text",0,null,0.25,0.75,0.024999999999999998,0],["inclusionai/ling-2.6-1t",262144,32768,"text",0,null,0.075,0.625,0.015,0],["inclusionai/ling-2.6-1t:free",262144,32768,"text",0,null,0,0,0,0],["inclusionai/ling-2.6-flash",262144,32768,"text",0,null,0.01,0.03,0.002,0],["inclusionai/ling-2.6-flash:free",262144,32768,"text",0,null,0,0,0,0],["inclusionai/ling-3.0-flash:free",262144,32768,"text",1,null,0,0,0,0],["inclusionai/ring-2.6-1t",262144,65536,"text",1,null,0.075,0.625,0.015,0],["inclusionai/ring-2.6-1t:free",262144,65536,"text",1,null,0,0,0,0],["kwaipilot/kat-coder-air-v2.5",256000,80000,"text",0,null,0.15,0.6,0.03,0],["kwaipilot/kat-coder-pro",256000,128000,"text",0,null,0.207,0.828,0.0414,0],["kwaipilot/kat-coder-pro-v2",262144,80000,"text",0,null,0.3,1.2,0.06,0],["kwaipilot/kat-coder-pro-v2.5",256000,80000,"text",0,null,0.74,2.96,0.15,0],["liquid/lfm-2.5-1.2b-thinking:free",32768,8888,"text",1,null,0,0,0,0],["meituan/longcat-2.0",1048756,262144,"text",1,null,0.3,1.2,0.006,0],["meituan/longcat-flash-chat",131072,131072,"text",0,null,0.19999999999999998,0.7999999999999999,0.19999999999999998,0],["meta-llama/llama-3-8b-instruct",8192,16384,"text",0,null,0.03,0.04,0,0],["meta-llama/llama-3.1-405b-instruct",131000,8888,"text",0,null,4,4,0,0],["meta-llama/llama-3.1-70b-instruct",131072,16384,"text",0,null,0.39999999999999997,0.39999999999999997,0,0],["meta-llama/llama-3.1-8b-instruct",131072,131072,"text",0,null,0.049999999999999996,0.08,0.024999999999999998,0],["meta-llama/llama-3.3-70b-instruct",131072,128000,"text",0,null,0.13,0.39999999999999997,0,0],["meta-llama/llama-3.3-70b-instruct:free",131072,8888,"text",0,null,0,0,0,0],["meta-llama/llama-4-maverick",1048576,16384,"text,image",0,null,0.19999999999999998,0.7999999999999999,0,0],["meta-llama/llama-4-scout",1310720,16384,"text,image",0,null,0.09999999999999999,0.3,0,0],["meta/muse-spark-1.1",1048576,8888,"text,image",1,null,1.25,4.25,0.15,0],["minimax/minimax-m1",1000000,40000,"text",1,null,0.55,2.2,0,0],["minimax/minimax-m2",204800,131072,"text",1,null,0.255,1.02,0.03,0],["minimax/minimax-m2.1",204800,131072,"text",1,null,0.3,1.2,0.03,0],["minimax/minimax-m2.5",204800,196608,"text",1,null,0.15,0.8999999999999999,0.049999999999999996,0],["minimax/minimax-m2.5:free",262144,8192,"text",1,null,0,0,0,0],["minimax/minimax-m2.7",204800,131072,"text",1,null,0.25,1,0.049999999999999996,0],["minimax/minimax-m3",1048576,512000,"text,image",1,null,0.3,1.2,0.06,0],["mistralai/codestral-2508",256000,8888,"text",0,null,0.3,0.8999999999999999,0.03,0],["mistralai/devstral-2512",262144,8888,"text",0,null,0.39999999999999997,2,0.04,0],["mistralai/devstral-medium",131072,8888,"text",0,null,0.39999999999999997,2,0.04,0],["mistralai/devstral-small",131072,8888,"text",0,null,0.09999999999999999,0.3,0.01,0],["mistralai/ministral-14b-2512",262144,8888,"text,image",0,null,0.19999999999999998,0.19999999999999998,0.02,0],["mistralai/ministral-3b-2512",131072,8888,"text,image",0,null,0.09999999999999999,0.09999999999999999,0.01,0],["mistralai/ministral-8b-2512",262144,8888,"text,image",0,null,0.15,0.15,0.015,0],["mistralai/mistral-large",128000,8888,"text",0,null,2,6,0.19999999999999998,0],["mistralai/mistral-large-2407",131072,8888,"text",0,null,2,6,0.19999999999999998,0],["mistralai/mistral-large-2411",131072,8888,"text",0,null,2,6,0.19999999999999998,0],["mistralai/mistral-large-2512",262144,8888,"text,image",0,null,0.5,1.5,0.049999999999999996,0],["mistralai/mistral-medium-3",131072,8888,"text,image",0,null,0.39999999999999997,2,0.04,0],["mistralai/mistral-medium-3-5",262144,8888,"text,image",1,null,1.5,7.5,0,0],["mistralai/mistral-medium-3.1",131072,8888,"text,image",0,null,0.39999999999999997,2,0.04,0],["mistralai/mistral-nemo",131072,16384,"text",0,null,0.019000000000000003,0.03,0,0],["mistralai/mistral-saba",32768,8888,"text",0,null,0.19999999999999998,0.6,0.02,0],["mistralai/mistral-small-24b-instruct-2501",32768,16384,"text",0,null,0.049999999999999996,0.08,0,0],["mistralai/mistral-small-2603",262144,8888,"text,image",1,null,0.15,0.6,0.015,0],["mistralai/mistral-small-3.1-24b-instruct",131072,131072,"text,image",0,null,0.03,0.11,0.015,0],["mistralai/mistral-small-3.1-24b-instruct:free",128000,8888,"text,image",0,null,0,0,0,0],["mistralai/mistral-small-3.2-24b-instruct",256000,8888,"text,image",0,null,0.09999999999999999,0.3,0.01,0],["mistralai/mistral-small-creative",32768,8888,"text",0,null,0.09999999999999999,0.3,0.01,0],["mistralai/mixtral-8x22b-instruct",65536,13108,"text",0,null,2,6,0.19999999999999998,0],["mistralai/mixtral-8x7b-instruct",32768,16384,"text",0,null,0.54,0.54,0,0],["mistralai/pixtral-large-2411",131072,8888,"text,image",0,null,2,6,0.19999999999999998,0],["mistralai/voxtral-small-24b-2507",32000,8888,"text",0,null,0.09999999999999999,0.3,0.01,0],["moonshotai/kimi-k2",131072,100352,"text",0,null,0.5700000000000001,2.3,0,0],["moonshotai/kimi-k2-0905",262144,100352,"text",0,null,0.6,2.5,0.15,0],["moonshotai/kimi-k2-0905:exacto",262144,8888,"text",0,null,0.6,2.5,0,0],["moonshotai/kimi-k2-thinking",262144,100352,"text",1,null,0.6,2.5,0.15,0],["moonshotai/kimi-k2.5",262144,262144,"text,image",1,null,0.5700000000000001,2.8499999999999996,0.095,0],["moonshotai/kimi-k2.6",262144,262144,"text,image",1,null,0.646,2.7199999999999998,0.1088,0],["moonshotai/kimi-k2.6:free",262144,8888,"text,image",1,null,0,0,0,0],["moonshotai/kimi-k2.7-code",262144,262144,"text,image",1,null,0.78,3.5,0.15,0],["moonshotai/kimi-k3",1048576,131072,"text,image",1,null,3,15,0.3,0],["nex-agi/deepseek-v3.1-nex-n1",131072,163840,"text",0,null,0.135,0.5,0,0],["nex-agi/nex-n2-mini",262144,262144,"text,image",1,null,0.024999999999999998,0.09999999999999999,0.0025,0],["nex-agi/nex-n2-pro",262144,262144,"text,image",1,null,0.25,1,0.024999999999999998,0],["nex-agi/nex-n2-pro:free",262144,262144,"text,image",1,null,0,0,0,0],["nousresearch/deephermes-3-mistral-24b-preview",32768,32768,"text",1,null,0.02,0.09999999999999999,0.01,0],["nousresearch/hermes-4-70b",131072,131072,"text",1,null,0.11,0.38,0.055,0],["nvidia/llama-3.1-nemotron-70b-instruct",131072,16384,"text",0,null,1.2,1.2,0,0],["nvidia/llama-3.3-nemotron-super-49b-v1.5",131072,16384,"text",1,null,0.39999999999999997,0.39999999999999997,0,0],["nvidia/nemotron-3-nano-30b-a3b",262144,228000,"text",1,null,0.049999999999999996,0.19999999999999998,0,0],["nvidia/nemotron-3-nano-30b-a3b:free",256000,8888,"text",1,null,0,0,0,0],["nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",256000,65536,"text,image",1,null,0,0,0,0],["nvidia/nemotron-3-super-120b-a12b",1000000,16384,"text",1,null,0.08499999999999999,0.39999999999999997,0.09999999999999999,0],["nvidia/nemotron-3-super-120b-a12b:free",262144,262144,"text",1,null,0,0,0,0],["nvidia/nemotron-3-ultra-550b-a55b",512288,65536,"text",1,null,0.6,3.5999999999999996,0.19999999999999998,0],["nvidia/nemotron-3-ultra-550b-a55b:free",1000000,65536,"text",1,null,0,0,0,0],["nvidia/nemotron-nano-12b-v2-vl:free",128000,128000,"text,image",1,null,0,0,0,0],["nvidia/nemotron-nano-9b-v2",131072,16384,"text",1,null,0.04,0.16,0,0],["nvidia/nemotron-nano-9b-v2:free",128000,8888,"text",1,null,0,0,0,0],["openai/gpt-3.5-turbo",16385,4096,"text",0,null,0.5,1.5,0,0],["openai/gpt-3.5-turbo-0613",4095,4096,"text",0,null,1,2,0,0],["openai/gpt-3.5-turbo-16k",16385,4096,"text",0,null,3,4,0,0],["openai/gpt-4",8191,8192,"text",0,null,30,60,0,0],["openai/gpt-4-0314",8191,4096,"text",0,null,30,60,0,0],["openai/gpt-4-1106-preview",128000,4096,"text",0,null,10,30,0,0],["openai/gpt-4-turbo",128000,4096,"text,image",0,null,10,30,0,0],["openai/gpt-4-turbo-preview",128000,4096,"text",0,null,10,30,0,0],["openai/gpt-4.1",1047576,32768,"text,image",0,null,2,8,0.5,0],["openai/gpt-4.1-mini",1047576,32768,"text,image",0,null,0.39999999999999997,1.5999999999999999,0.09999999999999999,0],["openai/gpt-4.1-nano",1047576,32768,"text,image",0,null,0.09999999999999999,0.39999999999999997,0.024999999999999998,0],["openai/gpt-4o",128000,16384,"text,image",0,null,2.5,10,1.25,0],["openai/gpt-4o-2024-05-13",128000,4096,"text,image",0,null,5,15,0,0],["openai/gpt-4o-2024-08-06",128000,16384,"text,image",0,null,2.5,10,1.25,0],["openai/gpt-4o-2024-11-20",128000,16384,"text,image",0,null,2.5,10,1.25,0],["openai/gpt-4o-audio-preview",128000,16384,"text",0,null,2.5,10,0,0],["openai/gpt-4o-mini",128000,16384,"text,image",0,null,0.15,0.6,0.075,0],["openai/gpt-4o-mini-2024-07-18",128000,16384,"text,image",0,null,0.15,0.6,0.075,0],["openai/gpt-4o:extended",128000,64000,"text,image",0,null,6,18,0,0],["openai/gpt-5",400000,128000,"text,image",1,null,1.25,10,0.125,0],["openai/gpt-5-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["openai/gpt-5-image",400000,128000,"text,image",1,null,10,10,1.25,0],["openai/gpt-5-image-mini",400000,128000,"text,image",1,null,2.5,2,0.25,0],["openai/gpt-5-mini",400000,128000,"text,image",1,null,0.25,2,0.024999999999999998,0],["openai/gpt-5-nano",400000,128000,"text,image",1,null,0.049999999999999996,0.39999999999999997,0.005,0],["openai/gpt-5-pro",400000,128000,"text,image",1,null,15,120,0,0],["openai/gpt-5.1",400000,128000,"text,image",1,null,1.25,10,0.125,0],["openai/gpt-5.1-chat",128000,16384,"text,image",0,null,1.25,10,0.125,0],["openai/gpt-5.1-codex",272000,128000,"text,image",1,null,1.25,10,0.125,0],["openai/gpt-5.1-codex-max",272000,128000,"text,image",1,null,1.25,10,0.125,0],["openai/gpt-5.1-codex-mini",272000,100000,"text,image",1,null,0.25,2,0.024999999999999998,0],["openai/gpt-5.2",400000,128000,"text,image",1,null,1.75,14,0.175,0],["openai/gpt-5.2-chat",128000,16384,"text,image",0,null,1.75,14,0.175,0],["openai/gpt-5.2-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["openai/gpt-5.2-pro",400000,128000,"text,image",1,null,21,168,0,0],["openai/gpt-5.3-chat",128000,16384,"text",0,null,1.75,14,0.175,0],["openai/gpt-5.3-codex",272000,128000,"text,image",1,null,1.75,14,0.175,0],["openai/gpt-5.4",1050000,128000,"text,image",1,null,2.5,15,0.25,0],["openai/gpt-5.4-mini",400000,128000,"text",0,null,0.75,4.5,0.075,0],["openai/gpt-5.4-nano",400000,128000,"text",0,null,0.19999999999999998,1.25,0.02,0],["openai/gpt-5.4-pro",1050000,128000,"text,image",1,null,30,180,0,0],["openai/gpt-5.5",1050000,128000,"text,image",1,null,5,30,0.5,0],["openai/gpt-5.5-pro",1050000,128000,"text,image",1,null,30,180,0,0],["openai/gpt-5.6-luna",373000,128000,"text,image",1,null,1,6,0.09999999999999999,1.25],["openai/gpt-5.6-luna-pro",373000,128000,"text,image",1,null,1,6,0.09999999999999999,1.25],["openai/gpt-5.6-sol",373000,128000,"text,image",1,null,5,30,0.5,6.25],["openai/gpt-5.6-sol-pro",373000,128000,"text,image",1,null,5,30,0.5,6.25],["openai/gpt-5.6-terra",373000,128000,"text,image",1,null,2.5,15,0.25,3.125],["openai/gpt-5.6-terra-pro",373000,128000,"text,image",1,null,2.5,15,0.25,3.125],["openai/gpt-audio",128000,16384,"text",0,null,2.5,10,0,0],["openai/gpt-audio-mini",128000,16384,"text",0,null,0.6,2.4,0,0],["openai/gpt-chat-latest",400000,128000,"text,image",0,null,5,30,0.5,0],["openai/gpt-oss-120b",131072,131072,"text",1,null,0.037,0.16999999999999998,0,0],["openai/gpt-oss-120b:exacto",131072,8888,"text",1,null,0.039,0.19,0,0],["openai/gpt-oss-120b:free",131072,131072,"text",1,null,0,0,0,0],["openai/gpt-oss-20b",131072,131072,"text",1,null,0.03,0.13,0.03,0],["openai/gpt-oss-20b:free",131072,32768,"text",1,null,0,0,0,0],["openai/gpt-oss-safeguard-20b",131072,65536,"text",1,null,0.075,0.3,0.0375,0],["openai/o1",200000,100000,"text,image",1,null,15,60,7.5,0],["openai/o3",200000,100000,"text,image",1,null,2,8,0.5,0],["openai/o3-deep-research",200000,100000,"text,image",1,null,10,40,2.5,0],["openai/o3-mini",200000,100000,"text",1,null,1.1,4.4,0.55,0],["openai/o3-mini-high",200000,100000,"text",1,null,1.1,4.4,0.55,0],["openai/o3-pro",200000,100000,"text,image",1,null,20,80,0,0],["openai/o4-mini",200000,100000,"text,image",1,null,1.1,4.4,0.275,0],["openai/o4-mini-deep-research",200000,100000,"text,image",1,null,2,8,0.5,0],["openai/o4-mini-high",200000,100000,"text,image",1,null,1.1,4.4,0.275,0],["openrouter/aurora-alpha",128000,50000,"text",1,null,0,0,0,0],["openrouter/auto",2000000,8888,"text,image",1,null,-1000000,-1000000,0,0],["openrouter/auto-beta",2000000,8888,"text,image",1,null,-1000000,-1000000,0,0],["openrouter/elephant-alpha",262144,32768,"text",0,null,0,0,0,0],["openrouter/free",200000,8888,"text,image",1,null,0,0,0,0],["openrouter/healer-alpha",262144,32000,"text,image",1,null,0,0,0,0],["openrouter/hunter-alpha",1048576,32000,"text",1,null,0,0,0,0],["openrouter/owl-alpha",1048756,262144,"text",0,null,0,0,0,0],["poolside/laguna-m.1",262144,32768,"text",1,null,0.19999999999999998,0.39999999999999997,0.09999999999999999,0],["poolside/laguna-m.1:free",262144,32768,"text",1,null,0,0,0,0],["poolside/laguna-s-2.1",1048576,131072,"text",1,null,0.09999999999999999,0.19999999999999998,0.01,0],["poolside/laguna-s-2.1:free",262144,32768,"text",1,null,0,0,0,0],["poolside/laguna-xs-2.1",262144,32768,"text",1,null,0.06,0.12,0.03,0],["poolside/laguna-xs-2.1:free",262144,32768,"text",1,null,0,0,0,0],["poolside/laguna-xs.2",262144,32768,"text",1,null,0.09999999999999999,0.19999999999999998,0.049999999999999996,0],["poolside/laguna-xs.2:free",262144,32768,"text",1,null,0,0,0,0],["prime-intellect/intellect-3",131072,131072,"text",1,null,0.19999999999999998,1.1,0,0],["qwen/qwen-2.5-72b-instruct",32768,16384,"text",0,null,0.36,0.39999999999999997,0,0],["qwen/qwen-2.5-7b-instruct",32768,32768,"text",0,null,0.04,0.09999999999999999,0,0],["qwen/qwen-max",32768,8192,"text",0,null,1.04,4.16,0.20800000000000002,0],["qwen/qwen-plus",1000000,32768,"text",0,null,0.26,0.78,0.052000000000000005,0.325],["qwen/qwen-plus-2025-07-28",1000000,32768,"text",0,null,0.26,0.78,0,0.325],["qwen/qwen-plus-2025-07-28:thinking",1000000,32768,"text",1,null,0.26,0.78,0,0.325],["qwen/qwen-turbo",131072,8192,"text",0,null,0.0325,0.13,0.006500000000000001,0],["qwen/qwen-vl-max",131072,32768,"text,image",0,null,0.52,2.08,0,0],["qwen/qwen3-14b",131072,8192,"text",1,null,0.22749999999999998,0.9099999999999999,0,0],["qwen/qwen3-235b-a22b",131072,8192,"text",1,null,0.45499999999999996,1.8199999999999998,0,0],["qwen/qwen3-235b-a22b-2507",262144,16384,"text",1,null,0.09,0.55,0,0],["qwen/qwen3-235b-a22b-thinking-2507",262144,32768,"text",1,null,0.3,3,0.09999999999999999,0],["qwen/qwen3-30b-a3b",131072,8192,"text",1,null,0.13,0.52,0,0],["qwen/qwen3-30b-a3b-instruct-2507",262144,32000,"text",0,null,0.04815,0.19305,0,0],["qwen/qwen3-30b-a3b-thinking-2507",81920,32768,"text",1,null,0.13,1.56,0.08,0],["qwen/qwen3-32b",131072,16384,"text",1,null,0.08,0.28,0.04,0],["qwen/qwen3-4b",131072,8192,"text",1,null,0.0715,0.273,0,0],["qwen/qwen3-4b:free",40960,8888,"text",1,null,0,0,0,0],["qwen/qwen3-8b",131072,8192,"text",1,null,0.117,0.45499999999999996,0.049999999999999996,0],["qwen/qwen3-coder",262144,65536,"text",0,null,0.3,1,0.09999999999999999,0],["qwen/qwen3-coder-30b-a3b-instruct",262144,32768,"text",0,null,0.07,0.27,0,0],["qwen/qwen3-coder-flash",1000000,65536,"text",0,null,0.195,0.975,0.039,0.24375],["qwen/qwen3-coder-next",262144,262144,"text",0,null,0.11,0.7999999999999999,0.07,0],["qwen/qwen3-coder-plus",1000000,65536,"text",0,null,0.65,3.25,0.13,0.8125],["qwen/qwen3-coder:exacto",262144,65536,"text",0,null,0.22,1.7999999999999998,0.022,0],["qwen/qwen3-coder:free",1048576,262000,"text",0,null,0,0,0,0],["qwen/qwen3-max",262144,32768,"text",1,null,0.78,3.9,0.156,0.975],["qwen/qwen3-max-thinking",262144,32768,"text",1,null,0.78,3.9,0,0],["qwen/qwen3-next-80b-a3b-instruct",262144,262144,"text",0,null,0.09999999999999999,1.1,0.07,0],["qwen/qwen3-next-80b-a3b-instruct:free",262144,8888,"text",0,null,0,0,0,0],["qwen/qwen3-next-80b-a3b-thinking",262144,32768,"text",1,null,0.0975,0.78,0,0],["qwen/qwen3-vl-235b-a22b-instruct",262144,32768,"text,image",0,null,0.21,1.9,0.09999999999999999,0],["qwen/qwen3-vl-235b-a22b-thinking",131072,32768,"text,image",1,null,0.26,2.6,0,0],["qwen/qwen3-vl-30b-a3b-instruct",262144,16384,"text,image",0,null,0.15,0.6,0,0],["qwen/qwen3-vl-30b-a3b-thinking",262144,32768,"text,image",1,null,0.13,1.56,0,0],["qwen/qwen3-vl-32b-instruct",131072,32768,"text,image",0,null,0.10400000000000001,0.41600000000000004,0,0],["qwen/qwen3-vl-8b-instruct",262144,32768,"text,image",0,null,0.117,0.45499999999999996,0,0],["qwen/qwen3-vl-8b-thinking",131072,32768,"text,image",1,null,0.117,1.365,0,0],["qwen/qwen3.5-122b-a10b",262144,65536,"text,image",1,null,0.26,2.08,0,0],["qwen/qwen3.5-27b",262144,65536,"text,image",1,null,0.195,1.56,0,0],["qwen/qwen3.5-35b-a3b",262144,262144,"text,image",1,null,0.14,1,0.049999999999999996,0],["qwen/qwen3.5-397b-a17b",262144,65536,"text,image",1,null,0.39,2.34,0.111,0],["qwen/qwen3.5-9b",262144,262144,"text,image",1,null,0.09999999999999999,0.15,0,0],["qwen/qwen3.5-flash-02-23",1000000,65536,"text,image",1,null,0.065,0.26,0,0.08125],["qwen/qwen3.5-plus-02-15",1000000,65536,"text,image",1,null,0.26,1.56,0,0.325],["qwen/qwen3.5-plus-20260420",1000000,65536,"text,image",1,null,0.3,1.7999999999999998,0,0.375],["qwen/qwen3.6-27b",262144,131072,"text,image",1,null,0.28900000000000003,2.4,0.15,0],["qwen/qwen3.6-35b-a3b",262144,262144,"text,image",1,null,0.14,1,0.049999999999999996,0],["qwen/qwen3.6-flash",1000000,65536,"text,image",1,null,0.1875,1.125,0,0.234375],["qwen/qwen3.6-max-preview",262144,65536,"text",1,null,1.04,6.24,0,1.3],["qwen/qwen3.6-plus",1000000,65536,"text",1,null,0.325,1.95,0,0.40625],["qwen/qwen3.6-plus-preview:free",1000000,32000,"text",1,null,0,0,0,0],["qwen/qwen3.6-plus:free",1000000,65536,"text,image",1,null,0,0,0,0],["qwen/qwen3.7-max",1000000,65536,"text",1,null,1.475,4.425,0.295,1.84375],["qwen/qwen3.7-plus",1000000,65536,"text,image",1,null,0.32,1.28,0.064,0.39999999999999997],["qwen/qwq-32b",131072,131072,"text",1,null,0.15,0.58,0,0],["reka/reka-edge",16384,16384,"text,image",0,null,0.09999999999999999,0.09999999999999999,0,0],["rekaai/reka-edge",16384,16384,"text,image",0,null,0.09999999999999999,0.09999999999999999,0,0],["relace/relace-search",256000,128000,"text",0,null,1,3,0,0],["sakana/fugu-ultra",1000000,128000,"text,image",1,null,5,30,0.5,0],["sao10k/l3-euryale-70b",8192,8192,"text",0,null,1.48,1.48,0,0],["sao10k/l3.1-euryale-70b",131072,16384,"text",0,null,0.85,0.85,0,0],["stepfun/step-3.5-flash",262144,65536,"text",0,null,0.09999999999999999,0.3,0.02,0],["stepfun/step-3.5-flash:free",256000,256000,"text",1,null,0,0,0,0],["stepfun/step-3.7-flash",262144,256000,"text,image",1,null,0.19999999999999998,1.15,0.04,0],["tencent/hy3",262144,128000,"text",1,null,0.13199999999999998,0.5279999999999999,0.032999999999999995,0],["tencent/hy3-preview",262144,64000,"text",1,null,0.063,0.21,0.020999999999999998,0],["tencent/hy3-preview:free",262144,262144,"text",1,null,0,0,0,0],["tencent/hy3:free",262144,262144,"text",1,null,0,0,0,0],["thedrummer/rocinante-12b",32768,32768,"text",0,null,0.16999999999999998,0.43,0,0],["thedrummer/unslopnemo-12b",32768,32768,"text",0,null,0.39999999999999997,0.39999999999999997,0,0],["thinkingmachines/inkling",1048576,8888,"text,image",1,null,1,4.05,0.16999999999999998,0],["tngtech/deepseek-r1t2-chimera",163840,163840,"text",1,null,0.3,1.1,0.15,0],["tngtech/tng-r1t-chimera",163840,65536,"text",1,null,0.25,0.85,0.125,0],["upstage/solar-pro-3",128000,8888,"text",1,null,0.15,0.6,0.015,0],["upstage/solar-pro-3:free",128000,8888,"text",1,null,0,0,0,0],["x-ai/grok-3",131072,8888,"text",0,null,3,15,0.75,0],["x-ai/grok-3-beta",131072,8888,"text",0,null,3,15,0.75,0],["x-ai/grok-3-mini",131072,8888,"text",1,null,0.3,0.5,0.075,0],["x-ai/grok-3-mini-beta",131072,8888,"text",1,null,0.3,0.5,0.075,0],["x-ai/grok-4",256000,64000,"text,image",1,null,3,15,0.75,0],["x-ai/grok-4-fast",2000000,30000,"text,image",1,null,0.19999999999999998,0.5,0.049999999999999996,0],["x-ai/grok-4.1-fast",2000000,30000,"text,image",1,null,0.19999999999999998,0.5,0.049999999999999996,0],["x-ai/grok-4.20",2000000,8888,"text,image",1,null,1.25,2.5,0.19999999999999998,0],["x-ai/grok-4.20-beta",2000000,8888,"text,image",1,null,2,6,0.19999999999999998,0],["x-ai/grok-4.3",1000000,1000000,"text,image",1,null,1.25,2.5,0.19999999999999998,0],["x-ai/grok-4.5",500000,500000,"text,image",1,null,2,6,0.3,0],["x-ai/grok-4.6",500000,500000,"text,image",1,null,2,6,0.3,0],["x-ai/grok-build-0.1",256000,256000,"text,image",1,null,1,2,0.19999999999999998,0],["x-ai/grok-code-fast-1",256000,10000,"text",1,null,0.19999999999999998,1.5,0.02,0],["xiaomi/mimo-v2-flash",262144,65536,"text",1,null,0.09999999999999999,0.3,0.01,0],["xiaomi/mimo-v2-omni",262144,65536,"text,image",1,null,0.39999999999999997,2,0.08,0],["xiaomi/mimo-v2-pro",1048576,131072,"text",1,null,1,3,0.19999999999999998,0],["xiaomi/mimo-v2.5",1050000,131072,"text,image",1,null,0.14,0.28,0.0028,0],["xiaomi/mimo-v2.5-pro",1050000,131072,"text",1,null,0.435,0.87,0.0036,0],["z-ai/glm-4-32b",128000,8888,"text",0,null,0.09999999999999999,0.09999999999999999,0,0],["z-ai/glm-4.5",131072,98304,"text",1,null,0.6,2.2,0.11,0],["z-ai/glm-4.5-air",131072,98304,"text",1,null,0.13,0.85,0.024999999999999998,0],["z-ai/glm-4.5-air:free",131072,96000,"text",1,null,0,0,0,0],["z-ai/glm-4.5v",65536,16384,"text,image",1,null,0.6,1.7999999999999998,0.11,0],["z-ai/glm-4.6",204800,131072,"text",1,null,0.5,2,0.09999999999999999,0],["z-ai/glm-4.6:exacto",204800,131072,"text",1,null,0.44,1.76,0.11,0],["z-ai/glm-4.6v",131072,32768,"text,image",1,null,0.3,0.8999999999999999,0.055,0],["z-ai/glm-4.7",204800,131072,"text",1,null,0.39999999999999997,1.75,0.08,0],["z-ai/glm-4.7-flash",202752,16384,"text",1,null,0.06,0.39999999999999997,0.01,0],["z-ai/glm-5",204800,131072,"text",1,null,0.95,2.5500000000000003,0.19999999999999998,0],["z-ai/glm-5-turbo",202752,131072,"text",1,null,1.2,4,0.24,0],["z-ai/glm-5.1",204800,128000,"text",1,null,0.966,3.036,0.1794,0],["z-ai/glm-5.2",1048576,131072,"text",1,null,0.707,2.222,0.1313,0],["z-ai/glm-5.3",1048576,131072,"text",1,null,0.707,2.222,0.1313,0],["z-ai/glm-5v-turbo",202752,131072,"text,image",1,null,1.2,4,0.24,0]],
|
|
53
54
|
"xai": [["grok-2",131072,8192,"text",0,null,2,10,2,0],["grok-2-1212",131072,8192,"text",0,null,2,10,2,0],["grok-2-latest",131072,8192,"text",0,null,2,10,2,0],["grok-2-vision",8192,4096,"text,image",0,null,2,10,2,0],["grok-2-vision-1212",8192,4096,"text,image",0,null,2,10,2,0],["grok-2-vision-latest",8192,4096,"text,image",0,null,2,10,2,0],["grok-3",131072,8192,"text",0,null,3,15,0.75,0],["grok-3-fast",131072,8192,"text",0,null,5,25,1.25,0],["grok-3-fast-latest",131072,8192,"text",0,null,5,25,1.25,0],["grok-3-latest",131072,8192,"text",0,null,3,15,0.75,0],["grok-3-mini",131072,8192,"text",1,null,0.3,0.5,0.075,0],["grok-3-mini-fast",131072,8192,"text",1,null,0.6,4,0.15,0],["grok-3-mini-fast-latest",131072,8192,"text",1,null,0.6,4,0.15,0],["grok-3-mini-latest",131072,8192,"text",1,null,0.3,0.5,0.075,0],["grok-4",256000,64000,"text",1,null,3,15,0.75,0],["grok-4-1-fast",2000000,30000,"text,image",1,null,0.2,0.5,0.05,0],["grok-4-1-fast-non-reasoning",2000000,30000,"text,image",0,null,0.2,0.5,0.05,0],["grok-4-fast",2000000,30000,"text,image",1,null,0.2,0.5,0.05,0],["grok-4-fast-non-reasoning",2000000,30000,"text,image",0,null,0.2,0.5,0.05,0],["grok-4.20-0309-non-reasoning",1000000,30000,"text,image",0,null,1.25,2.5,0.2,0],["grok-4.20-0309-reasoning",1000000,30000,"text,image",1,null,1.25,2.5,0.2,0],["grok-4.20-beta-latest-non-reasoning",2000000,30000,"text,image",0,null,2,6,0.2,0],["grok-4.20-beta-latest-reasoning",2000000,30000,"text,image",1,null,2,6,0.2,0],["grok-4.20-multi-agent-0309",1000000,30000,"text,image",1,null,1.25,2.5,0.2,0],["grok-4.20-multi-agent-beta-latest",2000000,30000,"text,image",1,null,2,6,0.2,0],["grok-4.3",1000000,30000,"text,image",1,null,1.25,2.5,0.2,0],["grok-4.5",500000,500000,"text,image",1,null,2,6,0.3,0],["grok-4.6",500000,500000,"text,image",1,null,2,6,0.3,0],["grok-beta",131072,4096,"text",0,null,5,15,5,0],["grok-build-0.1",256000,256000,"text,image",1,null,1,2,0.2,0],["grok-code-fast-1",256000,10000,"text",1,null,0.2,1.5,0.02,0],["grok-composer-2.5-fast",200000,64000,"text",1,null,0,0,0,0],["grok-vision-beta",8192,4096,"text,image",0,null,5,15,5,0]],
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One logical request, one execution budget (#4546).
|
|
3
|
+
*
|
|
4
|
+
* The amplification behind #4546 was never a single missing limit. Every layer that can
|
|
5
|
+
* re-send a request -- transport retry, adapter retry, auth recovery, account failover, combo
|
|
6
|
+
* failover, repair -- counted its own allowance, so a per-layer 3 composed into a per-request
|
|
7
|
+
* 12. #4605 and #4608 gave the transient layers one shared counter; this module is the policy
|
|
8
|
+
* that counter answers to.
|
|
9
|
+
*
|
|
10
|
+
* The policy is an INTERSECTION of constraints, not four independent counters. A request that
|
|
11
|
+
* still has total allowance left is not thereby entitled to a second account move, and a
|
|
12
|
+
* request that changed credentials does not get its target-transition allowance back. The
|
|
13
|
+
* default profile keeps the recovery shape that actually works today -- three same-account
|
|
14
|
+
* sends plus one alternate -- by funding the alternate from a reserve that a validated
|
|
15
|
+
* sanitized repair can spend instead, but never both.
|
|
16
|
+
*/
|
|
17
|
+
import type { TransientSendBudget } from "./upstream-retry";
|
|
18
|
+
|
|
19
|
+
export type SendClass =
|
|
20
|
+
| "initial"
|
|
21
|
+
| "transient"
|
|
22
|
+
| "auth-recovery"
|
|
23
|
+
| "repair"
|
|
24
|
+
| "account-failover"
|
|
25
|
+
| "combo-failover"
|
|
26
|
+
| "prewarm";
|
|
27
|
+
|
|
28
|
+
export interface RequestExecutionBudgetPolicy {
|
|
29
|
+
/** Every model send of one logical request, including the reserve. */
|
|
30
|
+
readonly maxTotalModelSends: number;
|
|
31
|
+
/** Shared by the initial send, same-target transient retries, and refresh/repair legs. */
|
|
32
|
+
readonly baseSendAllowance: number;
|
|
33
|
+
/** ONE final recovery, shared by an account move and a validated rebuild. Not one each. */
|
|
34
|
+
readonly finalRecoveryAllowance: number;
|
|
35
|
+
readonly maxAlternateTargetSends: number;
|
|
36
|
+
readonly maxTargetTransitions: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Text Codex guarded profile. Three same-account sends plus one alternate is the recovery
|
|
41
|
+
* shape that live traffic depends on, so a flat ceiling of 3 would break a working path.
|
|
42
|
+
*/
|
|
43
|
+
export const CODEX_TEXT_GUARDED_BUDGET_POLICY: RequestExecutionBudgetPolicy = {
|
|
44
|
+
maxTotalModelSends: 4,
|
|
45
|
+
baseSendAllowance: 3,
|
|
46
|
+
finalRecoveryAllowance: 1,
|
|
47
|
+
maxAlternateTargetSends: 1,
|
|
48
|
+
maxTargetTransitions: 1,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const REQUEST_BUDGET_POLICY_VERSION = "guarded-v1";
|
|
52
|
+
|
|
53
|
+
export type BudgetDenial =
|
|
54
|
+
| "total-exhausted"
|
|
55
|
+
| "base-allowance-exhausted"
|
|
56
|
+
| "final-recovery-spent"
|
|
57
|
+
| "alternate-target-exhausted"
|
|
58
|
+
| "target-transition-exhausted"
|
|
59
|
+
| "not-replay-safe";
|
|
60
|
+
|
|
61
|
+
export interface DispatchIntent {
|
|
62
|
+
readonly sendClass: SendClass;
|
|
63
|
+
/**
|
|
64
|
+
* (provider route, endpoint, model lane, upstream credential identity). A quota domain is a
|
|
65
|
+
* different thing and must not be folded in here.
|
|
66
|
+
*/
|
|
67
|
+
readonly targetKey: string;
|
|
68
|
+
/**
|
|
69
|
+
* False refuses the dispatch outright. A request whose execution state upstream is unknown
|
|
70
|
+
* is not replayable just because budget remains (RFC 9110 9.2.2).
|
|
71
|
+
*/
|
|
72
|
+
readonly replaySafe?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* True when the physical send is already reported through another counter -- the retry
|
|
75
|
+
* helpers' `onSendsConsumed` hook. The permit then books the reserve, alternate-target and
|
|
76
|
+
* transition ledgers but leaves `used` to that reporter, because charging both is how a
|
|
77
|
+
* four-send cap silently becomes a two-send cap.
|
|
78
|
+
*/
|
|
79
|
+
readonly countedExternally?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SingleUseDispatchPermit {
|
|
83
|
+
readonly sendClass: SendClass;
|
|
84
|
+
/** Consume exactly once. A second call returns false and charges nothing. */
|
|
85
|
+
use(): boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type DispatchDecision =
|
|
89
|
+
| { allowed: true; permit: SingleUseDispatchPermit }
|
|
90
|
+
| { allowed: false; reason: BudgetDenial };
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Carried on HandleResponsesOptions so a combo child, a rebuild and an alternate-account leg
|
|
94
|
+
* all decrement the same holder. `used` is the existing #4605 counter and still counts every
|
|
95
|
+
* model send; the reserve is what the fourth send draws on once the base allowance is gone.
|
|
96
|
+
*/
|
|
97
|
+
export interface RequestExecutionBudget extends TransientSendBudget {
|
|
98
|
+
readonly logicalRequestId: string;
|
|
99
|
+
readonly policyVersion: string;
|
|
100
|
+
readonly policy: RequestExecutionBudgetPolicy;
|
|
101
|
+
reserveDispatch(intent: DispatchIntent): DispatchDecision;
|
|
102
|
+
/**
|
|
103
|
+
* Sends still available from the base allowance, capped by a layer's own maximum.
|
|
104
|
+
* Returns 0 when the allowance is gone -- it never floors to 1, because a floor of 1 is
|
|
105
|
+
* what let every recovery leg send one more time forever.
|
|
106
|
+
*/
|
|
107
|
+
remainingBaseSends(cap: number): number;
|
|
108
|
+
readonly reserveSpent: boolean;
|
|
109
|
+
readonly alternateTargetSends: number;
|
|
110
|
+
readonly targetTransitions: number;
|
|
111
|
+
readonly lastTargetKey: string | undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const RESERVE_FUNDED_CLASSES: ReadonlySet<SendClass> = new Set<SendClass>([
|
|
115
|
+
"account-failover",
|
|
116
|
+
"combo-failover",
|
|
117
|
+
"repair",
|
|
118
|
+
"auth-recovery",
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
let logicalRequestSeq = 0;
|
|
122
|
+
|
|
123
|
+
export function createRequestExecutionBudget(
|
|
124
|
+
policy: RequestExecutionBudgetPolicy = CODEX_TEXT_GUARDED_BUDGET_POLICY,
|
|
125
|
+
logicalRequestId?: string,
|
|
126
|
+
): RequestExecutionBudget {
|
|
127
|
+
let reserveSpent = false;
|
|
128
|
+
let alternateTargetSends = 0;
|
|
129
|
+
let targetTransitions = 0;
|
|
130
|
+
let lastTargetKey: string | undefined;
|
|
131
|
+
|
|
132
|
+
const budget: RequestExecutionBudget = {
|
|
133
|
+
used: 0,
|
|
134
|
+
logicalRequestId: logicalRequestId ?? `lr-${Date.now().toString(36)}-${(logicalRequestSeq += 1).toString(36)}`,
|
|
135
|
+
policyVersion: REQUEST_BUDGET_POLICY_VERSION,
|
|
136
|
+
policy,
|
|
137
|
+
get reserveSpent() { return reserveSpent; },
|
|
138
|
+
get alternateTargetSends() { return alternateTargetSends; },
|
|
139
|
+
get targetTransitions() { return targetTransitions; },
|
|
140
|
+
get lastTargetKey() { return lastTargetKey; },
|
|
141
|
+
remainingBaseSends(cap: number): number {
|
|
142
|
+
const capped = Number.isFinite(cap) ? Math.trunc(cap) : 0;
|
|
143
|
+
return Math.max(0, Math.min(capped, policy.baseSendAllowance - budget.used));
|
|
144
|
+
},
|
|
145
|
+
reserveDispatch(intent: DispatchIntent): DispatchDecision {
|
|
146
|
+
if (intent.replaySafe === false) return { allowed: false, reason: "not-replay-safe" };
|
|
147
|
+
if (budget.used >= policy.maxTotalModelSends) return { allowed: false, reason: "total-exhausted" };
|
|
148
|
+
|
|
149
|
+
const changesTarget = lastTargetKey !== undefined && lastTargetKey !== intent.targetKey;
|
|
150
|
+
const isAlternateTarget = changesTarget || intent.sendClass === "account-failover"
|
|
151
|
+
|| intent.sendClass === "combo-failover";
|
|
152
|
+
if (isAlternateTarget && changesTarget && targetTransitions >= policy.maxTargetTransitions) {
|
|
153
|
+
return { allowed: false, reason: "target-transition-exhausted" };
|
|
154
|
+
}
|
|
155
|
+
if (isAlternateTarget && alternateTargetSends >= policy.maxAlternateTargetSends) {
|
|
156
|
+
return { allowed: false, reason: "alternate-target-exhausted" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The base allowance is spent first. Only once it is gone does a recovery class reach
|
|
160
|
+
// for the single shared reserve -- an account move and a validated rebuild cannot each
|
|
161
|
+
// take one.
|
|
162
|
+
const drawsReserve = budget.remainingBaseSends(policy.baseSendAllowance) === 0;
|
|
163
|
+
if (drawsReserve) {
|
|
164
|
+
if (!RESERVE_FUNDED_CLASSES.has(intent.sendClass)) {
|
|
165
|
+
return { allowed: false, reason: "base-allowance-exhausted" };
|
|
166
|
+
}
|
|
167
|
+
if (reserveSpent || policy.finalRecoveryAllowance <= 0) {
|
|
168
|
+
return { allowed: false, reason: "final-recovery-spent" };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let consumed = false;
|
|
173
|
+
return {
|
|
174
|
+
allowed: true,
|
|
175
|
+
permit: {
|
|
176
|
+
sendClass: intent.sendClass,
|
|
177
|
+
use(): boolean {
|
|
178
|
+
if (consumed) return false;
|
|
179
|
+
consumed = true;
|
|
180
|
+
// Charged here, immediately before the physical send, rather than reported after
|
|
181
|
+
// the helper returns: a counter that is only reconciled afterwards cannot stop two
|
|
182
|
+
// concurrent legs that both read the same remainder.
|
|
183
|
+
if (intent.countedExternally !== true) budget.used += 1;
|
|
184
|
+
if (drawsReserve) reserveSpent = true;
|
|
185
|
+
if (isAlternateTarget) alternateTargetSends += 1;
|
|
186
|
+
if (changesTarget) targetTransitions += 1;
|
|
187
|
+
lastTargetKey = intent.targetKey;
|
|
188
|
+
return true;
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
if (lastTargetKey === undefined) lastTargetKey = undefined;
|
|
195
|
+
return budget;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function isRequestExecutionBudget(
|
|
199
|
+
value: TransientSendBudget | undefined,
|
|
200
|
+
): value is RequestExecutionBudget {
|
|
201
|
+
return typeof (value as RequestExecutionBudget | undefined)?.reserveDispatch === "function";
|
|
202
|
+
}
|
|
@@ -55,7 +55,63 @@ const RESET_RETRY_BASE_DELAY_MS = 150;
|
|
|
55
55
|
const RESET_RETRY_MAX_DELAY_MS = 1_000;
|
|
56
56
|
|
|
57
57
|
// Transient-5xx status retry layer (pre-stream only; devlog/_plan/260716_claudecode_hardening/010).
|
|
58
|
-
|
|
58
|
+
/** Total sends one transient-retry helper call may make: 1 initial + 2 retries. */
|
|
59
|
+
export const TRANSIENT_RETRY_MAX_ATTEMPTS = 3;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Transient sends already spent by one LOGICAL request.
|
|
63
|
+
*
|
|
64
|
+
* A mutable holder rather than a counter local to one call frame, because the thing that has to
|
|
65
|
+
* share it spans frames: a combo parent runs a separate child turn per target, and a per-child
|
|
66
|
+
* counter is what let one logical request reach upstream three times per target (#4546).
|
|
67
|
+
*/
|
|
68
|
+
export interface TransientSendBudget {
|
|
69
|
+
used: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createTransientSendBudget(): TransientSendBudget {
|
|
73
|
+
return { used: 0 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Refusal raised when a logical request has no send left (#4546, REQ-B04/B05).
|
|
78
|
+
*
|
|
79
|
+
* It is deliberately a distinct type rather than a generic `Error`: every call site that
|
|
80
|
+
* catches a helper rejection today launders it into HTTP 502 `upstream_error`, which would
|
|
81
|
+
* report a proxy-side budget decision as an upstream fault and hide the real 401/429 the
|
|
82
|
+
* request already had. Callers must recognise this and return the structured local error
|
|
83
|
+
* instead. It is a backstop, not the policy -- a call site that still holds a reusable
|
|
84
|
+
* upstream response is supposed to check the remainder BEFORE it cancels that body.
|
|
85
|
+
*/
|
|
86
|
+
export class SendBudgetExhaustedError extends Error {
|
|
87
|
+
readonly code = "request_send_budget_exhausted";
|
|
88
|
+
constructor(label?: string) {
|
|
89
|
+
super(label
|
|
90
|
+
? `request send budget exhausted before dispatch (${label})`
|
|
91
|
+
: "request send budget exhausted before dispatch");
|
|
92
|
+
this.name = "SendBudgetExhaustedError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Configuration refusal for an attempts value that is not a send count.
|
|
98
|
+
*
|
|
99
|
+
* `undefined` means "use the policy default" and `0` means "refuse". A negative, fractional,
|
|
100
|
+
* NaN or infinite value is a programming or configuration error, and silently substituting the
|
|
101
|
+
* default for it is how a broken budget turns back into three free sends.
|
|
102
|
+
*/
|
|
103
|
+
export class InvalidSendBudgetError extends Error {
|
|
104
|
+
constructor(value: unknown) {
|
|
105
|
+
super(`invalid upstream send budget: ${String(value)}`);
|
|
106
|
+
this.name = "InvalidSendBudgetError";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeSendAttempts(value: number | undefined, fallback: number): number {
|
|
111
|
+
if (value === undefined) return fallback;
|
|
112
|
+
if (!Number.isInteger(value) || value < 0) throw new InvalidSendBudgetError(value);
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
59
115
|
const TRANSIENT_RETRY_BASE_DELAY_MS = 400;
|
|
60
116
|
const TRANSIENT_RETRY_MAX_DELAY_MS = 5_000;
|
|
61
117
|
// A failed attempt slower than this is the "slow 502" incident shape (191s observed on
|
|
@@ -77,8 +133,19 @@ export interface RetryBackoffOptions {
|
|
|
77
133
|
baseDelayMs: number;
|
|
78
134
|
maxDelayMs: number;
|
|
79
135
|
headers?: Headers;
|
|
136
|
+
/**
|
|
137
|
+
* Treat a provider's `Retry-After` as the earliest legal send rather than something the
|
|
138
|
+
* local maximum may shorten. Opt-in per caller so the change lands on the transient path
|
|
139
|
+
* first instead of silently lengthening every adapter's backoff.
|
|
140
|
+
*/
|
|
141
|
+
retryAfterIsLowerBound?: boolean;
|
|
142
|
+
/** Hard ceiling for an honoured `Retry-After`, so an hour-long wait cannot park a request. */
|
|
143
|
+
retryAfterCeilingMs?: number;
|
|
80
144
|
}
|
|
81
145
|
|
|
146
|
+
/** One minute, matching the same-target 429 ceiling the key-failover path already uses. */
|
|
147
|
+
export const RETRY_AFTER_CEILING_MS = 60_000;
|
|
148
|
+
|
|
82
149
|
export function abortError(signal?: AbortSignal): unknown {
|
|
83
150
|
return signal?.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
84
151
|
}
|
|
@@ -228,9 +295,20 @@ function retryAfterDelayMs(headers: Headers): number | undefined {
|
|
|
228
295
|
|
|
229
296
|
export function retryBackoffDelayMs(attempt: number, opts: RetryBackoffOptions): number {
|
|
230
297
|
const retryAfter = opts.headers ? retryAfterDelayMs(opts.headers) : undefined;
|
|
231
|
-
if (retryAfter !== undefined) return Math.min(retryAfter, opts.maxDelayMs);
|
|
232
298
|
const exp = Math.min(opts.baseDelayMs * (2 ** attempt), opts.maxDelayMs);
|
|
233
|
-
|
|
299
|
+
const jittered = Math.floor(exp * (0.8 + Math.random() * 0.4));
|
|
300
|
+
if (retryAfter === undefined) return jittered;
|
|
301
|
+
if (opts.retryAfterIsLowerBound !== true) {
|
|
302
|
+
// Historical behaviour, still the default for every caller that has not opted in.
|
|
303
|
+
return Math.min(retryAfter, opts.maxDelayMs);
|
|
304
|
+
}
|
|
305
|
+
// A provider that names a wait is stating when it will serve again; sending earlier is a
|
|
306
|
+
// request we already know will be refused, and refusing it twice is the retry storm the
|
|
307
|
+
// header exists to prevent. The local maximum bounds our OWN exponential backoff and has no
|
|
308
|
+
// business shortening someone else's instruction. The ceiling is separate: it stops an
|
|
309
|
+
// hour-long Retry-After from parking a request forever.
|
|
310
|
+
const ceiling = opts.retryAfterCeilingMs ?? RETRY_AFTER_CEILING_MS;
|
|
311
|
+
return Math.min(Math.max(retryAfter, jittered), ceiling);
|
|
234
312
|
}
|
|
235
313
|
|
|
236
314
|
export function cancelResponseBodyBestEffort(res: Response): void {
|
|
@@ -355,7 +433,11 @@ export async function fetchWithResetRetry(
|
|
|
355
433
|
opts: ResetRetryOptions = {},
|
|
356
434
|
firstRecovery?: UpstreamSendRecovery,
|
|
357
435
|
): Promise<Response> {
|
|
358
|
-
const attempts =
|
|
436
|
+
const attempts = normalizeSendAttempts(opts.attempts, RESET_RETRY_MAX_ATTEMPTS);
|
|
437
|
+
// Zero is zero. The old Math.max(1, ...) floor meant an exhausted budget still bought one
|
|
438
|
+
// more send on every recovery leg, which is most of what made a bounded per-layer retry
|
|
439
|
+
// compose into an unbounded per-request count.
|
|
440
|
+
if (attempts === 0) throw new SendBudgetExhaustedError(opts.label);
|
|
359
441
|
let lastError: unknown;
|
|
360
442
|
let sawReset = false;
|
|
361
443
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
@@ -401,7 +483,7 @@ export async function fetchWithTransientRetry(
|
|
|
401
483
|
doFetch: ReplayableFetch,
|
|
402
484
|
opts: TransientRetryOptions = {},
|
|
403
485
|
): Promise<Response> {
|
|
404
|
-
const budget =
|
|
486
|
+
const budget = normalizeSendAttempts(opts.attempts, TRANSIENT_RETRY_MAX_ATTEMPTS);
|
|
405
487
|
const slowAttemptMs = opts.slowAttemptMs ?? TRANSIENT_RETRY_SLOW_ATTEMPT_MS;
|
|
406
488
|
const transientStatuses: number[] = [];
|
|
407
489
|
// `attempts` is ONE total-send budget shared with the inner reset layer, not a per-layer
|
|
@@ -418,13 +500,15 @@ export async function fetchWithTransientRetry(
|
|
|
418
500
|
sent += 1;
|
|
419
501
|
return doFetch(recovery);
|
|
420
502
|
};
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
|
|
503
|
+
// No floor. A spent budget hands the inner helper 0, which refuses rather than buying one
|
|
504
|
+
// more send -- the loop condition alone was never enough, because every later recovery leg
|
|
505
|
+
// called this helper again and the floor funded each of them.
|
|
506
|
+
const remaining = () => Math.max(0, budget - sent);
|
|
424
507
|
// Reported in `finally` rather than at each exit: this function returns from five places
|
|
425
508
|
// and throws from one, and a caller sharing the budget across request legs must be told the
|
|
426
509
|
// real count on every one of them.
|
|
427
510
|
try {
|
|
511
|
+
if (budget === 0) throw new SendBudgetExhaustedError(opts.label);
|
|
428
512
|
let attemptStart = Date.now();
|
|
429
513
|
let res = await fetchWithResetRetry(countedFetch, { ...opts, attempts: remaining() });
|
|
430
514
|
for (let attempt = 0; sent < budget; attempt++) {
|
|
@@ -442,6 +526,7 @@ export async function fetchWithTransientRetry(
|
|
|
442
526
|
baseDelayMs: TRANSIENT_RETRY_BASE_DELAY_MS,
|
|
443
527
|
maxDelayMs: TRANSIENT_RETRY_MAX_DELAY_MS,
|
|
444
528
|
headers: res.headers,
|
|
529
|
+
retryAfterIsLowerBound: true,
|
|
445
530
|
});
|
|
446
531
|
cancelResponseBodyBestEffort(res);
|
|
447
532
|
// Throws on abort (see sleepWithAbort): the rejection propagates, and the body we just
|
|
@@ -454,6 +539,8 @@ export async function fetchWithTransientRetry(
|
|
|
454
539
|
} catch (err) {
|
|
455
540
|
// Keep the prior 5xx evidence attached: the origin already responded, so
|
|
456
541
|
// this rejection is not pre-connection and must not classify as neutral.
|
|
542
|
+
// A budget refusal is not upstream evidence of anything and must stay recognisable.
|
|
543
|
+
if (err instanceof SendBudgetExhaustedError) throw err;
|
|
457
544
|
throw new UpstreamRetryEvidenceError(transientStatuses, err);
|
|
458
545
|
}
|
|
459
546
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-workflow admission: a finite budget above the logical request (#4546).
|
|
3
|
+
*
|
|
4
|
+
* The per-request send budget bounds how many times ONE request reaches upstream. It cannot
|
|
5
|
+
* bound how many requests a fan-out makes. A worker that spawns seven hundred children, each
|
|
6
|
+
* of which sends exactly once, never violates a per-request cap and still spends the account.
|
|
7
|
+
* That is the second half of the #4546 incident and it needs a ceiling of its own.
|
|
8
|
+
*
|
|
9
|
+
* The unit is the root workflow -- the user-visible task -- identified by the parent thread
|
|
10
|
+
* header when the client supplies one. A retry is not a new user task and gets no new
|
|
11
|
+
* allowance; a genuinely new top-level request does.
|
|
12
|
+
*
|
|
13
|
+
* This ledger is process-local and in-memory. It bounds a single proxy process honestly and
|
|
14
|
+
* says nothing about a second process sharing the same account pool; that needs a shared
|
|
15
|
+
* durable store and is declared out of scope rather than implied.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface WorkflowBudgetPolicy {
|
|
19
|
+
/** Children admitted concurrently under one root. */
|
|
20
|
+
readonly maxConcurrentChildren: number;
|
|
21
|
+
/** Physical model sends charged to one root across its whole life. */
|
|
22
|
+
readonly maxPhysicalSends: number;
|
|
23
|
+
/** Distinct children one root may ever create. */
|
|
24
|
+
readonly maxDistinctChildren: number;
|
|
25
|
+
/**
|
|
26
|
+
* Concurrency slots a fan-out may never take. An interactive turn arriving into a saturated
|
|
27
|
+
* root still gets admitted; without this a worker burst starves the conversation it serves.
|
|
28
|
+
*/
|
|
29
|
+
readonly interactiveReserve: number;
|
|
30
|
+
/** Roots tracked at once. Bounded so a caller minting new ids cannot grow this forever. */
|
|
31
|
+
readonly maxTrackedRoots: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_WORKFLOW_BUDGET_POLICY: WorkflowBudgetPolicy = {
|
|
35
|
+
maxConcurrentChildren: 8,
|
|
36
|
+
maxPhysicalSends: 256,
|
|
37
|
+
maxDistinctChildren: 64,
|
|
38
|
+
interactiveReserve: 1,
|
|
39
|
+
maxTrackedRoots: 512,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type WorkflowDenial =
|
|
43
|
+
| "workflow-concurrency-exhausted"
|
|
44
|
+
| "workflow-sends-exhausted"
|
|
45
|
+
| "workflow-children-exhausted";
|
|
46
|
+
|
|
47
|
+
export type WorkflowLane = "interactive" | "worker";
|
|
48
|
+
|
|
49
|
+
export interface WorkflowAdmission {
|
|
50
|
+
readonly rootId: string;
|
|
51
|
+
release(): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type WorkflowDecision =
|
|
55
|
+
| { admitted: true; lease: WorkflowAdmission }
|
|
56
|
+
| { admitted: false; reason: WorkflowDenial; rootId: string };
|
|
57
|
+
|
|
58
|
+
interface WorkflowState {
|
|
59
|
+
active: number;
|
|
60
|
+
sends: number;
|
|
61
|
+
children: Set<string>;
|
|
62
|
+
lastSeenMs: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const roots = new Map<string, WorkflowState>();
|
|
66
|
+
|
|
67
|
+
function pruneOldestRoot(): void {
|
|
68
|
+
let oldestKey: string | undefined;
|
|
69
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
70
|
+
for (const [key, state] of roots) {
|
|
71
|
+
// An active root is never evicted: dropping it would hand its fan-out a fresh allowance,
|
|
72
|
+
// which is the exact laundering this ledger exists to prevent.
|
|
73
|
+
if (state.active > 0) continue;
|
|
74
|
+
if (state.lastSeenMs < oldestAt) { oldestAt = state.lastSeenMs; oldestKey = key; }
|
|
75
|
+
}
|
|
76
|
+
if (oldestKey !== undefined) roots.delete(oldestKey);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Admit one turn under a root workflow.
|
|
81
|
+
*
|
|
82
|
+
* `childId` distinguishes the members of a fan-out; omit it for the root's own turns.
|
|
83
|
+
* An interactive lane may use the reserved slots a worker lane may not.
|
|
84
|
+
*/
|
|
85
|
+
export function admitWorkflowTurn(
|
|
86
|
+
rootId: string | undefined,
|
|
87
|
+
lane: WorkflowLane,
|
|
88
|
+
policy: WorkflowBudgetPolicy = DEFAULT_WORKFLOW_BUDGET_POLICY,
|
|
89
|
+
childId?: string,
|
|
90
|
+
now: number = Date.now(),
|
|
91
|
+
): WorkflowDecision | undefined {
|
|
92
|
+
if (!rootId) return undefined;
|
|
93
|
+
let state = roots.get(rootId);
|
|
94
|
+
if (!state) {
|
|
95
|
+
if (roots.size >= policy.maxTrackedRoots) pruneOldestRoot();
|
|
96
|
+
state = { active: 0, sends: 0, children: new Set(), lastSeenMs: now };
|
|
97
|
+
roots.set(rootId, state);
|
|
98
|
+
}
|
|
99
|
+
state.lastSeenMs = now;
|
|
100
|
+
|
|
101
|
+
if (state.sends >= policy.maxPhysicalSends) {
|
|
102
|
+
return { admitted: false, reason: "workflow-sends-exhausted", rootId };
|
|
103
|
+
}
|
|
104
|
+
if (childId !== undefined && !state.children.has(childId)
|
|
105
|
+
&& state.children.size >= policy.maxDistinctChildren) {
|
|
106
|
+
return { admitted: false, reason: "workflow-children-exhausted", rootId };
|
|
107
|
+
}
|
|
108
|
+
const ceiling = lane === "worker"
|
|
109
|
+
? Math.max(0, policy.maxConcurrentChildren - policy.interactiveReserve)
|
|
110
|
+
: policy.maxConcurrentChildren;
|
|
111
|
+
if (state.active >= ceiling) {
|
|
112
|
+
return { admitted: false, reason: "workflow-concurrency-exhausted", rootId };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
state.active += 1;
|
|
116
|
+
if (childId !== undefined) state.children.add(childId);
|
|
117
|
+
let released = false;
|
|
118
|
+
return {
|
|
119
|
+
admitted: true,
|
|
120
|
+
lease: {
|
|
121
|
+
rootId,
|
|
122
|
+
release(): void {
|
|
123
|
+
if (released) return;
|
|
124
|
+
released = true;
|
|
125
|
+
const current = roots.get(rootId);
|
|
126
|
+
if (!current) return;
|
|
127
|
+
current.active = Math.max(0, current.active - 1);
|
|
128
|
+
current.lastSeenMs = Date.now();
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Charge physical sends to a root. Called from the send budget's own accounting so a retry
|
|
136
|
+
* inside one request counts toward the workflow total, not only the request total.
|
|
137
|
+
*/
|
|
138
|
+
export function chargeWorkflowSends(rootId: string | undefined, sends: number): void {
|
|
139
|
+
if (!rootId || sends <= 0) return;
|
|
140
|
+
const state = roots.get(rootId);
|
|
141
|
+
if (!state) return;
|
|
142
|
+
state.sends += sends;
|
|
143
|
+
state.lastSeenMs = Date.now();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether this root has already spent its whole physical-send ceiling.
|
|
148
|
+
*
|
|
149
|
+
* Separate from `admitWorkflowTurn` so a caller can refuse before dispatch without taking a
|
|
150
|
+
* concurrency slot it would have to remember to release.
|
|
151
|
+
*/
|
|
152
|
+
export function workflowSendCeilingReached(
|
|
153
|
+
rootId: string | undefined,
|
|
154
|
+
policy: WorkflowBudgetPolicy = DEFAULT_WORKFLOW_BUDGET_POLICY,
|
|
155
|
+
): boolean {
|
|
156
|
+
if (!rootId) return false;
|
|
157
|
+
const state = roots.get(rootId);
|
|
158
|
+
return state !== undefined && state.sends >= policy.maxPhysicalSends;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function workflowBudgetSnapshot(rootId: string): {
|
|
162
|
+
|
|
163
|
+
active: number; sends: number; children: number;
|
|
164
|
+
} | undefined {
|
|
165
|
+
const state = roots.get(rootId);
|
|
166
|
+
return state ? { active: state.active, sends: state.sends, children: state.children.size } : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Test seam. Production never clears a live ledger: that would reset a spent budget. */
|
|
170
|
+
export function resetWorkflowBudgetsForTest(): void {
|
|
171
|
+
roots.clear();
|
|
172
|
+
}
|