@linxiraos/pi-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5066 -0
- package/README.md +1195 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
- package/dist/types/auth-broker/client.d.ts +93 -0
- package/dist/types/auth-broker/discover.d.ts +39 -0
- package/dist/types/auth-broker/index.d.ts +7 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +136 -0
- package/dist/types/auth-broker/server.d.ts +48 -0
- package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
- package/dist/types/auth-broker/types.d.ts +152 -0
- package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
- package/dist/types/auth-gateway/http.d.ts +56 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +36 -0
- package/dist/types/auth-gateway/types.d.ts +127 -0
- package/dist/types/auth-retry.d.ts +150 -0
- package/dist/types/auth-storage.d.ts +1258 -0
- package/dist/types/dialect/anthropic.d.ts +15 -0
- package/dist/types/dialect/catalog.d.ts +3 -0
- package/dist/types/dialect/coercion.d.ts +23 -0
- package/dist/types/dialect/deepseek.d.ts +14 -0
- package/dist/types/dialect/demotion.d.ts +30 -0
- package/dist/types/dialect/examples.d.ts +18 -0
- package/dist/types/dialect/factory.d.ts +3 -0
- package/dist/types/dialect/fenced-thinking.d.ts +53 -0
- package/dist/types/dialect/gemini.d.ts +17 -0
- package/dist/types/dialect/gemma.d.ts +15 -0
- package/dist/types/dialect/glm.d.ts +9 -0
- package/dist/types/dialect/harmony.d.ts +8 -0
- package/dist/types/dialect/hermes.d.ts +9 -0
- package/dist/types/dialect/history.d.ts +3 -0
- package/dist/types/dialect/index.d.ts +11 -0
- package/dist/types/dialect/inventory.d.ts +9 -0
- package/dist/types/dialect/kimi.d.ts +14 -0
- package/dist/types/dialect/minimax.d.ts +3 -0
- package/dist/types/dialect/owned-stream.d.ts +4 -0
- package/dist/types/dialect/qwen3.d.ts +9 -0
- package/dist/types/dialect/rendering.d.ts +54 -0
- package/dist/types/dialect/thinking.d.ts +6 -0
- package/dist/types/dialect/types.d.ts +68 -0
- package/dist/types/dialect/xml.d.ts +9 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +20 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +27 -0
- package/dist/types/error/classes.d.ts +106 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +94 -0
- package/dist/types/error/format.d.ts +20 -0
- package/dist/types/error/gateway.d.ts +20 -0
- package/dist/types/error/index.d.ts +14 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/error/rate-limit.d.ts +80 -0
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/providers/amazon-bedrock.d.ts +39 -0
- package/dist/types/providers/anthropic-client.d.ts +106 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic-wire.d.ts +345 -0
- package/dist/types/providers/anthropic.d.ts +261 -0
- package/dist/types/providers/aws-credentials.d.ts +48 -0
- package/dist/types/providers/aws-eventstream.d.ts +39 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +16 -0
- package/dist/types/providers/bedrock-mantle.d.ts +13 -0
- package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
- package/dist/types/providers/cowork-fetch.d.ts +3 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor-pi-args.d.ts +105 -0
- package/dist/types/providers/cursor.d.ts +221 -0
- package/dist/types/providers/devin.d.ts +12 -0
- package/dist/types/providers/error-message.d.ts +25 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +120 -0
- package/dist/types/providers/google-shared.d.ts +203 -0
- package/dist/types/providers/google-types.d.ts +155 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +179 -0
- package/dist/types/providers/ollama.d.ts +8 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-chat-wire.d.ts +669 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
- package/dist/types/providers/openai-codex-responses.d.ts +238 -0
- package/dist/types/providers/openai-completions.d.ts +48 -0
- package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
- package/dist/types/providers/openai-responses.d.ts +131 -0
- package/dist/types/providers/openai-shared.d.ts +624 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +69 -0
- package/dist/types/providers/register-builtins.d.ts +37 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +32 -0
- package/dist/types/providers/vision-guard.d.ts +20 -0
- package/dist/types/registry/aiand.d.ts +7 -0
- package/dist/types/registry/aimlapi.d.ts +4 -0
- package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
- package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
- package/dist/types/registry/amazon-bedrock.d.ts +5 -0
- package/dist/types/registry/anthropic.d.ts +10 -0
- package/dist/types/registry/api-key-login.d.ts +42 -0
- package/dist/types/registry/api-key-validation.d.ts +43 -0
- package/dist/types/registry/aws.d.ts +13 -0
- package/dist/types/registry/azure.d.ts +4 -0
- package/dist/types/registry/baseten.d.ts +7 -0
- package/dist/types/registry/bedrock-mantle.d.ts +22 -0
- package/dist/types/registry/cerebras.d.ts +7 -0
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
- package/dist/types/registry/coreweave.d.ts +7 -0
- package/dist/types/registry/cursor.d.ts +7 -0
- package/dist/types/registry/deepseek.d.ts +8 -0
- package/dist/types/registry/derived.d.ts +5 -0
- package/dist/types/registry/devin.d.ts +8 -0
- package/dist/types/registry/exa.d.ts +8 -0
- package/dist/types/registry/firepass.d.ts +16 -0
- package/dist/types/registry/fireworks.d.ts +7 -0
- package/dist/types/registry/github-copilot.d.ts +7 -0
- package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
- package/dist/types/registry/gitlab-duo.d.ts +9 -0
- package/dist/types/registry/gmi-cloud.d.ts +7 -0
- package/dist/types/registry/google-antigravity.d.ts +9 -0
- package/dist/types/registry/google-gemini-cli.d.ts +9 -0
- package/dist/types/registry/google-vertex.d.ts +5 -0
- package/dist/types/registry/google.d.ts +4 -0
- package/dist/types/registry/groq.d.ts +4 -0
- package/dist/types/registry/huggingface.d.ts +7 -0
- package/dist/types/registry/index.d.ts +4 -0
- package/dist/types/registry/kagi.d.ts +14 -0
- package/dist/types/registry/kilo.d.ts +7 -0
- package/dist/types/registry/kimi-code.d.ts +7 -0
- package/dist/types/registry/litellm.d.ts +13 -0
- package/dist/types/registry/llama-cpp.d.ts +8 -0
- package/dist/types/registry/lm-studio.d.ts +8 -0
- package/dist/types/registry/meta.d.ts +7 -0
- package/dist/types/registry/minimax-code-cn.d.ts +6 -0
- package/dist/types/registry/minimax-code.d.ts +6 -0
- package/dist/types/registry/minimax.d.ts +4 -0
- package/dist/types/registry/mistral.d.ts +4 -0
- package/dist/types/registry/moonshot.d.ts +7 -0
- package/dist/types/registry/nanogpt.d.ts +7 -0
- package/dist/types/registry/novita.d.ts +6 -0
- package/dist/types/registry/nvidia.d.ts +7 -0
- package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
- package/dist/types/registry/oauth/anthropic.d.ts +24 -0
- package/dist/types/registry/oauth/callback-server.d.ts +74 -0
- package/dist/types/registry/oauth/cursor.d.ts +16 -0
- package/dist/types/registry/oauth/device-code.d.ts +25 -0
- package/dist/types/registry/oauth/devin.d.ts +5 -0
- package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
- package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
- package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
- package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
- package/dist/types/registry/oauth/index.d.ts +45 -0
- package/dist/types/registry/oauth/kimi.d.ts +21 -0
- package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
- package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
- package/dist/types/registry/oauth/opencode.d.ts +18 -0
- package/dist/types/registry/oauth/perplexity.d.ts +9 -0
- package/dist/types/registry/oauth/pkce.d.ts +8 -0
- package/dist/types/registry/oauth/types.d.ts +87 -0
- package/dist/types/registry/oauth/wafer.d.ts +1 -0
- package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
- package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
- package/dist/types/registry/oauth/zai.d.ts +25 -0
- package/dist/types/registry/ollama-cloud.d.ts +7 -0
- package/dist/types/registry/ollama.d.ts +12 -0
- package/dist/types/registry/openai-codex-device.d.ts +8 -0
- package/dist/types/registry/openai-codex.d.ts +9 -0
- package/dist/types/registry/openai.d.ts +4 -0
- package/dist/types/registry/opencode-go.d.ts +6 -0
- package/dist/types/registry/opencode-zen.d.ts +6 -0
- package/dist/types/registry/openrouter.d.ts +13 -0
- package/dist/types/registry/parallel.d.ts +14 -0
- package/dist/types/registry/perplexity.d.ts +7 -0
- package/dist/types/registry/qianfan.d.ts +7 -0
- package/dist/types/registry/qwen-portal.d.ts +7 -0
- package/dist/types/registry/registry.d.ts +367 -0
- package/dist/types/registry/sakana.d.ts +7 -0
- package/dist/types/registry/siliconflow-cn.d.ts +7 -0
- package/dist/types/registry/siliconflow.d.ts +7 -0
- package/dist/types/registry/synthetic.d.ts +6 -0
- package/dist/types/registry/tavily.d.ts +14 -0
- package/dist/types/registry/together.d.ts +6 -0
- package/dist/types/registry/types.d.ts +75 -0
- package/dist/types/registry/umans.d.ts +7 -0
- package/dist/types/registry/venice.d.ts +13 -0
- package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
- package/dist/types/registry/vllm.d.ts +7 -0
- package/dist/types/registry/wafer-serverless.d.ts +6 -0
- package/dist/types/registry/xai-oauth.d.ts +7 -0
- package/dist/types/registry/xai.d.ts +7 -0
- package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
- package/dist/types/registry/xiaomi.d.ts +6 -0
- package/dist/types/registry/zai.d.ts +15 -0
- package/dist/types/registry/zenmux.d.ts +7 -0
- package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
- package/dist/types/stream.d.ts +46 -0
- package/dist/types/types.d.ts +1064 -0
- package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
- package/dist/types/usage/claude.d.ts +4 -0
- package/dist/types/usage/cursor.d.ts +4 -0
- package/dist/types/usage/gemini.d.ts +2 -0
- package/dist/types/usage/github-copilot.d.ts +7 -0
- package/dist/types/usage/google-antigravity.d.ts +15 -0
- package/dist/types/usage/kimi.d.ts +2 -0
- package/dist/types/usage/minimax-code.d.ts +3 -0
- package/dist/types/usage/ollama.d.ts +5 -0
- package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
- package/dist/types/usage/openai-codex-reset.d.ts +88 -0
- package/dist/types/usage/openai-codex.d.ts +10 -0
- package/dist/types/usage/opencode-go.d.ts +2 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/synthetic.d.ts +2 -0
- package/dist/types/usage/umans.d.ts +2 -0
- package/dist/types/usage/xai-oauth.d.ts +12 -0
- package/dist/types/usage/zai.d.ts +3 -0
- package/dist/types/usage.d.ts +527 -0
- package/dist/types/utils/abort.d.ts +25 -0
- package/dist/types/utils/anthropic-auth.d.ts +35 -0
- package/dist/types/utils/aws-profile.d.ts +17 -0
- package/dist/types/utils/block-symbols.d.ts +62 -0
- package/dist/types/utils/deterministic-id.d.ts +16 -0
- package/dist/types/utils/empty-completion-retry.d.ts +21 -0
- package/dist/types/utils/event-stream.d.ts +39 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/google-validation.d.ts +2 -0
- package/dist/types/utils/harmony-leak.d.ts +135 -0
- package/dist/types/utils/http-inspector.d.ts +49 -0
- package/dist/types/utils/idle-iterator.d.ts +149 -0
- package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
- package/dist/types/utils/openai-http.d.ts +48 -0
- package/dist/types/utils/openrouter-headers.d.ts +1 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/proxy.d.ts +39 -0
- package/dist/types/utils/request-debug.d.ts +29 -0
- package/dist/types/utils/retry-after.d.ts +4 -0
- package/dist/types/utils/retry.d.ts +14 -0
- package/dist/types/utils/schema/adapt.d.ts +24 -0
- package/dist/types/utils/schema/compatibility.d.ts +30 -0
- package/dist/types/utils/schema/dereference.d.ts +11 -0
- package/dist/types/utils/schema/draft.d.ts +10 -0
- package/dist/types/utils/schema/equality.d.ts +4 -0
- package/dist/types/utils/schema/fields.d.ts +54 -0
- package/dist/types/utils/schema/index.d.ts +14 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +153 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +17 -0
- package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/typescript.d.ts +24 -0
- package/dist/types/utils/schema/wire.d.ts +52 -0
- package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
- package/dist/types/utils/sse-debug.d.ts +5 -0
- package/dist/types/utils/stream-markup-healing.d.ts +87 -0
- package/dist/types/utils/thinking-loop.d.ts +102 -0
- package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
- package/dist/types/utils/tool-choice.d.ts +52 -0
- package/dist/types/utils/validation.d.ts +28 -0
- package/dist/types/utils.d.ts +57 -0
- package/package.json +138 -0
- package/src/api-registry.ts +109 -0
- package/src/auth/sqlite-credential-store.ts +2066 -0
- package/src/auth-broker/client.ts +471 -0
- package/src/auth-broker/discover.ts +310 -0
- package/src/auth-broker/index.ts +7 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +1332 -0
- package/src/auth-broker/server.ts +898 -0
- package/src/auth-broker/snapshot-cache.ts +200 -0
- package/src/auth-broker/types.ts +193 -0
- package/src/auth-broker/wire-schema-resource.ts +487 -0
- package/src/auth-broker/wire-schemas.ts +43 -0
- package/src/auth-gateway/http.ts +227 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +836 -0
- package/src/auth-gateway/types.ts +153 -0
- package/src/auth-retry.ts +401 -0
- package/src/auth-storage.ts +6540 -0
- package/src/dialect/anthropic.md +31 -0
- package/src/dialect/anthropic.ts +608 -0
- package/src/dialect/catalog.ts +29 -0
- package/src/dialect/coercion.ts +136 -0
- package/src/dialect/deepseek.md +24 -0
- package/src/dialect/deepseek.ts +609 -0
- package/src/dialect/demotion.ts +40 -0
- package/src/dialect/examples.ts +71 -0
- package/src/dialect/factory.ts +34 -0
- package/src/dialect/fenced-thinking.ts +184 -0
- package/src/dialect/gemini.md +44 -0
- package/src/dialect/gemini.ts +583 -0
- package/src/dialect/gemma.md +33 -0
- package/src/dialect/gemma.ts +387 -0
- package/src/dialect/glm.md +32 -0
- package/src/dialect/glm.ts +579 -0
- package/src/dialect/harmony.md +31 -0
- package/src/dialect/harmony.ts +345 -0
- package/src/dialect/hermes.md +25 -0
- package/src/dialect/hermes.ts +206 -0
- package/src/dialect/history.ts +81 -0
- package/src/dialect/index.ts +15 -0
- package/src/dialect/inventory.ts +30 -0
- package/src/dialect/kimi.md +24 -0
- package/src/dialect/kimi.ts +340 -0
- package/src/dialect/minimax.md +31 -0
- package/src/dialect/minimax.ts +95 -0
- package/src/dialect/owned-stream.ts +481 -0
- package/src/dialect/prompt-template.md +12 -0
- package/src/dialect/qwen3.md +28 -0
- package/src/dialect/qwen3.ts +240 -0
- package/src/dialect/rendering.ts +304 -0
- package/src/dialect/thinking.ts +292 -0
- package/src/dialect/types.ts +56 -0
- package/src/dialect/xml.md +22 -0
- package/src/dialect/xml.ts +90 -0
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +47 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +35 -0
- package/src/error/classes.ts +281 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +602 -0
- package/src/error/format.ts +45 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +14 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +63 -0
- package/src/error/rate-limit.ts +303 -0
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +51 -0
- package/src/provider-details.ts +90 -0
- package/src/providers/amazon-bedrock.ts +1064 -0
- package/src/providers/anthropic-client.ts +317 -0
- package/src/providers/anthropic-messages-server-schema.ts +252 -0
- package/src/providers/anthropic-messages-server.ts +818 -0
- package/src/providers/anthropic-wire.ts +359 -0
- package/src/providers/anthropic.ts +4539 -0
- package/src/providers/aws-credentials.ts +772 -0
- package/src/providers/aws-eventstream.ts +181 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +438 -0
- package/src/providers/bedrock-mantle.ts +110 -0
- package/src/providers/claude-code-fingerprint.ts +20 -0
- package/src/providers/cowork-fetch.ts +201 -0
- package/src/providers/cursor/exec-modern.ts +496 -0
- package/src/providers/cursor/proto/agent.proto +4533 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor-pi-args.ts +165 -0
- package/src/providers/cursor.ts +4689 -0
- package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
- package/src/providers/devin/proto/buf.gen.yaml +33 -0
- package/src/providers/devin/proto/buf.yaml +17 -0
- package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
- package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
- package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
- package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
- package/src/providers/devin/proto/cel/expr/value.proto +41 -0
- package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
- package/src/providers/devin/proto/errorspb/errors.proto +56 -0
- package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
- package/src/providers/devin/proto/errorspb/markers.proto +10 -0
- package/src/providers/devin/proto/errorspb/tags.proto +12 -0
- package/src/providers/devin/proto/errorspb/testing.proto +6 -0
- package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
- package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
- package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
- package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
- package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
- package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
- package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
- package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
- package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
- package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
- package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
- package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
- package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
- package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
- package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
- package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
- package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
- package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
- package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
- package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
- package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
- package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
- package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
- package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
- package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
- package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
- package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
- package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
- package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
- package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
- package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
- package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
- package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
- package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
- package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
- package/src/providers/devin.ts +679 -0
- package/src/providers/error-message.ts +23 -0
- package/src/providers/github-copilot-headers.ts +141 -0
- package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
- package/src/providers/gitlab-duo-workflow.ts +3135 -0
- package/src/providers/gitlab-duo.ts +399 -0
- package/src/providers/google-auth.ts +330 -0
- package/src/providers/google-gemini-cli.ts +1370 -0
- package/src/providers/google-shared.ts +1122 -0
- package/src/providers/google-types.ts +180 -0
- package/src/providers/google-vertex.ts +135 -0
- package/src/providers/google.ts +47 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +51 -0
- package/src/providers/mock.ts +514 -0
- package/src/providers/ollama.ts +776 -0
- package/src/providers/openai-anthropic-shim.ts +166 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +752 -0
- package/src/providers/openai-chat-wire.ts +859 -0
- package/src/providers/openai-codex/request-transformer.ts +491 -0
- package/src/providers/openai-codex/response-handler.ts +102 -0
- package/src/providers/openai-codex-responses.ts +4716 -0
- package/src/providers/openai-completions.ts +2389 -0
- package/src/providers/openai-reasoning-fallback.ts +269 -0
- package/src/providers/openai-responses-server-schema.ts +397 -0
- package/src/providers/openai-responses-server.ts +1466 -0
- package/src/providers/openai-responses-wire.ts +6416 -0
- package/src/providers/openai-responses.ts +1393 -0
- package/src/providers/openai-shared.ts +3500 -0
- package/src/providers/pi-native-client.ts +275 -0
- package/src/providers/pi-native-server.ts +245 -0
- package/src/providers/register-builtins.ts +503 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +1083 -0
- package/src/providers/vision-guard.ts +54 -0
- package/src/registry/aiand.ts +22 -0
- package/src/registry/aimlapi.ts +6 -0
- package/src/registry/alibaba-coding-plan.ts +104 -0
- package/src/registry/alibaba-token-plan.ts +125 -0
- package/src/registry/amazon-bedrock.ts +22 -0
- package/src/registry/anthropic.ts +26 -0
- package/src/registry/api-key-login.ts +115 -0
- package/src/registry/api-key-validation.ts +145 -0
- package/src/registry/aws.ts +57 -0
- package/src/registry/azure.ts +6 -0
- package/src/registry/baseten.ts +22 -0
- package/src/registry/bedrock-mantle.ts +34 -0
- package/src/registry/cerebras.ts +23 -0
- package/src/registry/cloudflare-ai-gateway.ts +45 -0
- package/src/registry/coreweave.ts +40 -0
- package/src/registry/cursor.ts +20 -0
- package/src/registry/deepseek.ts +46 -0
- package/src/registry/derived.ts +9 -0
- package/src/registry/devin.ts +15 -0
- package/src/registry/exa.ts +19 -0
- package/src/registry/firepass.ts +32 -0
- package/src/registry/fireworks.ts +28 -0
- package/src/registry/github-copilot.ts +22 -0
- package/src/registry/gitlab-duo-workflow.ts +20 -0
- package/src/registry/gitlab-duo.ts +19 -0
- package/src/registry/gmi-cloud.ts +22 -0
- package/src/registry/google-antigravity.ts +22 -0
- package/src/registry/google-gemini-cli.ts +22 -0
- package/src/registry/google-vertex.ts +38 -0
- package/src/registry/google.ts +6 -0
- package/src/registry/groq.ts +6 -0
- package/src/registry/huggingface.ts +29 -0
- package/src/registry/index.ts +4 -0
- package/src/registry/kagi.ts +46 -0
- package/src/registry/kilo.ts +114 -0
- package/src/registry/kimi-code.ts +17 -0
- package/src/registry/litellm.ts +45 -0
- package/src/registry/llama-cpp.ts +35 -0
- package/src/registry/lm-studio.ts +31 -0
- package/src/registry/meta.ts +22 -0
- package/src/registry/minimax-code-cn.ts +12 -0
- package/src/registry/minimax-code.ts +12 -0
- package/src/registry/minimax.ts +6 -0
- package/src/registry/mistral.ts +6 -0
- package/src/registry/moonshot.ts +28 -0
- package/src/registry/nanogpt.ts +22 -0
- package/src/registry/novita.ts +25 -0
- package/src/registry/nvidia.ts +61 -0
- package/src/registry/oauth/anthropic-constants.ts +12 -0
- package/src/registry/oauth/anthropic.ts +346 -0
- package/src/registry/oauth/callback-server.ts +438 -0
- package/src/registry/oauth/cursor.ts +187 -0
- package/src/registry/oauth/device-code.ts +92 -0
- package/src/registry/oauth/devin.ts +124 -0
- package/src/registry/oauth/github-copilot.ts +369 -0
- package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
- package/src/registry/oauth/gitlab-duo.ts +222 -0
- package/src/registry/oauth/google-antigravity.ts +225 -0
- package/src/registry/oauth/google-gemini-cli.ts +297 -0
- package/src/registry/oauth/google-oauth-shared.ts +211 -0
- package/src/registry/oauth/index.ts +187 -0
- package/src/registry/oauth/kimi.ts +297 -0
- package/src/registry/oauth/minimax-code.ts +53 -0
- package/src/registry/oauth/oauth.html +317 -0
- package/src/registry/oauth/openai-codex.ts +384 -0
- package/src/registry/oauth/opencode.ts +50 -0
- package/src/registry/oauth/perplexity.ts +228 -0
- package/src/registry/oauth/pkce.ts +18 -0
- package/src/registry/oauth/types.ts +96 -0
- package/src/registry/oauth/wafer.ts +24 -0
- package/src/registry/oauth/xai-oauth.ts +559 -0
- package/src/registry/oauth/xiaomi.ts +211 -0
- package/src/registry/oauth/zai.ts +285 -0
- package/src/registry/ollama-cloud.ts +36 -0
- package/src/registry/ollama.ts +43 -0
- package/src/registry/openai-codex-device.ts +18 -0
- package/src/registry/openai-codex.ts +19 -0
- package/src/registry/openai.ts +6 -0
- package/src/registry/opencode-go.ts +12 -0
- package/src/registry/opencode-zen.ts +12 -0
- package/src/registry/openrouter.ts +28 -0
- package/src/registry/parallel.ts +45 -0
- package/src/registry/perplexity.ts +13 -0
- package/src/registry/qianfan.ts +27 -0
- package/src/registry/qwen-portal.ts +50 -0
- package/src/registry/registry.ts +182 -0
- package/src/registry/sakana.ts +22 -0
- package/src/registry/siliconflow-cn.ts +22 -0
- package/src/registry/siliconflow.ts +22 -0
- package/src/registry/synthetic.ts +21 -0
- package/src/registry/tavily.ts +45 -0
- package/src/registry/together.ts +22 -0
- package/src/registry/types.ts +86 -0
- package/src/registry/umans.ts +23 -0
- package/src/registry/venice.ts +33 -0
- package/src/registry/vercel-ai-gateway.ts +38 -0
- package/src/registry/vllm.ts +34 -0
- package/src/registry/wafer-serverless.ts +12 -0
- package/src/registry/xai-oauth.ts +17 -0
- package/src/registry/xai.ts +22 -0
- package/src/registry/xiaomi-token-plan-ams.ts +12 -0
- package/src/registry/xiaomi-token-plan-cn.ts +12 -0
- package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
- package/src/registry/xiaomi.ts +12 -0
- package/src/registry/zai.ts +41 -0
- package/src/registry/zenmux.ts +22 -0
- package/src/registry/zhipu-coding-plan.ts +27 -0
- package/src/stream.ts +1944 -0
- package/src/types.ts +1243 -0
- package/src/usage/alibaba-token-plan.ts +230 -0
- package/src/usage/claude.ts +830 -0
- package/src/usage/cursor.ts +335 -0
- package/src/usage/gemini.ts +258 -0
- package/src/usage/github-copilot.ts +424 -0
- package/src/usage/google-antigravity.ts +497 -0
- package/src/usage/kimi.ts +277 -0
- package/src/usage/minimax-code.ts +291 -0
- package/src/usage/ollama.ts +41 -0
- package/src/usage/openai-codex-base-url.ts +35 -0
- package/src/usage/openai-codex-reset.ts +205 -0
- package/src/usage/openai-codex.ts +627 -0
- package/src/usage/opencode-go.ts +89 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/synthetic.ts +180 -0
- package/src/usage/umans.ts +192 -0
- package/src/usage/xai-oauth.ts +414 -0
- package/src/usage/zai.ts +370 -0
- package/src/usage.ts +411 -0
- package/src/utils/abort.ts +67 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/aws-profile.ts +88 -0
- package/src/utils/block-symbols.ts +78 -0
- package/src/utils/deterministic-id.ts +20 -0
- package/src/utils/empty-completion-retry.ts +161 -0
- package/src/utils/event-stream.ts +202 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/google-validation.ts +25 -0
- package/src/utils/harmony-leak.ts +500 -0
- package/src/utils/http-inspector.ts +196 -0
- package/src/utils/idle-iterator.ts +531 -0
- package/src/utils/leaked-thinking-stream.ts +483 -0
- package/src/utils/openai-http.ts +119 -0
- package/src/utils/openrouter-headers.ts +12 -0
- package/src/utils/parse-bind.ts +56 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/proxy.ts +314 -0
- package/src/utils/request-debug.ts +351 -0
- package/src/utils/retry-after.ts +121 -0
- package/src/utils/retry.ts +77 -0
- package/src/utils/schema/CONSTRAINTS.md +168 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +210 -0
- package/src/utils/schema/index.ts +14 -0
- package/src/utils/schema/json-schema-validator.ts +595 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +2314 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +109 -0
- package/src/utils/schema/strict-tool-validation.ts +117 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/typescript.ts +212 -0
- package/src/utils/schema/wire.ts +662 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +18 -0
- package/src/utils/stream-markup-healing.ts +247 -0
- package/src/utils/thinking-loop.ts +552 -0
- package/src/utils/tool-call-loop-guard.ts +107 -0
- package/src/utils/tool-choice.ts +101 -0
- package/src/utils/validation.ts +1932 -0
- package/src/utils.ts +492 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import { scheduler } from "node:timers/promises";
|
|
2
|
+
import { bareModelId, parseAnthropicModel } from "@linxiraos/pi-catalog/identity";
|
|
3
|
+
import { toNumber } from "@linxiraos/pi-catalog/utils";
|
|
4
|
+
import * as AIError from "../error";
|
|
5
|
+
import { claudeCodeVersion } from "../providers/claude-code-fingerprint";
|
|
6
|
+
import {
|
|
7
|
+
type CredentialRankingContext,
|
|
8
|
+
type CredentialRankingStrategy,
|
|
9
|
+
resolveUsedFraction,
|
|
10
|
+
type UsageAmount,
|
|
11
|
+
type UsageFetchContext,
|
|
12
|
+
type UsageFetchParams,
|
|
13
|
+
type UsageLimit,
|
|
14
|
+
type UsageProvider,
|
|
15
|
+
type UsageReport,
|
|
16
|
+
type UsageStatus,
|
|
17
|
+
type UsageWindow,
|
|
18
|
+
} from "../usage";
|
|
19
|
+
import { isRecord } from "../utils";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_ENDPOINT = "https://api.anthropic.com/api/oauth";
|
|
22
|
+
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
|
|
23
|
+
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
|
+
const MAX_ATTEMPTS = 3;
|
|
25
|
+
const BASE_RETRY_DELAY_MS = 500;
|
|
26
|
+
|
|
27
|
+
const CLAUDE_HEADERS = {
|
|
28
|
+
accept: "application/json, text/plain, */*",
|
|
29
|
+
"accept-encoding": "gzip, compress, deflate, br",
|
|
30
|
+
"anthropic-beta":
|
|
31
|
+
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,redact-thinking-2026-02-12,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advanced-tool-use-2025-11-20,effort-2025-11-24,extended-cache-ttl-2025-04-11",
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
|
|
34
|
+
connection: "keep-alive",
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
function normalizeClaudeBaseUrl(baseUrl?: string): string {
|
|
38
|
+
if (!baseUrl?.trim()) return DEFAULT_ENDPOINT;
|
|
39
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
40
|
+
const lower = trimmed.toLowerCase();
|
|
41
|
+
if (lower.endsWith("/api/oauth")) return trimmed;
|
|
42
|
+
let url: URL;
|
|
43
|
+
try {
|
|
44
|
+
url = new URL(trimmed);
|
|
45
|
+
} catch {
|
|
46
|
+
return DEFAULT_ENDPOINT;
|
|
47
|
+
}
|
|
48
|
+
let path = url.pathname.replace(/\/+$/, "");
|
|
49
|
+
if (path === "/") path = "";
|
|
50
|
+
if (path.toLowerCase().endsWith("/v1")) {
|
|
51
|
+
path = path.slice(0, -3);
|
|
52
|
+
}
|
|
53
|
+
if (!path) return `${url.origin}/api/oauth`;
|
|
54
|
+
return `${url.origin}${path}/api/oauth`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ClaudeUsageBucket {
|
|
58
|
+
utilization?: number;
|
|
59
|
+
resets_at?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ParsedUsageBucket {
|
|
63
|
+
utilization?: number;
|
|
64
|
+
resetsAt?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ClaudeExtraUsage {
|
|
68
|
+
is_enabled?: boolean;
|
|
69
|
+
monthly_limit?: number | null;
|
|
70
|
+
used_credits?: number;
|
|
71
|
+
decimal_places?: number;
|
|
72
|
+
currency?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface ClaudeMoneyAmount {
|
|
76
|
+
amount_minor?: number;
|
|
77
|
+
currency?: string;
|
|
78
|
+
exponent?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ClaudeSpend {
|
|
82
|
+
used?: ClaudeMoneyAmount | null;
|
|
83
|
+
limit?: ClaudeMoneyAmount | null;
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ParsedClaudeExtraUsage {
|
|
88
|
+
used: number;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}
|
|
91
|
+
type ClaudeUnifiedWindow = "5h" | "7d" | "7d_oi";
|
|
92
|
+
type ClaudeModelKind = "opus" | "sonnet" | "fable" | "mythos";
|
|
93
|
+
|
|
94
|
+
interface ClaudeUsageResponse {
|
|
95
|
+
five_hour?: ClaudeUsageBucket | null;
|
|
96
|
+
seven_day?: ClaudeUsageBucket | null;
|
|
97
|
+
seven_day_opus?: ClaudeUsageBucket | null;
|
|
98
|
+
seven_day_sonnet?: ClaudeUsageBucket | null;
|
|
99
|
+
limits?: unknown;
|
|
100
|
+
extra_usage?: ClaudeExtraUsage | null;
|
|
101
|
+
spend?: ClaudeSpend | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface ClaudeApiLimitModelScope {
|
|
105
|
+
display_name?: string | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface ClaudeApiLimitScope {
|
|
109
|
+
model?: ClaudeApiLimitModelScope | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface ClaudeApiLimitEntry {
|
|
113
|
+
kind?: string;
|
|
114
|
+
percent?: unknown;
|
|
115
|
+
resets_at?: string | null;
|
|
116
|
+
scope?: ClaudeApiLimitScope | null;
|
|
117
|
+
is_active?: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface ParsedApiLimitEntry {
|
|
121
|
+
kind: string;
|
|
122
|
+
bucket: ParsedUsageBucket;
|
|
123
|
+
displayName?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseIsoTime(value: string | undefined): number | undefined {
|
|
127
|
+
if (!value) return undefined;
|
|
128
|
+
const parsed = Date.parse(value);
|
|
129
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseBucket(bucket: unknown): ParsedUsageBucket | undefined {
|
|
133
|
+
if (!isRecord(bucket)) return undefined;
|
|
134
|
+
const utilization = toNumber(bucket.utilization);
|
|
135
|
+
const resetsAt = parseIsoTime(typeof bucket.resets_at === "string" ? bucket.resets_at : undefined);
|
|
136
|
+
if (utilization === undefined && resetsAt === undefined) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
return { utilization, resetsAt };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getApiLimitDisplayName(scope: unknown): string | undefined {
|
|
143
|
+
if (!isRecord(scope)) return undefined;
|
|
144
|
+
const model = scope.model;
|
|
145
|
+
if (!isRecord(model)) return undefined;
|
|
146
|
+
const displayName = model.display_name;
|
|
147
|
+
return typeof displayName === "string" && displayName.trim() ? displayName.trim() : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Anthropic kept the legacy account-wide buckets populated, but as of
|
|
152
|
+
* 2026-07-02 the legacy per-model weekly buckets (`seven_day_opus` /
|
|
153
|
+
* `seven_day_sonnet`) are permanently null. Model-scoped weekly caps now arrive
|
|
154
|
+
* only through generic `limits[]` entries (`kind: "weekly_scoped"`) with the
|
|
155
|
+
* model family named by `scope.model.display_name`.
|
|
156
|
+
*
|
|
157
|
+
* `is_active` is deliberately ignored: live payloads mark only the currently
|
|
158
|
+
* binding limit active (an account pinned at a 100% Fable cap reports its 77%
|
|
159
|
+
* shared weekly row as `is_active: false`), so it signals severity ranking,
|
|
160
|
+
* not bucket existence. Filtering on it hid real utilization — a scoped row
|
|
161
|
+
* at 5% with a live reset rendered as `not reported` in `omp usage`.
|
|
162
|
+
*/
|
|
163
|
+
function parseApiLimitEntries(raw: unknown): ParsedApiLimitEntry[] {
|
|
164
|
+
if (!Array.isArray(raw)) return [];
|
|
165
|
+
const entries: ParsedApiLimitEntry[] = [];
|
|
166
|
+
for (const rawEntry of raw) {
|
|
167
|
+
if (!isRecord(rawEntry)) continue;
|
|
168
|
+
const entry = rawEntry as ClaudeApiLimitEntry;
|
|
169
|
+
if (typeof entry.kind !== "string") continue;
|
|
170
|
+
const utilization = toNumber(entry.percent);
|
|
171
|
+
const resetsAt = parseIsoTime(typeof entry.resets_at === "string" ? entry.resets_at : undefined);
|
|
172
|
+
if (utilization === undefined && resetsAt === undefined) continue;
|
|
173
|
+
const displayName = getApiLimitDisplayName(entry.scope);
|
|
174
|
+
entries.push({
|
|
175
|
+
kind: entry.kind,
|
|
176
|
+
bucket: { utilization, resetsAt },
|
|
177
|
+
...(displayName ? { displayName } : {}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return entries;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseUnifiedWindow(
|
|
184
|
+
headers: Record<string, string>,
|
|
185
|
+
window: ClaudeUnifiedWindow,
|
|
186
|
+
): ParsedUsageBucket | undefined {
|
|
187
|
+
const prefix = `anthropic-ratelimit-unified-${window}-`;
|
|
188
|
+
const utilizationFraction = toNumber(headers[`${prefix}utilization`]);
|
|
189
|
+
const resetSeconds = toNumber(headers[`${prefix}reset`]);
|
|
190
|
+
const utilization = utilizationFraction === undefined ? undefined : utilizationFraction * 100;
|
|
191
|
+
const resetsAt = resetSeconds !== undefined && resetSeconds > 0 ? resetSeconds * 1000 : undefined;
|
|
192
|
+
if (utilization === undefined && resetsAt === undefined) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return { utilization, resetsAt };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getPayloadString(payload: Record<string, unknown>, key: string): string | undefined {
|
|
199
|
+
const value = payload[key];
|
|
200
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getNestedPayloadString(payload: Record<string, unknown>, key: string, nestedKey: string): string | undefined {
|
|
204
|
+
const nested = payload[key];
|
|
205
|
+
return isRecord(nested) ? getPayloadString(nested, nestedKey) : undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function extractUsageIdentity(payload: ClaudeUsageResponse): { accountId?: string; email?: string } {
|
|
209
|
+
if (!isRecord(payload)) return {};
|
|
210
|
+
const accountId =
|
|
211
|
+
getPayloadString(payload, "account_id") ??
|
|
212
|
+
getPayloadString(payload, "accountId") ??
|
|
213
|
+
getPayloadString(payload, "user_id") ??
|
|
214
|
+
getPayloadString(payload, "userId") ??
|
|
215
|
+
getNestedPayloadString(payload, "account", "uuid") ??
|
|
216
|
+
getNestedPayloadString(payload, "account", "id") ??
|
|
217
|
+
getNestedPayloadString(payload, "user", "uuid") ??
|
|
218
|
+
getNestedPayloadString(payload, "user", "id");
|
|
219
|
+
const email =
|
|
220
|
+
getPayloadString(payload, "email") ??
|
|
221
|
+
getPayloadString(payload, "user_email") ??
|
|
222
|
+
getPayloadString(payload, "userEmail") ??
|
|
223
|
+
getNestedPayloadString(payload, "account", "email") ??
|
|
224
|
+
getNestedPayloadString(payload, "user", "email");
|
|
225
|
+
return { accountId, email };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function hasUsageData(payload: ClaudeUsageResponse): boolean {
|
|
229
|
+
return (
|
|
230
|
+
parseBucket(payload.five_hour)?.utilization !== undefined ||
|
|
231
|
+
parseBucket(payload.seven_day)?.utilization !== undefined ||
|
|
232
|
+
parseBucket(payload.seven_day_opus)?.utilization !== undefined ||
|
|
233
|
+
parseBucket(payload.seven_day_sonnet)?.utilization !== undefined ||
|
|
234
|
+
parseApiLimitEntries(payload.limits).some(entry => entry.bucket.utilization !== undefined) ||
|
|
235
|
+
buildClaudeExtraUsageLimit(payload) !== null
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isRetryableStatus(status: number): boolean {
|
|
240
|
+
// Exclude 429: the usage endpoint is informational and rate-limited per
|
|
241
|
+
// source IP, so retrying a rate_limit_error inside a single fetch can't
|
|
242
|
+
// succeed and only deepens the throttle (3 attempts per poll). Fall through
|
|
243
|
+
// to the caller's failure cool-down and retry on the next poll instead.
|
|
244
|
+
return AIError.isTransientStatus(status) && status !== 429;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isAbortError(error: unknown, signal?: AbortSignal): boolean {
|
|
248
|
+
if (signal?.aborted) return true;
|
|
249
|
+
if (!isRecord(error)) return false;
|
|
250
|
+
return error.name === "AbortError" || error.name === "TimeoutError";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function retryDelayMs(attempt: number, retryAfter: string | null): number {
|
|
254
|
+
const baseline = BASE_RETRY_DELAY_MS * 2 ** attempt;
|
|
255
|
+
if (!retryAfter?.trim()) return baseline;
|
|
256
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
257
|
+
if (Number.isFinite(seconds)) return Math.max(baseline, Math.max(0, seconds * 1000));
|
|
258
|
+
const dateDelay = Date.parse(retryAfter) - Date.now();
|
|
259
|
+
return Number.isFinite(dateDelay) ? Math.max(baseline, Math.max(0, dateDelay)) : baseline;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function waitBeforeRetry(
|
|
263
|
+
attempt: number,
|
|
264
|
+
retryAfter: string | null,
|
|
265
|
+
signal?: AbortSignal,
|
|
266
|
+
retryWait?: UsageFetchContext["retryWait"],
|
|
267
|
+
): Promise<boolean> {
|
|
268
|
+
if (signal?.aborted) return false;
|
|
269
|
+
if (attempt >= MAX_ATTEMPTS - 1) return false;
|
|
270
|
+
try {
|
|
271
|
+
const delayMs = retryDelayMs(attempt, retryAfter);
|
|
272
|
+
if (retryWait) {
|
|
273
|
+
await retryWait(delayMs, signal);
|
|
274
|
+
} else {
|
|
275
|
+
await scheduler.wait(delayMs, { signal });
|
|
276
|
+
}
|
|
277
|
+
return !signal?.aborted;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (isAbortError(error, signal)) return false;
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function fetchUsagePayload(
|
|
285
|
+
url: string,
|
|
286
|
+
headers: Record<string, string>,
|
|
287
|
+
ctx: UsageFetchContext,
|
|
288
|
+
signal?: AbortSignal,
|
|
289
|
+
): Promise<ClaudeUsageResponse | null> {
|
|
290
|
+
if (signal?.aborted) return null;
|
|
291
|
+
|
|
292
|
+
let lastPayload: ClaudeUsageResponse | null = null;
|
|
293
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
294
|
+
try {
|
|
295
|
+
const response = await ctx.fetch(url, { headers, signal });
|
|
296
|
+
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
const retryable = isRetryableStatus(response.status);
|
|
299
|
+
ctx.logger?.warn("Claude usage fetch failed", {
|
|
300
|
+
status: response.status,
|
|
301
|
+
statusText: response.statusText,
|
|
302
|
+
attempt,
|
|
303
|
+
willRetry: retryable && attempt < MAX_ATTEMPTS - 1,
|
|
304
|
+
});
|
|
305
|
+
if (!retryable) return null;
|
|
306
|
+
const retryAfter = response.headers.get("retry-after");
|
|
307
|
+
if (!(await waitBeforeRetry(attempt, retryAfter, signal, ctx.retryWait))) break;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const parsed = (await response.json()) as unknown;
|
|
312
|
+
if (isRecord(parsed)) {
|
|
313
|
+
const payload = parsed as ClaudeUsageResponse;
|
|
314
|
+
lastPayload = payload;
|
|
315
|
+
if (hasUsageData(payload)) return payload;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
ctx.logger?.warn("Claude usage response missing usage data", {
|
|
319
|
+
attempt,
|
|
320
|
+
willRetry: attempt < MAX_ATTEMPTS - 1,
|
|
321
|
+
});
|
|
322
|
+
if (!(await waitBeforeRetry(attempt, null, signal, ctx.retryWait))) break;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (isAbortError(error, signal)) return null;
|
|
325
|
+
ctx.logger?.warn("Claude usage fetch error", {
|
|
326
|
+
error: String(error),
|
|
327
|
+
attempt,
|
|
328
|
+
willRetry: attempt < MAX_ATTEMPTS - 1,
|
|
329
|
+
});
|
|
330
|
+
if (!(await waitBeforeRetry(attempt, null, signal, ctx.retryWait))) break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return lastPayload;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
interface ClaudeProfile {
|
|
338
|
+
uuid?: string;
|
|
339
|
+
email?: string;
|
|
340
|
+
account?: {
|
|
341
|
+
uuid?: string;
|
|
342
|
+
email?: string;
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function extractProfileIdentity(profile: ClaudeProfile | null): { accountId?: string; email?: string } {
|
|
347
|
+
if (!profile || !isRecord(profile)) return {};
|
|
348
|
+
const account = isRecord(profile.account) ? profile.account : undefined;
|
|
349
|
+
return {
|
|
350
|
+
accountId:
|
|
351
|
+
(typeof profile.uuid === "string" && profile.uuid.trim() ? profile.uuid.trim() : undefined) ??
|
|
352
|
+
(typeof account?.uuid === "string" && account.uuid.trim() ? account.uuid.trim() : undefined),
|
|
353
|
+
email:
|
|
354
|
+
(typeof profile.email === "string" && profile.email.trim() ? profile.email.trim() : undefined) ??
|
|
355
|
+
(typeof account?.email === "string" && account.email.trim() ? account.email.trim() : undefined),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function fetchProfile(
|
|
360
|
+
baseUrl: string,
|
|
361
|
+
headers: Record<string, string>,
|
|
362
|
+
ctx: UsageFetchContext,
|
|
363
|
+
signal?: AbortSignal,
|
|
364
|
+
): Promise<ClaudeProfile | null> {
|
|
365
|
+
if (signal?.aborted) return null;
|
|
366
|
+
const url = `${baseUrl}/profile`;
|
|
367
|
+
try {
|
|
368
|
+
const response = await ctx.fetch(url, { headers, signal });
|
|
369
|
+
if (!response.ok) return null;
|
|
370
|
+
const payload = (await response.json()) as unknown;
|
|
371
|
+
return isRecord(payload) ? (payload as ClaudeProfile) : null;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (isAbortError(error, signal)) return null;
|
|
374
|
+
ctx.logger?.debug("Claude profile fetch error", { error: String(error) });
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildUsageAmount(utilization: number | undefined): UsageAmount | undefined {
|
|
380
|
+
if (utilization === undefined) return undefined;
|
|
381
|
+
const clamped = Math.min(Math.max(utilization, 0), 100);
|
|
382
|
+
const usedFraction = clamped / 100;
|
|
383
|
+
return {
|
|
384
|
+
used: clamped,
|
|
385
|
+
limit: 100,
|
|
386
|
+
remaining: Math.max(0, 100 - clamped),
|
|
387
|
+
usedFraction,
|
|
388
|
+
remainingFraction: Math.max(0, 1 - usedFraction),
|
|
389
|
+
unit: "percent",
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function buildUsageStatus(usedFraction: number | undefined): UsageStatus | undefined {
|
|
394
|
+
if (usedFraction === undefined) return undefined;
|
|
395
|
+
if (usedFraction >= 1) return "exhausted";
|
|
396
|
+
if (usedFraction >= 0.9) return "warning";
|
|
397
|
+
return "ok";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function parseDollarAmount(
|
|
401
|
+
amountMinor: unknown,
|
|
402
|
+
exponent: unknown,
|
|
403
|
+
currency: unknown,
|
|
404
|
+
currencyRequired: boolean,
|
|
405
|
+
): number | undefined {
|
|
406
|
+
if (
|
|
407
|
+
typeof amountMinor !== "number" ||
|
|
408
|
+
!Number.isSafeInteger(amountMinor) ||
|
|
409
|
+
amountMinor < 0 ||
|
|
410
|
+
typeof exponent !== "number" ||
|
|
411
|
+
!Number.isSafeInteger(exponent) ||
|
|
412
|
+
exponent < 0
|
|
413
|
+
) {
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
if (currency === undefined) {
|
|
417
|
+
if (currencyRequired) return undefined;
|
|
418
|
+
} else if (typeof currency !== "string" || currency.toUpperCase() !== "USD") {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
const divisor = 10 ** exponent;
|
|
422
|
+
if (!Number.isFinite(divisor)) return undefined;
|
|
423
|
+
const dollars = amountMinor / divisor;
|
|
424
|
+
return Number.isFinite(dollars) ? dollars : undefined;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function parseSpendExtraUsage(value: unknown): ParsedClaudeExtraUsage | null {
|
|
428
|
+
if (!isRecord(value) || value.enabled !== true || !Object.hasOwn(value, "limit") || !isRecord(value.used)) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
const used = parseDollarAmount(value.used.amount_minor, value.used.exponent, value.used.currency, true);
|
|
432
|
+
if (used === undefined) return null;
|
|
433
|
+
if (value.limit === null) return { used };
|
|
434
|
+
if (!isRecord(value.limit)) return null;
|
|
435
|
+
const limit = parseDollarAmount(value.limit.amount_minor, value.limit.exponent, value.limit.currency, true);
|
|
436
|
+
// Reject non-positive caps rather than normalizing them into contradictory zero fractions.
|
|
437
|
+
return limit === undefined || limit <= 0 ? null : { used, limit };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function parseLegacyExtraUsage(value: unknown): ParsedClaudeExtraUsage | null {
|
|
441
|
+
if (!isRecord(value) || value.is_enabled !== true || !Object.hasOwn(value, "monthly_limit")) return null;
|
|
442
|
+
const decimalPlaces = value.decimal_places === undefined ? 2 : value.decimal_places;
|
|
443
|
+
const used = parseDollarAmount(value.used_credits, decimalPlaces, value.currency, false);
|
|
444
|
+
if (used === undefined) return null;
|
|
445
|
+
if (value.monthly_limit === null || value.monthly_limit === undefined) return { used };
|
|
446
|
+
const limit = parseDollarAmount(value.monthly_limit, decimalPlaces, value.currency, false);
|
|
447
|
+
return limit === undefined || limit <= 0 ? null : { used, limit };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function buildExtraUsageAmount(used: number, limit: number | undefined): UsageAmount | undefined {
|
|
451
|
+
if (limit === undefined) return { used, unit: "usd" };
|
|
452
|
+
const remaining = Math.max(0, limit - used);
|
|
453
|
+
const usedFraction = used / limit;
|
|
454
|
+
const remainingFraction = remaining / limit;
|
|
455
|
+
if (!Number.isFinite(remaining) || !Number.isFinite(usedFraction) || !Number.isFinite(remainingFraction)) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
used,
|
|
460
|
+
unit: "usd",
|
|
461
|
+
limit,
|
|
462
|
+
remaining,
|
|
463
|
+
usedFraction,
|
|
464
|
+
remainingFraction,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function buildClaudeExtraUsageLimit(payload: ClaudeUsageResponse): UsageLimit | null {
|
|
469
|
+
const parsed =
|
|
470
|
+
payload.spend === null || payload.spend === undefined
|
|
471
|
+
? parseLegacyExtraUsage(payload.extra_usage)
|
|
472
|
+
: parseSpendExtraUsage(payload.spend);
|
|
473
|
+
if (!parsed) return null;
|
|
474
|
+
|
|
475
|
+
const amount = buildExtraUsageAmount(parsed.used, parsed.limit);
|
|
476
|
+
if (!amount) return null;
|
|
477
|
+
const status =
|
|
478
|
+
parsed.limit === undefined
|
|
479
|
+
? undefined
|
|
480
|
+
: parsed.used >= parsed.limit
|
|
481
|
+
? "exhausted"
|
|
482
|
+
: (buildUsageStatus(amount.usedFraction) ?? "ok");
|
|
483
|
+
return {
|
|
484
|
+
id: "anthropic:extra",
|
|
485
|
+
label: "Claude Extra Usage",
|
|
486
|
+
scope: {
|
|
487
|
+
provider: "anthropic",
|
|
488
|
+
windowId: "extra",
|
|
489
|
+
},
|
|
490
|
+
amount,
|
|
491
|
+
...(status !== undefined ? { status } : {}),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function buildUsageLimit(args: {
|
|
496
|
+
id: string;
|
|
497
|
+
label: string;
|
|
498
|
+
windowId: string;
|
|
499
|
+
windowLabel: string;
|
|
500
|
+
durationMs: number;
|
|
501
|
+
bucket: ParsedUsageBucket | undefined;
|
|
502
|
+
provider: "anthropic";
|
|
503
|
+
tier?: string;
|
|
504
|
+
shared?: boolean;
|
|
505
|
+
}): UsageLimit | null {
|
|
506
|
+
if (!args.bucket) return null;
|
|
507
|
+
const amount = buildUsageAmount(args.bucket.utilization);
|
|
508
|
+
if (!amount) return null;
|
|
509
|
+
const window: UsageWindow = {
|
|
510
|
+
id: args.windowId,
|
|
511
|
+
label: args.windowLabel,
|
|
512
|
+
durationMs: args.durationMs,
|
|
513
|
+
...(args.bucket.resetsAt !== undefined ? { resetsAt: args.bucket.resetsAt } : {}),
|
|
514
|
+
};
|
|
515
|
+
return {
|
|
516
|
+
id: args.id,
|
|
517
|
+
label: args.label,
|
|
518
|
+
scope: {
|
|
519
|
+
provider: args.provider,
|
|
520
|
+
windowId: args.windowId,
|
|
521
|
+
...(args.tier !== undefined ? { tier: args.tier } : {}),
|
|
522
|
+
...(args.shared !== undefined ? { shared: args.shared } : {}),
|
|
523
|
+
},
|
|
524
|
+
window,
|
|
525
|
+
amount,
|
|
526
|
+
status: buildUsageStatus(amount.usedFraction),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function slugifyClaudeLimitDisplayName(displayName: string): string {
|
|
531
|
+
return displayName
|
|
532
|
+
.trim()
|
|
533
|
+
.toLowerCase()
|
|
534
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
535
|
+
.replace(/^-+|-+$/g, "");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Scoped weekly rows are per-model-family counters, not account-wide windows.
|
|
540
|
+
* They deliberately leave `scope.shared` unset so credential-wide exhaustion
|
|
541
|
+
* gating only considers the shared umbrella windows; an exhausted Fable weekly
|
|
542
|
+
* cap must not block Opus or Sonnet requests on the same credential.
|
|
543
|
+
*/
|
|
544
|
+
function buildScopedWeeklyUsageLimits(entries: readonly ParsedApiLimitEntry[]): UsageLimit[] {
|
|
545
|
+
const seenSlugs = new Set<string>();
|
|
546
|
+
const limits: UsageLimit[] = [];
|
|
547
|
+
for (const entry of entries) {
|
|
548
|
+
if (entry.kind !== "weekly_scoped" || !entry.displayName) continue;
|
|
549
|
+
const slug = slugifyClaudeLimitDisplayName(entry.displayName);
|
|
550
|
+
if (!slug || seenSlugs.has(slug)) continue;
|
|
551
|
+
seenSlugs.add(slug);
|
|
552
|
+
const limit = buildUsageLimit({
|
|
553
|
+
id: `anthropic:7d:${slug}`,
|
|
554
|
+
label: `Claude 7 Day (${entry.displayName})`,
|
|
555
|
+
windowId: "7d",
|
|
556
|
+
windowLabel: "7 Day",
|
|
557
|
+
durationMs: SEVEN_DAYS_MS,
|
|
558
|
+
bucket: entry.bucket,
|
|
559
|
+
provider: "anthropic",
|
|
560
|
+
tier: slug,
|
|
561
|
+
});
|
|
562
|
+
if (limit) limits.push(limit);
|
|
563
|
+
}
|
|
564
|
+
return limits;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function parseClaudeRateLimitHeaders(headers: Record<string, string>, now = Date.now()): UsageReport | null {
|
|
568
|
+
const fiveHour = parseUnifiedWindow(headers, "5h");
|
|
569
|
+
const sevenDay = parseUnifiedWindow(headers, "7d");
|
|
570
|
+
const modelScopedSevenDay = parseUnifiedWindow(headers, "7d_oi");
|
|
571
|
+
const limits = [
|
|
572
|
+
buildUsageLimit({
|
|
573
|
+
id: "anthropic:5h",
|
|
574
|
+
label: "Claude 5 Hour",
|
|
575
|
+
windowId: "5h",
|
|
576
|
+
windowLabel: "5 Hour",
|
|
577
|
+
durationMs: FIVE_HOURS_MS,
|
|
578
|
+
bucket: fiveHour,
|
|
579
|
+
provider: "anthropic",
|
|
580
|
+
shared: true,
|
|
581
|
+
}),
|
|
582
|
+
buildUsageLimit({
|
|
583
|
+
id: "anthropic:7d",
|
|
584
|
+
label: "Claude 7 Day",
|
|
585
|
+
windowId: "7d",
|
|
586
|
+
windowLabel: "7 Day",
|
|
587
|
+
durationMs: SEVEN_DAYS_MS,
|
|
588
|
+
bucket: sevenDay,
|
|
589
|
+
provider: "anthropic",
|
|
590
|
+
shared: true,
|
|
591
|
+
}),
|
|
592
|
+
buildUsageLimit({
|
|
593
|
+
id: "anthropic:7d:fable",
|
|
594
|
+
label: "Claude 7 Day (Fable)",
|
|
595
|
+
windowId: "7d",
|
|
596
|
+
windowLabel: "7 Day",
|
|
597
|
+
durationMs: SEVEN_DAYS_MS,
|
|
598
|
+
bucket: modelScopedSevenDay,
|
|
599
|
+
provider: "anthropic",
|
|
600
|
+
tier: "fable",
|
|
601
|
+
}),
|
|
602
|
+
].filter((limit): limit is UsageLimit => limit !== null);
|
|
603
|
+
|
|
604
|
+
if (limits.length === 0) return null;
|
|
605
|
+
return {
|
|
606
|
+
provider: "anthropic",
|
|
607
|
+
fetchedAt: now,
|
|
608
|
+
limits,
|
|
609
|
+
metadata: { source: "ratelimit-headers" },
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise<UsageReport | null> {
|
|
614
|
+
if (params.provider !== "anthropic") return null;
|
|
615
|
+
const credential = params.credential;
|
|
616
|
+
if (credential.type !== "oauth" || !credential.accessToken) return null;
|
|
617
|
+
|
|
618
|
+
const baseUrl = normalizeClaudeBaseUrl(params.baseUrl);
|
|
619
|
+
const url = `${baseUrl}/usage`;
|
|
620
|
+
const headers: Record<string, string> = {
|
|
621
|
+
...CLAUDE_HEADERS,
|
|
622
|
+
authorization: `Bearer ${credential.accessToken}`,
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const payload = await fetchUsagePayload(url, headers, ctx, params.signal);
|
|
626
|
+
if (!payload || !isRecord(payload)) return null;
|
|
627
|
+
|
|
628
|
+
const apiLimitEntries = parseApiLimitEntries(payload.limits);
|
|
629
|
+
const fiveHour = parseBucket(payload.five_hour) ?? apiLimitEntries.find(entry => entry.kind === "session")?.bucket;
|
|
630
|
+
const sevenDay =
|
|
631
|
+
parseBucket(payload.seven_day) ?? apiLimitEntries.find(entry => entry.kind === "weekly_all")?.bucket;
|
|
632
|
+
const sevenDayOpus = parseBucket(payload.seven_day_opus);
|
|
633
|
+
const sevenDaySonnet = parseBucket(payload.seven_day_sonnet);
|
|
634
|
+
|
|
635
|
+
const limits = [
|
|
636
|
+
buildUsageLimit({
|
|
637
|
+
id: "anthropic:5h",
|
|
638
|
+
label: "Claude 5 Hour",
|
|
639
|
+
windowId: "5h",
|
|
640
|
+
windowLabel: "5 Hour",
|
|
641
|
+
durationMs: FIVE_HOURS_MS,
|
|
642
|
+
bucket: fiveHour,
|
|
643
|
+
provider: "anthropic",
|
|
644
|
+
shared: true,
|
|
645
|
+
}),
|
|
646
|
+
buildUsageLimit({
|
|
647
|
+
id: "anthropic:7d",
|
|
648
|
+
label: "Claude 7 Day",
|
|
649
|
+
windowId: "7d",
|
|
650
|
+
windowLabel: "7 Day",
|
|
651
|
+
durationMs: SEVEN_DAYS_MS,
|
|
652
|
+
bucket: sevenDay,
|
|
653
|
+
provider: "anthropic",
|
|
654
|
+
shared: true,
|
|
655
|
+
}),
|
|
656
|
+
buildUsageLimit({
|
|
657
|
+
id: "anthropic:7d:opus",
|
|
658
|
+
label: "Claude 7 Day (Opus)",
|
|
659
|
+
windowId: "7d",
|
|
660
|
+
windowLabel: "7 Day",
|
|
661
|
+
durationMs: SEVEN_DAYS_MS,
|
|
662
|
+
bucket: sevenDayOpus,
|
|
663
|
+
provider: "anthropic",
|
|
664
|
+
tier: "opus",
|
|
665
|
+
}),
|
|
666
|
+
buildUsageLimit({
|
|
667
|
+
id: "anthropic:7d:sonnet",
|
|
668
|
+
label: "Claude 7 Day (Sonnet)",
|
|
669
|
+
windowId: "7d",
|
|
670
|
+
windowLabel: "7 Day",
|
|
671
|
+
durationMs: SEVEN_DAYS_MS,
|
|
672
|
+
bucket: sevenDaySonnet,
|
|
673
|
+
provider: "anthropic",
|
|
674
|
+
tier: "sonnet",
|
|
675
|
+
}),
|
|
676
|
+
...buildScopedWeeklyUsageLimits(apiLimitEntries),
|
|
677
|
+
buildClaudeExtraUsageLimit(payload),
|
|
678
|
+
].filter((limit): limit is UsageLimit => limit !== null);
|
|
679
|
+
|
|
680
|
+
if (limits.length === 0) return null;
|
|
681
|
+
const identity = extractUsageIdentity(payload);
|
|
682
|
+
let accountId = identity.accountId ?? credential.accountId;
|
|
683
|
+
let email = identity.email ?? credential.email;
|
|
684
|
+
if ((!accountId || !email) && !params.signal?.aborted) {
|
|
685
|
+
const profileIdentity = extractProfileIdentity(await fetchProfile(baseUrl, headers, ctx, params.signal));
|
|
686
|
+
accountId = accountId ?? profileIdentity.accountId;
|
|
687
|
+
email = email ?? profileIdentity.email;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const report: UsageReport = {
|
|
691
|
+
provider: params.provider,
|
|
692
|
+
fetchedAt: Date.now(),
|
|
693
|
+
limits,
|
|
694
|
+
metadata: {
|
|
695
|
+
endpoint: url,
|
|
696
|
+
...(accountId ? { accountId } : {}),
|
|
697
|
+
...(email ? { email } : {}),
|
|
698
|
+
...(credential.orgId ? { orgId: credential.orgId } : {}),
|
|
699
|
+
},
|
|
700
|
+
raw: payload,
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
return report;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export const claudeUsageProvider: UsageProvider = {
|
|
707
|
+
id: "anthropic",
|
|
708
|
+
fetchUsage: fetchClaudeUsage,
|
|
709
|
+
parseRateLimitHeaders: parseClaudeRateLimitHeaders,
|
|
710
|
+
supports: params => params.provider === "anthropic" && params.credential.type === "oauth",
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
function getClaudeModelKind(context: CredentialRankingContext | undefined): ClaudeModelKind | undefined {
|
|
714
|
+
const modelId = context?.modelId;
|
|
715
|
+
if (!modelId) return undefined;
|
|
716
|
+
return parseAnthropicModel(bareModelId(modelId))?.kind;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Claude model-scoped rows are only relevant to the matching model family.
|
|
721
|
+
* Credential-wide exhaustion checks stay on shared umbrella windows unless the
|
|
722
|
+
* request model parses to a concrete Anthropic kind, preventing a Fable cap from
|
|
723
|
+
* suppressing unrelated Opus/Sonnet traffic.
|
|
724
|
+
*/
|
|
725
|
+
function scopeClaudeLimitsForModel(report: UsageReport, context: CredentialRankingContext | undefined): UsageLimit[] {
|
|
726
|
+
const kind = getClaudeModelKind(context);
|
|
727
|
+
return report.limits.filter(
|
|
728
|
+
limit => limit.scope.shared === true || (kind !== undefined && limit.scope.tier === kind),
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* A Fable/Mythos weekly row is trusted for gating only at full exhaustion
|
|
734
|
+
* (server `exhausted` status or used fraction >= 1) with a live reset
|
|
735
|
+
* timestamp. Anything below that stays untrusted: the counters are
|
|
736
|
+
* notoriously unreliable short of the cap (they report high utilization
|
|
737
|
+
* while the account can still serve requests).
|
|
738
|
+
*/
|
|
739
|
+
function isConfirmedExhaustedTierRow(limit: UsageLimit, nowMs: number): boolean {
|
|
740
|
+
const resetsAt = limit.window?.resetsAt;
|
|
741
|
+
if (typeof resetsAt !== "number" || !Number.isFinite(resetsAt) || resetsAt <= nowMs) return false;
|
|
742
|
+
if (limit.status === "exhausted") return true;
|
|
743
|
+
const fraction = resolveUsedFraction(limit);
|
|
744
|
+
return typeof fraction === "number" && fraction >= 1;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Scope limits for proactive hard-blocking (gating). Fable and Mythos tier
|
|
749
|
+
* weekly caps participate only when {@link isConfirmedExhaustedTierRow}
|
|
750
|
+
* confirms them, so a confirmed-dead account is skipped up front and a
|
|
751
|
+
* reactive 429 block extends to the tier reset in markUsageLimitReached,
|
|
752
|
+
* while unconfirmed rows remain ranking pressure only via
|
|
753
|
+
* scopeClaudeLimitsForModel.
|
|
754
|
+
*/
|
|
755
|
+
function scopeClaudeLimitsForModelHardBlock(
|
|
756
|
+
report: UsageReport,
|
|
757
|
+
context: CredentialRankingContext | undefined,
|
|
758
|
+
): UsageLimit[] {
|
|
759
|
+
const kind = getClaudeModelKind(context);
|
|
760
|
+
const requireConfirmedTierRow = kind === "fable" || kind === "mythos";
|
|
761
|
+
const nowMs = Date.now();
|
|
762
|
+
return report.limits.filter(limit => {
|
|
763
|
+
if (limit.scope.shared === true) return true;
|
|
764
|
+
if (kind === undefined || limit.scope.tier !== kind) return false;
|
|
765
|
+
return !requireConfirmedTierRow || isConfirmedExhaustedTierRow(limit, nowMs);
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function rankingUsedFraction(limit: UsageLimit): number {
|
|
770
|
+
const fraction = resolveUsedFraction(limit);
|
|
771
|
+
if (typeof fraction !== "number" || !Number.isFinite(fraction)) return 0.5;
|
|
772
|
+
return Math.min(Math.max(fraction, 0), 1);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function rankingDrainRate(limit: UsageLimit, nowMs: number): number {
|
|
776
|
+
const usedFraction = rankingUsedFraction(limit);
|
|
777
|
+
const durationMs = limit.window?.durationMs ?? SEVEN_DAYS_MS;
|
|
778
|
+
if (!Number.isFinite(durationMs) || durationMs <= 0) return usedFraction;
|
|
779
|
+
const resetAt = limit.window?.resetsAt;
|
|
780
|
+
if (typeof resetAt !== "number" || !Number.isFinite(resetAt)) return usedFraction;
|
|
781
|
+
const remainingWindowMs = resetAt - nowMs;
|
|
782
|
+
const clampedRemainingWindowMs = Math.min(Math.max(remainingWindowMs, 0), durationMs);
|
|
783
|
+
const elapsedMs = durationMs - clampedRemainingWindowMs;
|
|
784
|
+
if (elapsedMs <= 0) return usedFraction;
|
|
785
|
+
const elapsedHours = elapsedMs / (60 * 60 * 1000);
|
|
786
|
+
if (!Number.isFinite(elapsedHours) || elapsedHours <= 0) return usedFraction;
|
|
787
|
+
return usedFraction / elapsedHours;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function morePressuredLimit(
|
|
791
|
+
left: UsageLimit | undefined,
|
|
792
|
+
right: UsageLimit | undefined,
|
|
793
|
+
nowMs: number,
|
|
794
|
+
): UsageLimit | undefined {
|
|
795
|
+
if (!left) return right;
|
|
796
|
+
if (!right) return left;
|
|
797
|
+
const leftDrainRate = rankingDrainRate(left, nowMs);
|
|
798
|
+
const rightDrainRate = rankingDrainRate(right, nowMs);
|
|
799
|
+
if (rightDrainRate !== leftDrainRate) return rightDrainRate > leftDrainRate ? right : left;
|
|
800
|
+
return rankingUsedFraction(right) > rankingUsedFraction(left) ? right : left;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function findClaudeSecondaryLimit(
|
|
804
|
+
report: UsageReport,
|
|
805
|
+
context: CredentialRankingContext | undefined,
|
|
806
|
+
): UsageLimit | undefined {
|
|
807
|
+
const nowMs = Date.now();
|
|
808
|
+
return scopeClaudeLimitsForModel(report, context)
|
|
809
|
+
.filter(limit => limit.scope.windowId === "7d" || limit.window?.id === "7d")
|
|
810
|
+
.reduce<UsageLimit | undefined>((selected, limit) => morePressuredLimit(selected, limit, nowMs), undefined);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export const claudeRankingStrategy: CredentialRankingStrategy = {
|
|
814
|
+
findWindowLimits(report, context) {
|
|
815
|
+
const primary = report.limits.find(limit => limit.id === "anthropic:5h");
|
|
816
|
+
const secondary = findClaudeSecondaryLimit(report, context);
|
|
817
|
+
return { primary, secondary };
|
|
818
|
+
},
|
|
819
|
+
scopeLimits: scopeClaudeLimitsForModelHardBlock,
|
|
820
|
+
/**
|
|
821
|
+
* Fable/Mythos usage-limit errors map to tier-local weekly counters. Scope
|
|
822
|
+
* reactive backoff blocks for those tiers, mirroring the per-counter
|
|
823
|
+
* precedent in packages/ai/src/usage/google-antigravity.ts:466-497.
|
|
824
|
+
*/
|
|
825
|
+
blockScope(context) {
|
|
826
|
+
const kind = getClaudeModelKind(context);
|
|
827
|
+
return kind === "fable" || kind === "mythos" ? `tier:${kind}` : undefined;
|
|
828
|
+
},
|
|
829
|
+
windowDefaults: { primaryMs: 5 * 60 * 60 * 1000, secondaryMs: 7 * 24 * 60 * 60 * 1000 },
|
|
830
|
+
};
|