@aryee337/aery-ai 0.2.28 → 0.2.29
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 +2914 -0
- package/README.md +614 -813
- package/package.json +140 -105
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +358 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +623 -0
- package/src/auth-broker/server.ts +644 -0
- package/src/auth-broker/types.ts +127 -0
- package/src/auth-broker/wire-schemas.ts +200 -0
- package/src/auth-gateway/http.ts +194 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +818 -0
- package/src/auth-gateway/types.ts +143 -0
- package/src/auth-storage.ts +4422 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +469 -0
- package/src/model-thinking.ts +782 -0
- package/src/models.json +83530 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +90 -0
- package/src/provider-models/bundled-references.ts +38 -0
- package/src/provider-models/descriptors.ts +355 -0
- package/src/provider-models/google.ts +88 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2817 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/aery-native-client.ts +228 -0
- package/src/providers/aery-native-server.ts +212 -0
- package/src/providers/amazon-bedrock.ts +873 -0
- package/src/providers/anthropic-client.ts +318 -0
- package/src/providers/anthropic-messages-server-schema.ts +243 -0
- package/src/providers/anthropic-messages-server.ts +683 -0
- package/src/providers/anthropic-wire.ts +268 -0
- package/src/providers/anthropic.ts +3094 -0
- package/src/providers/aws-credentials.ts +501 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +361 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2621 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +372 -0
- package/src/providers/google-auth.ts +252 -0
- package/src/providers/google-gemini-cli.ts +809 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +917 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +91 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/mock.ts +496 -0
- package/src/providers/ollama.ts +644 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +252 -0
- package/src/providers/openai-chat-server.ts +647 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +161 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +3018 -0
- package/src/providers/openai-completions-compat.ts +300 -0
- package/src/providers/openai-completions.ts +1979 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1183 -0
- package/src/providers/openai-responses-shared.ts +873 -0
- package/src/providers/openai-responses.ts +679 -0
- package/src/providers/register-builtins.ts +436 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +382 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/providers/xai-responses.ts +82 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +1065 -0
- package/src/types.ts +944 -0
- package/src/usage/claude.ts +482 -0
- package/src/usage/gemini.ts +250 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +201 -0
- package/src/usage/kimi.ts +271 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +503 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +185 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/abortable-iterator.ts +69 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +142 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +267 -0
- package/src/utils/json-parse.ts +182 -0
- package/src/utils/oauth/__tests__/xai-oauth.test.ts +107 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +273 -0
- package/src/utils/oauth/api-key-login.ts +87 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepseek.ts +53 -0
- package/src/utils/oauth/firepass.ts +24 -0
- package/src/utils/oauth/fireworks.ts +15 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +200 -0
- package/src/utils/oauth/google-gemini-cli.ts +256 -0
- package/src/utils/oauth/google-oauth-shared.ts +110 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +484 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +254 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +23 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +203 -0
- package/src/utils/oauth/ollama-cloud.ts +28 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +299 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/openrouter.ts +20 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +206 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +15 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +99 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/wafer.ts +50 -0
- package/src/utils/oauth/xai-oauth.ts +342 -0
- package/src/utils/oauth/xiaomi.ts +139 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/oauth/zhipu.ts +60 -0
- package/src/utils/overflow.ts +137 -0
- package/src/utils/parse-bind.ts +54 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/request-debug.ts +336 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +54 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -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 +191 -0
- package/src/utils/schema/index.ts +13 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/wire.ts +293 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/stream-markup-healing.ts +612 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1024 -0
- package/src/utils.ts +166 -0
- package/dist/api-registry.d.ts +0 -20
- package/dist/api-registry.d.ts.map +0 -1
- package/dist/api-registry.js +0 -44
- package/dist/api-registry.js.map +0 -1
- package/dist/bedrock-provider.d.ts +0 -5
- package/dist/bedrock-provider.d.ts.map +0 -1
- package/dist/bedrock-provider.js +0 -6
- package/dist/bedrock-provider.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -130
- package/dist/cli.js.map +0 -1
- package/dist/env-api-keys.d.ts +0 -18
- package/dist/env-api-keys.d.ts.map +0 -1
- package/dist/env-api-keys.js +0 -178
- package/dist/env-api-keys.js.map +0 -1
- package/dist/image-models.d.ts +0 -10
- package/dist/image-models.d.ts.map +0 -1
- package/dist/image-models.generated.d.ts +0 -440
- package/dist/image-models.generated.d.ts.map +0 -1
- package/dist/image-models.generated.js +0 -442
- package/dist/image-models.generated.js.map +0 -1
- package/dist/image-models.js +0 -23
- package/dist/image-models.js.map +0 -1
- package/dist/images-api-registry.d.ts +0 -14
- package/dist/images-api-registry.d.ts.map +0 -1
- package/dist/images-api-registry.js +0 -22
- package/dist/images-api-registry.js.map +0 -1
- package/dist/images.d.ts +0 -4
- package/dist/images.d.ts.map +0 -1
- package/dist/images.js +0 -14
- package/dist/images.js.map +0 -1
- package/dist/index.d.ts +0 -32
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/models.d.ts +0 -18
- package/dist/models.d.ts.map +0 -1
- package/dist/models.generated.d.ts +0 -17707
- package/dist/models.generated.d.ts.map +0 -1
- package/dist/models.generated.js +0 -16561
- package/dist/models.generated.js.map +0 -1
- package/dist/models.js +0 -71
- package/dist/models.js.map +0 -1
- package/dist/oauth.d.ts +0 -2
- package/dist/oauth.d.ts.map +0 -1
- package/dist/oauth.js +0 -2
- package/dist/oauth.js.map +0 -1
- package/dist/providers/aery-error-formatting.d.ts +0 -13
- package/dist/providers/aery-error-formatting.d.ts.map +0 -1
- package/dist/providers/aery-error-formatting.js +0 -112
- package/dist/providers/aery-error-formatting.js.map +0 -1
- package/dist/providers/amazon-bedrock.d.ts +0 -38
- package/dist/providers/amazon-bedrock.d.ts.map +0 -1
- package/dist/providers/amazon-bedrock.js +0 -763
- package/dist/providers/amazon-bedrock.js.map +0 -1
- package/dist/providers/anthropic.d.ts +0 -71
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js +0 -949
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/azure-openai-responses.d.ts +0 -15
- package/dist/providers/azure-openai-responses.d.ts.map +0 -1
- package/dist/providers/azure-openai-responses.js +0 -225
- package/dist/providers/azure-openai-responses.js.map +0 -1
- package/dist/providers/cloudflare.d.ts +0 -13
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js +0 -26
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/faux.d.ts +0 -56
- package/dist/providers/faux.d.ts.map +0 -1
- package/dist/providers/faux.js +0 -368
- package/dist/providers/faux.js.map +0 -1
- package/dist/providers/github-copilot-headers.d.ts +0 -8
- package/dist/providers/github-copilot-headers.d.ts.map +0 -1
- package/dist/providers/github-copilot-headers.js +0 -29
- package/dist/providers/github-copilot-headers.js.map +0 -1
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -779
- package/dist/providers/google-gemini-cli.js.map +0 -1
- package/dist/providers/google-shared.d.ts +0 -70
- package/dist/providers/google-shared.d.ts.map +0 -1
- package/dist/providers/google-shared.js +0 -329
- package/dist/providers/google-shared.js.map +0 -1
- package/dist/providers/google-vertex.d.ts +0 -15
- package/dist/providers/google-vertex.d.ts.map +0 -1
- package/dist/providers/google-vertex.js +0 -442
- package/dist/providers/google-vertex.js.map +0 -1
- package/dist/providers/google.d.ts +0 -13
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js +0 -400
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/images/openrouter.d.ts +0 -3
- package/dist/providers/images/openrouter.d.ts.map +0 -1
- package/dist/providers/images/openrouter.js +0 -129
- package/dist/providers/images/openrouter.js.map +0 -1
- package/dist/providers/images/register-builtins.d.ts +0 -4
- package/dist/providers/images/register-builtins.d.ts.map +0 -1
- package/dist/providers/images/register-builtins.js +0 -34
- package/dist/providers/images/register-builtins.js.map +0 -1
- package/dist/providers/mistral.d.ts +0 -25
- package/dist/providers/mistral.d.ts.map +0 -1
- package/dist/providers/mistral.js +0 -535
- package/dist/providers/mistral.js.map +0 -1
- package/dist/providers/openai-codex-responses.d.ts +0 -30
- package/dist/providers/openai-codex-responses.d.ts.map +0 -1
- package/dist/providers/openai-codex-responses.js +0 -1090
- package/dist/providers/openai-codex-responses.js.map +0 -1
- package/dist/providers/openai-completions.d.ts +0 -19
- package/dist/providers/openai-completions.d.ts.map +0 -1
- package/dist/providers/openai-completions.js +0 -950
- package/dist/providers/openai-completions.js.map +0 -1
- package/dist/providers/openai-prompt-cache.d.ts +0 -3
- package/dist/providers/openai-prompt-cache.d.ts.map +0 -1
- package/dist/providers/openai-prompt-cache.js +0 -10
- package/dist/providers/openai-prompt-cache.js.map +0 -1
- package/dist/providers/openai-responses-shared.d.ts +0 -18
- package/dist/providers/openai-responses-shared.d.ts.map +0 -1
- package/dist/providers/openai-responses-shared.js +0 -492
- package/dist/providers/openai-responses-shared.js.map +0 -1
- package/dist/providers/openai-responses.d.ts +0 -13
- package/dist/providers/openai-responses.d.ts.map +0 -1
- package/dist/providers/openai-responses.js +0 -237
- package/dist/providers/openai-responses.js.map +0 -1
- package/dist/providers/register-builtins.d.ts +0 -38
- package/dist/providers/register-builtins.d.ts.map +0 -1
- package/dist/providers/register-builtins.js +0 -278
- package/dist/providers/register-builtins.js.map +0 -1
- package/dist/providers/simple-options.d.ts +0 -8
- package/dist/providers/simple-options.d.ts.map +0 -1
- package/dist/providers/simple-options.js +0 -41
- package/dist/providers/simple-options.js.map +0 -1
- package/dist/providers/transform-messages.d.ts +0 -8
- package/dist/providers/transform-messages.d.ts.map +0 -1
- package/dist/providers/transform-messages.js +0 -184
- package/dist/providers/transform-messages.js.map +0 -1
- package/dist/session-resources.d.ts +0 -4
- package/dist/session-resources.d.ts.map +0 -1
- package/dist/session-resources.js +0 -22
- package/dist/session-resources.js.map +0 -1
- package/dist/stream.d.ts +0 -8
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -27
- package/dist/stream.js.map +0 -1
- package/dist/types.d.ts +0 -498
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/diagnostics.d.ts +0 -19
- package/dist/utils/diagnostics.d.ts.map +0 -1
- package/dist/utils/diagnostics.js +0 -25
- package/dist/utils/diagnostics.js.map +0 -1
- package/dist/utils/event-stream.d.ts +0 -21
- package/dist/utils/event-stream.d.ts.map +0 -1
- package/dist/utils/event-stream.js +0 -81
- package/dist/utils/event-stream.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -3
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -14
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/headers.d.ts +0 -2
- package/dist/utils/headers.d.ts.map +0 -1
- package/dist/utils/headers.js +0 -8
- package/dist/utils/headers.js.map +0 -1
- package/dist/utils/json-parse.d.ts +0 -16
- package/dist/utils/json-parse.d.ts.map +0 -1
- package/dist/utils/json-parse.js +0 -113
- package/dist/utils/json-parse.js.map +0 -1
- package/dist/utils/node-http-proxy.d.ts +0 -10
- package/dist/utils/node-http-proxy.d.ts.map +0 -1
- package/dist/utils/node-http-proxy.js +0 -97
- package/dist/utils/node-http-proxy.js.map +0 -1
- package/dist/utils/oauth/anthropic.d.ts +0 -25
- package/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/dist/utils/oauth/anthropic.js +0 -335
- package/dist/utils/oauth/anthropic.js.map +0 -1
- package/dist/utils/oauth/device-code.d.ts +0 -19
- package/dist/utils/oauth/device-code.d.ts.map +0 -1
- package/dist/utils/oauth/device-code.js +0 -55
- package/dist/utils/oauth/device-code.js.map +0 -1
- package/dist/utils/oauth/github-copilot.d.ts +0 -30
- package/dist/utils/oauth/github-copilot.d.ts.map +0 -1
- package/dist/utils/oauth/github-copilot.js +0 -268
- package/dist/utils/oauth/github-copilot.js.map +0 -1
- package/dist/utils/oauth/google-antigravity.d.ts +0 -26
- package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
- package/dist/utils/oauth/google-antigravity.js +0 -377
- package/dist/utils/oauth/google-antigravity.js.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.js +0 -482
- package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
- package/dist/utils/oauth/index.d.ts +0 -63
- package/dist/utils/oauth/index.d.ts.map +0 -1
- package/dist/utils/oauth/index.js +0 -131
- package/dist/utils/oauth/index.js.map +0 -1
- package/dist/utils/oauth/oauth-page.d.ts +0 -3
- package/dist/utils/oauth/oauth-page.d.ts.map +0 -1
- package/dist/utils/oauth/oauth-page.js +0 -105
- package/dist/utils/oauth/oauth-page.js.map +0 -1
- package/dist/utils/oauth/openai-codex.d.ts +0 -34
- package/dist/utils/oauth/openai-codex.d.ts.map +0 -1
- package/dist/utils/oauth/openai-codex.js +0 -385
- package/dist/utils/oauth/openai-codex.js.map +0 -1
- package/dist/utils/oauth/pkce.d.ts +0 -13
- package/dist/utils/oauth/pkce.d.ts.map +0 -1
- package/dist/utils/oauth/pkce.js +0 -31
- package/dist/utils/oauth/pkce.js.map +0 -1
- package/dist/utils/oauth/types.d.ts +0 -64
- package/dist/utils/oauth/types.d.ts.map +0 -1
- package/dist/utils/oauth/types.js +0 -2
- package/dist/utils/oauth/types.js.map +0 -1
- package/dist/utils/overflow.d.ts +0 -56
- package/dist/utils/overflow.d.ts.map +0 -1
- package/dist/utils/overflow.js +0 -151
- package/dist/utils/overflow.js.map +0 -1
- package/dist/utils/sanitize-unicode.d.ts +0 -22
- package/dist/utils/sanitize-unicode.d.ts.map +0 -1
- package/dist/utils/sanitize-unicode.js +0 -26
- package/dist/utils/sanitize-unicode.js.map +0 -1
- package/dist/utils/typebox-helpers.d.ts +0 -17
- package/dist/utils/typebox-helpers.d.ts.map +0 -1
- package/dist/utils/typebox-helpers.js +0 -21
- package/dist/utils/typebox-helpers.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -18
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -281
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wafer login flows.
|
|
3
|
+
*
|
|
4
|
+
* Wafer (https://wafer.ai) exposes a single OpenAI-compatible base URL
|
|
5
|
+
* (`https://pass.wafer.ai/v1`) for two SKUs:
|
|
6
|
+
*
|
|
7
|
+
* - **Wafer Pass** — flat-rate subscription. The key authorizes models whose
|
|
8
|
+
* catalog entries carry `wafer.tier = "pass_included"`.
|
|
9
|
+
* - **Wafer Serverless** — pay-as-you-go. Superset of Pass; the same `/v1/models`
|
|
10
|
+
* endpoint returns the full per-account model list.
|
|
11
|
+
*
|
|
12
|
+
* Both SKUs issue `wfr_…` keys. The key prefix alone does not distinguish
|
|
13
|
+
* tiers — the entitlement is per-account on the server side — so we expose
|
|
14
|
+
* two parallel logins / env vars (`WAFER_PASS_API_KEY`, `WAFER_SERVERLESS_API_KEY`)
|
|
15
|
+
* mirroring the firepass/fireworks split, letting users with both
|
|
16
|
+
* subscriptions switch between them without re-pasting.
|
|
17
|
+
*
|
|
18
|
+
* Validation uses the shared `/v1/models` endpoint, which works for both
|
|
19
|
+
* tiers and is cheap (no token spend).
|
|
20
|
+
*/
|
|
21
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
22
|
+
|
|
23
|
+
const WAFER_AUTH_URL = "https://wafer.ai/dashboard";
|
|
24
|
+
const WAFER_MODELS_URL = "https://pass.wafer.ai/v1/models";
|
|
25
|
+
|
|
26
|
+
export const loginWaferPass = createApiKeyLogin({
|
|
27
|
+
providerLabel: "Wafer Pass",
|
|
28
|
+
authUrl: WAFER_AUTH_URL,
|
|
29
|
+
instructions: "Create or copy your Wafer Pass API key from the Wafer dashboard",
|
|
30
|
+
promptMessage: "Paste your Wafer Pass API key",
|
|
31
|
+
placeholder: "wfr_...",
|
|
32
|
+
validation: {
|
|
33
|
+
kind: "models-endpoint",
|
|
34
|
+
provider: "Wafer Pass",
|
|
35
|
+
modelsUrl: WAFER_MODELS_URL,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const loginWaferServerless = createApiKeyLogin({
|
|
40
|
+
providerLabel: "Wafer Serverless",
|
|
41
|
+
authUrl: WAFER_AUTH_URL,
|
|
42
|
+
instructions: "Create or copy your Wafer Serverless API key from the Wafer dashboard",
|
|
43
|
+
promptMessage: "Paste your Wafer Serverless API key",
|
|
44
|
+
placeholder: "wfr_...",
|
|
45
|
+
validation: {
|
|
46
|
+
kind: "models-endpoint",
|
|
47
|
+
provider: "Wafer Serverless",
|
|
48
|
+
modelsUrl: WAFER_MODELS_URL,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// Ported from NousResearch/hermes-agent (MIT) — hermes_cli/auth.py xAI sections (L93-111, L2979-3160, L5286-5469).
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* xAI Grok (SuperGrok Subscription) OAuth flow.
|
|
5
|
+
*
|
|
6
|
+
* Loopback PKCE flow on `127.0.0.1:56121/callback`. One token unlocks Grok-4.x
|
|
7
|
+
* chat, Grok Imagine image generation, and Grok Voice TTS via subsequent
|
|
8
|
+
* commits. Endpoint discovery is hardened against MITM via
|
|
9
|
+
* {@link validateXAIEndpoint}: any non-HTTPS or non-`x.ai`/`*.x.ai` host is
|
|
10
|
+
* rejected on every call site, not just the first.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
14
|
+
import { generatePKCE } from "./pkce";
|
|
15
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
16
|
+
|
|
17
|
+
// Hermes hermes_cli/auth.py L93-111
|
|
18
|
+
const XAI_OAUTH_ISSUER = "https://auth.x.ai";
|
|
19
|
+
const XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;
|
|
20
|
+
const XAI_OAUTH_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
21
|
+
const XAI_OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
22
|
+
const XAI_OAUTH_REDIRECT_HOST = "127.0.0.1";
|
|
23
|
+
const XAI_OAUTH_REDIRECT_PORT = 56121;
|
|
24
|
+
const XAI_OAUTH_REDIRECT_PATH = "/callback";
|
|
25
|
+
const XAI_OAUTH_DOCS_URL = "https://hermes-agent.nousresearch.com/docs/guides/xai-grok-oauth";
|
|
26
|
+
|
|
27
|
+
// Mirrors the 5-min skew used by anthropic.ts:160 — keeps every provider on the
|
|
28
|
+
// same conservative client-side expiry window.
|
|
29
|
+
const ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
const DISCOVERY_TIMEOUT_MS = 15_000;
|
|
32
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 20_000;
|
|
33
|
+
|
|
34
|
+
interface XAIOAuthDiscovery {
|
|
35
|
+
authorization_endpoint: string;
|
|
36
|
+
token_endpoint: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate an xAI OIDC discovery endpoint against scheme + host.
|
|
41
|
+
*
|
|
42
|
+
* Hermes `_xai_validate_oauth_endpoint` L2997-3035. The discovery response is
|
|
43
|
+
* long-lived and cached in {@link OAuthCredentials}; a single MITM during
|
|
44
|
+
* initial login could substitute a malicious `token_endpoint` that would then
|
|
45
|
+
* receive every future refresh_token. Rejecting non-HTTPS or non-`x.ai` /
|
|
46
|
+
* `*.x.ai` hosts pins the cached endpoint to the xAI auth origin.
|
|
47
|
+
*
|
|
48
|
+
* @throws Error with message `Invalid xAI <field>: <url>` when the URL fails
|
|
49
|
+
* either scheme or host validation.
|
|
50
|
+
*/
|
|
51
|
+
export function validateXAIEndpoint(url: string, field: string): string {
|
|
52
|
+
let parsed: URL;
|
|
53
|
+
try {
|
|
54
|
+
parsed = new URL(url);
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error(`Invalid xAI ${field}: ${url}`);
|
|
57
|
+
}
|
|
58
|
+
if (parsed.protocol !== "https:") {
|
|
59
|
+
throw new Error(`Invalid xAI ${field}: ${url}`);
|
|
60
|
+
}
|
|
61
|
+
const host = parsed.hostname.toLowerCase();
|
|
62
|
+
if (!host || (host !== "x.ai" && !host.endsWith(".x.ai"))) {
|
|
63
|
+
throw new Error(`Invalid xAI ${field}: ${url}`);
|
|
64
|
+
}
|
|
65
|
+
return url;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fetch xAI's OIDC discovery document and validate both endpoints.
|
|
70
|
+
*
|
|
71
|
+
* Hermes `_xai_oauth_discovery` L3038-3084.
|
|
72
|
+
*/
|
|
73
|
+
async function xaiOAuthDiscovery(timeoutMs: number = DISCOVERY_TIMEOUT_MS): Promise<XAIOAuthDiscovery> {
|
|
74
|
+
let response: Response;
|
|
75
|
+
try {
|
|
76
|
+
response = await fetch(XAI_OAUTH_DISCOVERY_URL, {
|
|
77
|
+
method: "GET",
|
|
78
|
+
headers: { Accept: "application/json" },
|
|
79
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`xAI OIDC discovery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
}
|
|
84
|
+
if (response.status !== 200) {
|
|
85
|
+
throw new Error(`xAI OIDC discovery returned status ${response.status}.`);
|
|
86
|
+
}
|
|
87
|
+
let payload: unknown;
|
|
88
|
+
try {
|
|
89
|
+
payload = await response.json();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`xAI OIDC discovery returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (!payload || typeof payload !== "object") {
|
|
96
|
+
throw new Error("xAI OIDC discovery response was not a JSON object.");
|
|
97
|
+
}
|
|
98
|
+
const obj = payload as Record<string, unknown>;
|
|
99
|
+
const authorizationEndpoint =
|
|
100
|
+
typeof obj.authorization_endpoint === "string" ? obj.authorization_endpoint.trim() : "";
|
|
101
|
+
const tokenEndpoint = typeof obj.token_endpoint === "string" ? obj.token_endpoint.trim() : "";
|
|
102
|
+
if (!authorizationEndpoint || !tokenEndpoint) {
|
|
103
|
+
throw new Error("xAI OIDC discovery response was missing required endpoints.");
|
|
104
|
+
}
|
|
105
|
+
validateXAIEndpoint(authorizationEndpoint, "authorization_endpoint");
|
|
106
|
+
validateXAIEndpoint(tokenEndpoint, "token_endpoint");
|
|
107
|
+
return {
|
|
108
|
+
authorization_endpoint: authorizationEndpoint,
|
|
109
|
+
token_endpoint: tokenEndpoint,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check whether a JWT access token is at or past its `exp` claim (with an
|
|
115
|
+
* optional refresh-skew margin).
|
|
116
|
+
*
|
|
117
|
+
* Hermes `_xai_access_token_is_expiring` L2979-2994. Returns `false` for any
|
|
118
|
+
* malformed input — this is a refresh-trigger check, not a validation, so
|
|
119
|
+
* non-JWTs ("no token in cache") must NOT trigger a spurious refresh.
|
|
120
|
+
*/
|
|
121
|
+
export function isXAIAccessTokenExpiring(jwt: string, skewSeconds: number = 0): boolean {
|
|
122
|
+
try {
|
|
123
|
+
if (typeof jwt !== "string" || !jwt.includes(".")) return false;
|
|
124
|
+
const parts = jwt.split(".");
|
|
125
|
+
if (parts.length < 2) return false;
|
|
126
|
+
const payloadPart = parts[1];
|
|
127
|
+
if (!payloadPart) return false;
|
|
128
|
+
const decoded = Buffer.from(payloadPart, "base64url").toString("utf8");
|
|
129
|
+
const payload = JSON.parse(decoded) as { exp?: unknown };
|
|
130
|
+
const exp = payload.exp;
|
|
131
|
+
if (typeof exp !== "number" || !Number.isFinite(exp)) return false;
|
|
132
|
+
const now = Math.floor(Date.now() / 1000);
|
|
133
|
+
const skew = Math.max(0, Math.floor(skewSeconds));
|
|
134
|
+
return exp <= now + skew;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface BuildXAIAuthorizeUrlOptions {
|
|
141
|
+
authorizationEndpoint: string;
|
|
142
|
+
redirectUri: string;
|
|
143
|
+
codeChallenge: string;
|
|
144
|
+
state: string;
|
|
145
|
+
nonce: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build the xAI authorization URL.
|
|
150
|
+
*
|
|
151
|
+
* Hermes `_xai_oauth_build_authorize_url` L5286-5312. `plan=generic` opts the
|
|
152
|
+
* consent screen into xAI's generic OAuth plan tier; without it,
|
|
153
|
+
* `accounts.x.ai` rejects loopback OAuth from non-allowlisted clients.
|
|
154
|
+
* `referrer=aery` lets xAI attribute aery-originated logins in their
|
|
155
|
+
* OAuth server logs (Hermes uses `referrer=hermes-agent`; aery mirrors the
|
|
156
|
+
* pattern with its own attribution string).
|
|
157
|
+
*/
|
|
158
|
+
function buildXAIAuthorizeUrl(opts: BuildXAIAuthorizeUrlOptions): string {
|
|
159
|
+
const params = new URLSearchParams({
|
|
160
|
+
response_type: "code",
|
|
161
|
+
client_id: XAI_OAUTH_CLIENT_ID,
|
|
162
|
+
redirect_uri: opts.redirectUri,
|
|
163
|
+
scope: XAI_OAUTH_SCOPE,
|
|
164
|
+
code_challenge: opts.codeChallenge,
|
|
165
|
+
code_challenge_method: "S256",
|
|
166
|
+
state: opts.state,
|
|
167
|
+
nonce: opts.nonce,
|
|
168
|
+
plan: "generic",
|
|
169
|
+
referrer: "aery",
|
|
170
|
+
});
|
|
171
|
+
return `${opts.authorizationEndpoint}?${params.toString()}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* xAI Grok OAuth loopback flow (Hermes `_xai_oauth_loopback_login` L5315-5469).
|
|
176
|
+
*
|
|
177
|
+
* Uses a fixed redirect URI so the callback server fails fast instead of
|
|
178
|
+
* falling back to a random port that xAI's redirect_uri allowlist rejects.
|
|
179
|
+
*/
|
|
180
|
+
export class XAIOAuthFlow extends OAuthCallbackFlow {
|
|
181
|
+
#verifier: string = "";
|
|
182
|
+
|
|
183
|
+
constructor(ctrl: OAuthController) {
|
|
184
|
+
super(ctrl, {
|
|
185
|
+
preferredPort: XAI_OAUTH_REDIRECT_PORT,
|
|
186
|
+
callbackPath: XAI_OAUTH_REDIRECT_PATH,
|
|
187
|
+
callbackHostname: XAI_OAUTH_REDIRECT_HOST,
|
|
188
|
+
redirectUri: `http://${XAI_OAUTH_REDIRECT_HOST}:${XAI_OAUTH_REDIRECT_PORT}${XAI_OAUTH_REDIRECT_PATH}`,
|
|
189
|
+
} satisfies OAuthCallbackFlowOptions);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
193
|
+
const pkce = await generatePKCE();
|
|
194
|
+
this.#verifier = pkce.verifier;
|
|
195
|
+
const nonce = crypto.randomUUID().replace(/-/g, "");
|
|
196
|
+
|
|
197
|
+
const discovery = await xaiOAuthDiscovery();
|
|
198
|
+
const url = buildXAIAuthorizeUrl({
|
|
199
|
+
authorizationEndpoint: discovery.authorization_endpoint,
|
|
200
|
+
redirectUri,
|
|
201
|
+
codeChallenge: pkce.challenge,
|
|
202
|
+
state,
|
|
203
|
+
nonce,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
url,
|
|
208
|
+
instructions: `Complete login in your browser for xAI Grok (SuperGrok). Docs: ${XAI_OAUTH_DOCS_URL}`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
|
|
213
|
+
const discovery = await xaiOAuthDiscovery();
|
|
214
|
+
const tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, "token_endpoint");
|
|
215
|
+
|
|
216
|
+
const body = new URLSearchParams({
|
|
217
|
+
grant_type: "authorization_code",
|
|
218
|
+
client_id: XAI_OAUTH_CLIENT_ID,
|
|
219
|
+
code,
|
|
220
|
+
redirect_uri: redirectUri,
|
|
221
|
+
code_verifier: this.#verifier,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const response = await fetch(tokenEndpoint, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
228
|
+
Accept: "application/json",
|
|
229
|
+
},
|
|
230
|
+
body,
|
|
231
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
let detail = "";
|
|
236
|
+
try {
|
|
237
|
+
detail = (await response.text()).trim();
|
|
238
|
+
} catch {
|
|
239
|
+
// Ignore body-read failures; the status code is the diagnostic.
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`xAI token exchange failed: ${response.status}${detail ? ` ${detail}` : ""}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let tokenData: { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
|
245
|
+
try {
|
|
246
|
+
tokenData = (await response.json()) as typeof tokenData;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`xAI token exchange returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (typeof tokenData.access_token !== "string" || !tokenData.access_token) {
|
|
254
|
+
throw new Error("xAI token exchange response missing access_token");
|
|
255
|
+
}
|
|
256
|
+
if (typeof tokenData.refresh_token !== "string" || !tokenData.refresh_token) {
|
|
257
|
+
throw new Error("xAI token exchange response missing refresh_token");
|
|
258
|
+
}
|
|
259
|
+
if (typeof tokenData.expires_in !== "number" || !Number.isFinite(tokenData.expires_in)) {
|
|
260
|
+
throw new Error("xAI token exchange response missing expires_in");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
access: tokenData.access_token,
|
|
265
|
+
refresh: tokenData.refresh_token,
|
|
266
|
+
expires: Date.now() + tokenData.expires_in * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Login with xAI Grok OAuth (SuperGrok Subscription).
|
|
273
|
+
*/
|
|
274
|
+
export async function loginXAIOAuth(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
275
|
+
return new XAIOAuthFlow(ctrl).login();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Refresh an xAI OAuth access token using a stored refresh_token.
|
|
280
|
+
*
|
|
281
|
+
* Hermes `refresh_xai_oauth_pure` L3087-3160. Re-runs OIDC discovery and
|
|
282
|
+
* re-validates the cached `token_endpoint` on the refresh hot path so a
|
|
283
|
+
* cached-but-poisoned endpoint cannot silently leak a refresh_token.
|
|
284
|
+
*/
|
|
285
|
+
export async function refreshXAIOAuthToken(refreshToken: string): Promise<OAuthCredentials> {
|
|
286
|
+
if (typeof refreshToken !== "string" || !refreshToken.trim()) {
|
|
287
|
+
throw new Error("missing refresh_token");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const discovery = await xaiOAuthDiscovery();
|
|
291
|
+
const tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, "token_endpoint");
|
|
292
|
+
|
|
293
|
+
const body = new URLSearchParams({
|
|
294
|
+
grant_type: "refresh_token",
|
|
295
|
+
client_id: XAI_OAUTH_CLIENT_ID,
|
|
296
|
+
refresh_token: refreshToken,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const response = await fetch(tokenEndpoint, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: {
|
|
302
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
303
|
+
Accept: "application/json",
|
|
304
|
+
},
|
|
305
|
+
body,
|
|
306
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
let detail = "";
|
|
311
|
+
try {
|
|
312
|
+
detail = (await response.text()).trim();
|
|
313
|
+
} catch {
|
|
314
|
+
// Ignore body-read failures; the status code is the diagnostic.
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`xAI token refresh failed: ${response.status}${detail ? ` ${detail}` : ""}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let data: { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
|
320
|
+
try {
|
|
321
|
+
data = (await response.json()) as typeof data;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`xAI token refresh returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (typeof data.access_token !== "string" || !data.access_token) {
|
|
329
|
+
throw new Error("xAI token refresh response missing access_token");
|
|
330
|
+
}
|
|
331
|
+
if (typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in)) {
|
|
332
|
+
throw new Error("xAI token refresh response missing expires_in");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const newRefresh = typeof data.refresh_token === "string" && data.refresh_token ? data.refresh_token : refreshToken;
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
access: data.access_token,
|
|
339
|
+
refresh: newRefresh,
|
|
340
|
+
expires: Date.now() + data.expires_in * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaomi MiMo login flow.
|
|
3
|
+
*
|
|
4
|
+
* Xiaomi MiMo provides OpenAI-compatible models via
|
|
5
|
+
* https://api.xiaomimimo.com/v1.
|
|
6
|
+
*
|
|
7
|
+
* This is not OAuth - it's a simple API key flow:
|
|
8
|
+
* 1. Open browser to Xiaomi MiMo API key console
|
|
9
|
+
* 2. User copies their API key
|
|
10
|
+
* 3. User pastes the API key into the CLI
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { OAuthController } from "./types";
|
|
14
|
+
|
|
15
|
+
const PROVIDER_ID = "xiaomi";
|
|
16
|
+
const PROVIDER_NAME = "Xiaomi MiMo";
|
|
17
|
+
const STANDARD_AUTH_URL = "https://platform.xiaomimimo.com/#/console/api-keys";
|
|
18
|
+
const STANDARD_API_BASE_URL = "https://api.xiaomimimo.com/v1";
|
|
19
|
+
const TOKEN_PLAN_SGP_API_BASE_URL = "https://token-plan-sgp.xiaomimimo.com/v1";
|
|
20
|
+
const TOKEN_PLAN_AMS_API_BASE_URL = "https://token-plan-ams.xiaomimimo.com/v1";
|
|
21
|
+
const TOKEN_PLAN_CN_API_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1";
|
|
22
|
+
const TOKEN_PLAN_KEY_PREFIX = "tp-";
|
|
23
|
+
const STANDARD_VALIDATION_MODEL = "mimo-v2-flash";
|
|
24
|
+
const TOKEN_PLAN_VALIDATION_MODEL = "mimo-v2.5";
|
|
25
|
+
|
|
26
|
+
function isTokenPlanKey(apiKey: string): boolean {
|
|
27
|
+
return apiKey.startsWith(TOKEN_PLAN_KEY_PREFIX);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const VALIDATION_TIMEOUT_MS = 15_000;
|
|
31
|
+
|
|
32
|
+
async function validateXiaomiApiKey(apiKey: string, signal?: AbortSignal): Promise<void> {
|
|
33
|
+
// For token-plan keys try SGP → AMS → CN in order until one succeeds.
|
|
34
|
+
// Standard sk- keys only hit the one endpoint.
|
|
35
|
+
const endpoints = isTokenPlanKey(apiKey)
|
|
36
|
+
? [
|
|
37
|
+
{ baseUrl: TOKEN_PLAN_SGP_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
38
|
+
{ baseUrl: TOKEN_PLAN_AMS_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
39
|
+
{ baseUrl: TOKEN_PLAN_CN_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
40
|
+
]
|
|
41
|
+
: [{ baseUrl: STANDARD_API_BASE_URL, model: STANDARD_VALIDATION_MODEL }];
|
|
42
|
+
|
|
43
|
+
let lastError: Error | null = null;
|
|
44
|
+
|
|
45
|
+
for (const ep of endpoints) {
|
|
46
|
+
// Fresh timeout per endpoint so SGP→AMS fallback works after a regional
|
|
47
|
+
// timeout: a shared AbortSignal.timeout would stay aborted and instantly
|
|
48
|
+
// abort the AMS fetch.
|
|
49
|
+
const timeoutSignal = AbortSignal.timeout(VALIDATION_TIMEOUT_MS);
|
|
50
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(`${ep.baseUrl}/chat/completions`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
model: ep.model,
|
|
60
|
+
max_tokens: 1,
|
|
61
|
+
messages: [{ role: "user", content: "ping" }],
|
|
62
|
+
}),
|
|
63
|
+
signal: requestSignal,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 401 means this endpoint didn't accept the key; try the next one
|
|
71
|
+
if (response.status === 401) {
|
|
72
|
+
let details = "";
|
|
73
|
+
try {
|
|
74
|
+
details = (await response.text()).trim();
|
|
75
|
+
} catch {
|
|
76
|
+
// ignore body parse errors, status is enough
|
|
77
|
+
}
|
|
78
|
+
lastError = new Error(
|
|
79
|
+
details
|
|
80
|
+
? `${PROVIDER_NAME} API key validation failed (${response.status}): ${details}`
|
|
81
|
+
: `${PROVIDER_NAME} API key validation failed (${response.status})`,
|
|
82
|
+
);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Non-auth errors are real failures
|
|
87
|
+
let details = "";
|
|
88
|
+
try {
|
|
89
|
+
details = (await response.text()).trim();
|
|
90
|
+
} catch {
|
|
91
|
+
// ignore body parse errors, status is enough
|
|
92
|
+
}
|
|
93
|
+
const message = details
|
|
94
|
+
? `${PROVIDER_NAME} API key validation failed (${response.status}): ${details}`
|
|
95
|
+
: `${PROVIDER_NAME} API key validation failed (${response.status})`;
|
|
96
|
+
throw new Error(message);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// Only re-throw AbortError when the caller explicitly cancelled.
|
|
99
|
+
// Timeout aborts (from AbortSignal.timeout) should fall through to
|
|
100
|
+
// the next endpoint so SGP→AMS fallback works during regional outages.
|
|
101
|
+
if (e instanceof DOMException && e.name === "AbortError" && signal?.aborted) {
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw lastError ?? new Error(`${PROVIDER_NAME} API key validation failed`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Login to Xiaomi MiMo.
|
|
112
|
+
*
|
|
113
|
+
* Opens browser to API keys page, prompts user to paste their API key.
|
|
114
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
115
|
+
*/
|
|
116
|
+
export async function loginXiaomi(options: OAuthController): Promise<string> {
|
|
117
|
+
if (!options.onPrompt) {
|
|
118
|
+
throw new Error(`${PROVIDER_NAME} login requires onPrompt callback`);
|
|
119
|
+
}
|
|
120
|
+
options.onAuth?.({
|
|
121
|
+
url: STANDARD_AUTH_URL,
|
|
122
|
+
instructions: "Copy your API key from the Xiaomi MiMo console",
|
|
123
|
+
});
|
|
124
|
+
const apiKey = await options.onPrompt({
|
|
125
|
+
message: "Paste your Xiaomi API key (sk-... or token-plan tp-...)",
|
|
126
|
+
placeholder: "sk-... or tp-...",
|
|
127
|
+
});
|
|
128
|
+
if (options.signal?.aborted) {
|
|
129
|
+
throw new Error("Login cancelled");
|
|
130
|
+
}
|
|
131
|
+
const trimmed = apiKey.trim();
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
throw new Error("API key is required");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
options.onProgress?.(`Validating ${PROVIDER_ID} API key...`);
|
|
137
|
+
await validateXiaomiApiKey(trimmed, options.signal);
|
|
138
|
+
return trimmed;
|
|
139
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z.AI login flow.
|
|
3
|
+
*
|
|
4
|
+
* Z.AI is a platform that provides access to GLM models through an OpenAI-compatible API.
|
|
5
|
+
* API docs: https://docs.z.ai/guides/overview/quick-start
|
|
6
|
+
*
|
|
7
|
+
* This is not OAuth - it's a simple API key flow:
|
|
8
|
+
* 1. User gets their API key from https://z.ai/settings/api-keys
|
|
9
|
+
* 2. User pastes the API key into the CLI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
|
|
13
|
+
import type { OAuthController } from "./types";
|
|
14
|
+
|
|
15
|
+
const AUTH_URL = "https://z.ai/manage-apikey/apikey-list";
|
|
16
|
+
const API_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
17
|
+
const VALIDATION_MODEL = "glm-4.7";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Login to Z.AI.
|
|
21
|
+
*
|
|
22
|
+
* Opens browser to API keys page, prompts user to paste their API key.
|
|
23
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
24
|
+
*/
|
|
25
|
+
export async function loginZai(options: OAuthController): Promise<string> {
|
|
26
|
+
if (!options.onPrompt) {
|
|
27
|
+
throw new Error("Z.AI login requires onPrompt callback");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Open browser to API keys page
|
|
31
|
+
options.onAuth?.({
|
|
32
|
+
url: AUTH_URL,
|
|
33
|
+
instructions: "Copy your API key from the dashboard",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Prompt user to paste their API key
|
|
37
|
+
const apiKey = await options.onPrompt({
|
|
38
|
+
message: "Paste your Z.AI API key",
|
|
39
|
+
placeholder: "sk-...",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (options.signal?.aborted) {
|
|
43
|
+
throw new Error("Login cancelled");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const trimmed = apiKey.trim();
|
|
47
|
+
if (!trimmed) {
|
|
48
|
+
throw new Error("API key is required");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
options.onProgress?.("Validating API key...");
|
|
52
|
+
await validateOpenAICompatibleApiKey({
|
|
53
|
+
provider: "Z.AI",
|
|
54
|
+
apiKey: trimmed,
|
|
55
|
+
baseUrl: API_BASE_URL,
|
|
56
|
+
model: VALIDATION_MODEL,
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
});
|
|
59
|
+
return trimmed;
|
|
60
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** ZenMux login flow (API key paste, validated via /models). */
|
|
2
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
3
|
+
|
|
4
|
+
export const loginZenMux = createApiKeyLogin({
|
|
5
|
+
providerLabel: "ZenMux",
|
|
6
|
+
authUrl: "https://zenmux.ai/settings/keys",
|
|
7
|
+
instructions: "Create or copy your ZenMux API key",
|
|
8
|
+
promptMessage: "Paste your ZenMux API key",
|
|
9
|
+
placeholder: "sk-...",
|
|
10
|
+
validation: {
|
|
11
|
+
kind: "models-endpoint",
|
|
12
|
+
provider: "ZenMux",
|
|
13
|
+
modelsUrl: "https://zenmux.ai/api/v1/models",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zhipu Coding Plan login flow.
|
|
3
|
+
*
|
|
4
|
+
* GLM Coding Plan provides an OpenAI-compatible API on the dedicated coding
|
|
5
|
+
* endpoint. API docs: https://docs.bigmodel.cn/cn/coding-plan/quick-start
|
|
6
|
+
*
|
|
7
|
+
* Simple API key flow:
|
|
8
|
+
* 1. User gets a Coding Plan API key from https://bigmodel.cn/coding-plan/personal/overview
|
|
9
|
+
* 2. User pastes the API key into the CLI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
|
|
13
|
+
import type { OAuthController } from "./types";
|
|
14
|
+
|
|
15
|
+
const AUTH_URL = "https://bigmodel.cn/coding-plan/personal/overview";
|
|
16
|
+
const API_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
|
17
|
+
const VALIDATION_MODEL = "glm-5.1";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Login to Zhipu Coding Plan.
|
|
21
|
+
*
|
|
22
|
+
* Opens browser to API keys page, prompts user to paste their API key.
|
|
23
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
24
|
+
*/
|
|
25
|
+
export async function loginZhipuCodingPlan(options: OAuthController): Promise<string> {
|
|
26
|
+
if (!options.onPrompt) {
|
|
27
|
+
throw new Error("Zhipu Coding Plan login requires onPrompt callback");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Open browser to API keys page
|
|
31
|
+
options.onAuth?.({
|
|
32
|
+
url: AUTH_URL,
|
|
33
|
+
instructions: "Copy your API key from the Coding Plan dashboard",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Prompt user to paste their API key
|
|
37
|
+
const apiKey = await options.onPrompt({
|
|
38
|
+
message: "Paste your Zhipu API key",
|
|
39
|
+
placeholder: "sk-...",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (options.signal?.aborted) {
|
|
43
|
+
throw new Error("Login cancelled");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const trimmed = apiKey.trim();
|
|
47
|
+
if (!trimmed) {
|
|
48
|
+
throw new Error("API key is required");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
options.onProgress?.("Validating API key...");
|
|
52
|
+
await validateOpenAICompatibleApiKey({
|
|
53
|
+
provider: "Zhipu",
|
|
54
|
+
apiKey: trimmed,
|
|
55
|
+
baseUrl: API_BASE_URL,
|
|
56
|
+
model: VALIDATION_MODEL,
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
});
|
|
59
|
+
return trimmed;
|
|
60
|
+
}
|