@aryee337/aery-ai 0.2.27 → 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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for OAuth flows with local callback servers.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Port allocation (tries expected port, falls back to random)
|
|
6
|
+
* - Callback server setup and request handling
|
|
7
|
+
* - Common OAuth flow logic
|
|
8
|
+
*
|
|
9
|
+
* Providers extend this and implement:
|
|
10
|
+
* - generateAuthUrl(): Build provider-specific authorization URL
|
|
11
|
+
* - exchangeToken(): Exchange authorization code for tokens
|
|
12
|
+
*/
|
|
13
|
+
import templateHtml from "./oauth.html" with { type: "text" };
|
|
14
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_TIMEOUT = 300_000;
|
|
17
|
+
const DEFAULT_HOSTNAME = "localhost";
|
|
18
|
+
const CALLBACK_PATH = "/callback";
|
|
19
|
+
|
|
20
|
+
export type CallbackResult = { code: string; state: string };
|
|
21
|
+
|
|
22
|
+
export interface OAuthCallbackFlowOptions {
|
|
23
|
+
preferredPort: number;
|
|
24
|
+
callbackPath?: string;
|
|
25
|
+
callbackHostname?: string;
|
|
26
|
+
/** Exact redirect URI advertised to the provider; disables port fallback. */
|
|
27
|
+
redirectUri?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Abstract base class for OAuth flows with local callback servers.
|
|
32
|
+
*/
|
|
33
|
+
export abstract class OAuthCallbackFlow {
|
|
34
|
+
ctrl: OAuthController;
|
|
35
|
+
preferredPort: number;
|
|
36
|
+
callbackPath: string;
|
|
37
|
+
callbackHostname: string;
|
|
38
|
+
redirectUri?: string;
|
|
39
|
+
#callbackResolve?: (result: CallbackResult) => void;
|
|
40
|
+
#callbackReject?: (error: string) => void;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
ctrl: OAuthController,
|
|
44
|
+
preferredPortOrOptions: number | OAuthCallbackFlowOptions,
|
|
45
|
+
callbackPath: string = CALLBACK_PATH,
|
|
46
|
+
) {
|
|
47
|
+
this.ctrl = ctrl;
|
|
48
|
+
if (typeof preferredPortOrOptions === "number") {
|
|
49
|
+
this.preferredPort = preferredPortOrOptions;
|
|
50
|
+
this.callbackPath = callbackPath;
|
|
51
|
+
this.callbackHostname = DEFAULT_HOSTNAME;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.preferredPort = preferredPortOrOptions.preferredPort;
|
|
56
|
+
this.callbackPath = preferredPortOrOptions.callbackPath ?? CALLBACK_PATH;
|
|
57
|
+
this.callbackHostname = preferredPortOrOptions.callbackHostname ?? DEFAULT_HOSTNAME;
|
|
58
|
+
this.redirectUri = preferredPortOrOptions.redirectUri;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate provider-specific authorization URL.
|
|
63
|
+
* @param state - CSRF state token
|
|
64
|
+
* @param redirectUri - The actual redirect URI to use (may differ from expected if port fallback occurred)
|
|
65
|
+
* @returns Authorization URL and optional instructions
|
|
66
|
+
*/
|
|
67
|
+
abstract generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Exchange authorization code for OAuth tokens.
|
|
71
|
+
* @param code - Authorization code from callback
|
|
72
|
+
* @param state - CSRF state token
|
|
73
|
+
* @param redirectUri - The actual redirect URI used (must match authorization request)
|
|
74
|
+
* @returns OAuth credentials
|
|
75
|
+
*/
|
|
76
|
+
abstract exchangeToken(code: string, state: string, redirectUri: string): Promise<OAuthCredentials>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate CSRF state token. Override if provider needs custom state generation.
|
|
80
|
+
*/
|
|
81
|
+
generateState(): string {
|
|
82
|
+
const bytes = new Uint8Array(16);
|
|
83
|
+
crypto.getRandomValues(bytes);
|
|
84
|
+
return Array.from(bytes)
|
|
85
|
+
.map(value => value.toString(16).padStart(2, "0"))
|
|
86
|
+
.join("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Execute the OAuth login flow.
|
|
91
|
+
*/
|
|
92
|
+
async login(): Promise<OAuthCredentials> {
|
|
93
|
+
const state = this.generateState();
|
|
94
|
+
|
|
95
|
+
// Start callback server first to get actual redirect URI
|
|
96
|
+
const { server, redirectUri } = await this.#startCallbackServer(state);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Generate auth URL with the ACTUAL redirect URI (may differ from expected if port was busy)
|
|
100
|
+
const { url: authUrl, instructions } = await this.generateAuthUrl(state, redirectUri);
|
|
101
|
+
|
|
102
|
+
// Notify controller that auth is ready
|
|
103
|
+
this.ctrl.onAuth?.({ url: authUrl, instructions });
|
|
104
|
+
this.ctrl.onProgress?.("Waiting for browser authentication...");
|
|
105
|
+
|
|
106
|
+
// Wait for callback or manual input
|
|
107
|
+
const { code } = await this.#waitForCallback(state);
|
|
108
|
+
|
|
109
|
+
this.ctrl.onProgress?.("Exchanging authorization code for tokens...");
|
|
110
|
+
|
|
111
|
+
return await this.exchangeToken(code, state, redirectUri);
|
|
112
|
+
} finally {
|
|
113
|
+
server.stop();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Start callback server, trying preferred port first, falling back to random.
|
|
119
|
+
*/
|
|
120
|
+
async #startCallbackServer(expectedState: string): Promise<{ server: Bun.Server<unknown>; redirectUri: string }> {
|
|
121
|
+
try {
|
|
122
|
+
const server = this.#createServer(this.preferredPort, expectedState);
|
|
123
|
+
if (this.redirectUri) {
|
|
124
|
+
return { server, redirectUri: this.redirectUri };
|
|
125
|
+
}
|
|
126
|
+
const redirectUri = `http://${this.callbackHostname}:${this.preferredPort}${this.callbackPath}`;
|
|
127
|
+
return { server, redirectUri };
|
|
128
|
+
} catch {
|
|
129
|
+
if (this.redirectUri) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`OAuth callback port ${this.preferredPort} unavailable; cannot fall back to a random port when oauth.redirectUri is set`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const server = this.#createServer(0, expectedState);
|
|
135
|
+
const actualPort = server.port;
|
|
136
|
+
const redirectUri = `http://${this.callbackHostname}:${actualPort}${this.callbackPath}`;
|
|
137
|
+
this.ctrl.onProgress?.(`Preferred port ${this.preferredPort} unavailable, using port ${actualPort}`);
|
|
138
|
+
return { server, redirectUri };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create HTTP server for OAuth callback.
|
|
144
|
+
*/
|
|
145
|
+
#createServer(port: number, expectedState: string): Bun.Server<unknown> {
|
|
146
|
+
return Bun.serve({
|
|
147
|
+
hostname: this.callbackHostname,
|
|
148
|
+
port,
|
|
149
|
+
reusePort: false,
|
|
150
|
+
fetch: req => this.#handleCallback(req, expectedState),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Handle OAuth callback HTTP request.
|
|
156
|
+
*/
|
|
157
|
+
#handleCallback(req: Request, expectedState: string): Response {
|
|
158
|
+
const url = new URL(req.url);
|
|
159
|
+
|
|
160
|
+
if (url.pathname !== this.callbackPath) {
|
|
161
|
+
return new Response("Not Found", { status: 404 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const code = url.searchParams.get("code");
|
|
165
|
+
const state = url.searchParams.get("state") || "";
|
|
166
|
+
const error = url.searchParams.get("error") || "";
|
|
167
|
+
const errorDescription = url.searchParams.get("error_description") || error;
|
|
168
|
+
|
|
169
|
+
type OkState = { ok: true; code: string; state: string };
|
|
170
|
+
type ErrorState = { ok?: false; error?: string };
|
|
171
|
+
let resultState: OkState | ErrorState;
|
|
172
|
+
|
|
173
|
+
if (error) {
|
|
174
|
+
resultState = { ok: false, error: `Authorization failed: ${errorDescription}` };
|
|
175
|
+
} else if (!code) {
|
|
176
|
+
resultState = { ok: false, error: "Missing authorization code" };
|
|
177
|
+
} else if (expectedState && state !== expectedState) {
|
|
178
|
+
resultState = { ok: false, error: "State mismatch - possible CSRF attack" };
|
|
179
|
+
} else {
|
|
180
|
+
resultState = { ok: true, code, state };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Signal to waitForCallback - capture refs before they could be cleared
|
|
184
|
+
const resolve = this.#callbackResolve;
|
|
185
|
+
const reject = this.#callbackReject;
|
|
186
|
+
queueMicrotask(() => {
|
|
187
|
+
if (resultState.ok) {
|
|
188
|
+
resolve?.({ code: resultState.code, state: resultState.state });
|
|
189
|
+
} else {
|
|
190
|
+
reject?.(resultState.error ?? "Unknown error");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return new Response(
|
|
195
|
+
(templateHtml as unknown as string).replaceAll("__OAUTH_STATE__", JSON.stringify(resultState)),
|
|
196
|
+
{
|
|
197
|
+
status: resultState.ok ? 200 : 500,
|
|
198
|
+
headers: { "Content-Type": "text/html" },
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Wait for OAuth callback or manual input (whichever comes first).
|
|
205
|
+
*/
|
|
206
|
+
#waitForCallback(expectedState: string): Promise<CallbackResult> {
|
|
207
|
+
const timeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT);
|
|
208
|
+
const signal = this.ctrl.signal ? AbortSignal.any([this.ctrl.signal, timeoutSignal]) : timeoutSignal;
|
|
209
|
+
|
|
210
|
+
const callbackPromise = new Promise<CallbackResult>((resolve, reject) => {
|
|
211
|
+
this.#callbackResolve = resolve;
|
|
212
|
+
this.#callbackReject = reject;
|
|
213
|
+
|
|
214
|
+
signal.addEventListener("abort", () => {
|
|
215
|
+
this.#callbackResolve = undefined;
|
|
216
|
+
this.#callbackReject = undefined;
|
|
217
|
+
reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Manual input race (if supported)
|
|
222
|
+
if (this.ctrl.onManualCodeInput) {
|
|
223
|
+
const requestManualInput = this.ctrl.onManualCodeInput;
|
|
224
|
+
const manualPromise = (async (): Promise<CallbackResult> => {
|
|
225
|
+
while (true) {
|
|
226
|
+
const result = await Promise.race([
|
|
227
|
+
callbackPromise,
|
|
228
|
+
requestManualInput()
|
|
229
|
+
.then((input): CallbackResult | null => {
|
|
230
|
+
const parsed = parseCallbackInput(input);
|
|
231
|
+
if (!parsed.code) return null;
|
|
232
|
+
if (expectedState && parsed.state && parsed.state !== expectedState) return null;
|
|
233
|
+
return { code: parsed.code, state: parsed.state ?? "" };
|
|
234
|
+
})
|
|
235
|
+
.catch((): CallbackResult | null => null),
|
|
236
|
+
]);
|
|
237
|
+
if (result) return result;
|
|
238
|
+
}
|
|
239
|
+
})();
|
|
240
|
+
|
|
241
|
+
return Promise.race([callbackPromise, manualPromise]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return callbackPromise;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Parse a redirect URL or code string to extract code and state.
|
|
250
|
+
*/
|
|
251
|
+
export function parseCallbackInput(input: string): { code?: string; state?: string } {
|
|
252
|
+
const value = input.trim();
|
|
253
|
+
if (!value) return {};
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const url = new URL(value);
|
|
257
|
+
return {
|
|
258
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
259
|
+
state: url.searchParams.get("state") ?? undefined,
|
|
260
|
+
};
|
|
261
|
+
} catch {
|
|
262
|
+
// Not a URL - check for query string format
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (value.includes("code=")) {
|
|
266
|
+
const params = new URLSearchParams(value.replace(/^[?#]/, ""));
|
|
267
|
+
return {
|
|
268
|
+
code: params.get("code") ?? undefined,
|
|
269
|
+
state: params.get("state") ?? undefined,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Assume raw code, possibly with state after #
|
|
274
|
+
const [code, state] = value.split("#", 2);
|
|
275
|
+
return { code, state };
|
|
276
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Cerebras login flow (API key paste against https://api.cerebras.ai/v1). */
|
|
2
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
3
|
+
|
|
4
|
+
export const loginCerebras = createApiKeyLogin({
|
|
5
|
+
providerLabel: "Cerebras",
|
|
6
|
+
authUrl: "https://cloud.cerebras.ai/platform/",
|
|
7
|
+
instructions: "Copy your API key from the Cerebras dashboard",
|
|
8
|
+
promptMessage: "Paste your Cerebras API key",
|
|
9
|
+
placeholder: "csk-...",
|
|
10
|
+
validation: {
|
|
11
|
+
kind: "chat-completions",
|
|
12
|
+
provider: "Cerebras",
|
|
13
|
+
baseUrl: "https://api.cerebras.ai/v1",
|
|
14
|
+
model: "gpt-oss-120b",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare AI Gateway login flow.
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare AI Gateway proxies upstream model providers.
|
|
5
|
+
*
|
|
6
|
+
* This is not OAuth - it's a simple API key flow:
|
|
7
|
+
* 1. Open Cloudflare AI Gateway docs/dashboard
|
|
8
|
+
* 2. User copies their Cloudflare AI Gateway token/API key
|
|
9
|
+
* 3. User pastes the API key into the CLI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { OAuthController } from "./types";
|
|
13
|
+
|
|
14
|
+
const AUTH_URL = "https://developers.cloudflare.com/ai-gateway/configuration/authentication/";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Login to Cloudflare AI Gateway.
|
|
18
|
+
*
|
|
19
|
+
* Opens browser to Cloudflare AI Gateway authentication docs and prompts for a gateway token/API key.
|
|
20
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
21
|
+
*/
|
|
22
|
+
export async function loginCloudflareAiGateway(options: OAuthController): Promise<string> {
|
|
23
|
+
if (!options.onPrompt) {
|
|
24
|
+
throw new Error("Cloudflare AI Gateway login requires onPrompt callback");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
options.onAuth?.({
|
|
28
|
+
url: AUTH_URL,
|
|
29
|
+
instructions:
|
|
30
|
+
"Copy your Cloudflare AI Gateway token/API key. Configure account/gateway base URL in models config.",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const apiKey = await options.onPrompt({
|
|
34
|
+
message: "Paste your Cloudflare AI Gateway token/API key",
|
|
35
|
+
placeholder: "cf-aig-...",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (options.signal?.aborted) {
|
|
39
|
+
throw new Error("Login cancelled");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const trimmed = apiKey.trim();
|
|
43
|
+
if (!trimmed) {
|
|
44
|
+
throw new Error("API key is required");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { generatePKCE } from "./pkce";
|
|
2
|
+
import type { OAuthCredentials } from "./types";
|
|
3
|
+
|
|
4
|
+
const CURSOR_LOGIN_URL = "https://cursor.com/loginDeepControl";
|
|
5
|
+
const CURSOR_POLL_URL = "https://api2.cursor.sh/auth/poll";
|
|
6
|
+
const CURSOR_REFRESH_URL = "https://api2.cursor.sh/auth/exchange_user_api_key";
|
|
7
|
+
|
|
8
|
+
const POLL_MAX_ATTEMPTS = 150;
|
|
9
|
+
const POLL_BASE_DELAY = 1000;
|
|
10
|
+
const POLL_MAX_DELAY = 10000;
|
|
11
|
+
const POLL_BACKOFF_MULTIPLIER = 1.2;
|
|
12
|
+
|
|
13
|
+
export interface CursorAuthParams {
|
|
14
|
+
verifier: string;
|
|
15
|
+
challenge: string;
|
|
16
|
+
uuid: string;
|
|
17
|
+
loginUrl: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function generateCursorAuthParams(): Promise<CursorAuthParams> {
|
|
21
|
+
const { verifier, challenge } = await generatePKCE();
|
|
22
|
+
const uuid = crypto.randomUUID();
|
|
23
|
+
|
|
24
|
+
const params = new URLSearchParams({
|
|
25
|
+
challenge,
|
|
26
|
+
uuid,
|
|
27
|
+
mode: "login",
|
|
28
|
+
redirectTarget: "cli",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const loginUrl = `${CURSOR_LOGIN_URL}?${params.toString()}`;
|
|
32
|
+
|
|
33
|
+
return { verifier, challenge, uuid, loginUrl };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function pollCursorAuth(
|
|
37
|
+
uuid: string,
|
|
38
|
+
verifier: string,
|
|
39
|
+
): Promise<{ accessToken: string; refreshToken: string }> {
|
|
40
|
+
let delay = POLL_BASE_DELAY;
|
|
41
|
+
let consecutiveErrors = 0;
|
|
42
|
+
|
|
43
|
+
for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {
|
|
44
|
+
await Bun.sleep(delay);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`${CURSOR_POLL_URL}?uuid=${uuid}&verifier=${verifier}`);
|
|
48
|
+
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
consecutiveErrors = 0;
|
|
51
|
+
delay = Math.min(delay * POLL_BACKOFF_MULTIPLIER, POLL_MAX_DELAY);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (response.ok) {
|
|
56
|
+
const data = (await response.json()) as {
|
|
57
|
+
accessToken: string;
|
|
58
|
+
refreshToken: string;
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
accessToken: data.accessToken,
|
|
62
|
+
refreshToken: data.refreshToken,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw new Error(`Poll failed: ${response.status}`);
|
|
67
|
+
} catch {
|
|
68
|
+
consecutiveErrors++;
|
|
69
|
+
if (consecutiveErrors >= 3) {
|
|
70
|
+
throw new Error("Too many consecutive errors during Cursor auth polling");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error("Cursor authentication polling timeout");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function loginCursor(
|
|
79
|
+
onAuthUrl: (url: string) => void,
|
|
80
|
+
onPollStart?: () => void,
|
|
81
|
+
): Promise<OAuthCredentials> {
|
|
82
|
+
const { verifier, uuid, loginUrl } = await generateCursorAuthParams();
|
|
83
|
+
|
|
84
|
+
onAuthUrl(loginUrl);
|
|
85
|
+
onPollStart?.();
|
|
86
|
+
|
|
87
|
+
const { accessToken, refreshToken } = await pollCursorAuth(uuid, verifier);
|
|
88
|
+
|
|
89
|
+
const expiresAt = getTokenExpiry(accessToken);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
access: accessToken,
|
|
93
|
+
refresh: refreshToken,
|
|
94
|
+
expires: expiresAt,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function refreshCursorToken(apiKeyOrRefreshToken: string): Promise<OAuthCredentials> {
|
|
99
|
+
const response = await fetch(CURSOR_REFRESH_URL, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${apiKeyOrRefreshToken}`,
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
},
|
|
105
|
+
body: "{}",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const error = await response.text();
|
|
110
|
+
throw new Error(`Cursor token refresh failed: ${error}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const data = (await response.json()) as {
|
|
114
|
+
accessToken: string;
|
|
115
|
+
refreshToken: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const expiresAt = getTokenExpiry(data.accessToken);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
access: data.accessToken,
|
|
122
|
+
refresh: data.refreshToken || apiKeyOrRefreshToken,
|
|
123
|
+
expires: expiresAt,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getTokenExpiry(token: string): number {
|
|
128
|
+
try {
|
|
129
|
+
const parts = token.split(".");
|
|
130
|
+
if (parts.length !== 3) {
|
|
131
|
+
return Date.now() + 3600 * 1000;
|
|
132
|
+
}
|
|
133
|
+
const payload = parts[1];
|
|
134
|
+
if (!payload) {
|
|
135
|
+
return Date.now() + 3600 * 1000;
|
|
136
|
+
}
|
|
137
|
+
const decoded = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
138
|
+
if (decoded && typeof decoded === "object" && typeof decoded.exp === "number") {
|
|
139
|
+
return decoded.exp * 1000 - 5 * 60 * 1000;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Ignore parsing errors
|
|
143
|
+
}
|
|
144
|
+
return Date.now() + 3600 * 1000;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function isCursorTokenExpiringSoon(token: string, thresholdSeconds = 300): boolean {
|
|
148
|
+
try {
|
|
149
|
+
const [, payload] = token.split(".");
|
|
150
|
+
if (!payload) return true;
|
|
151
|
+
const decoded = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
152
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
153
|
+
return decoded.exp - currentTime < thresholdSeconds;
|
|
154
|
+
} catch {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek login flow (API key paste against https://api.deepseek.com).
|
|
3
|
+
*
|
|
4
|
+
* Validation hits `GET /v1/models` so it authenticates the key without
|
|
5
|
+
* depending on a specific model being enabled on the account. The previous
|
|
6
|
+
* implementation issued a chat-completion against `deepseek-v4-pro`, which
|
|
7
|
+
* 404s for accounts without that preview model even when the key is valid.
|
|
8
|
+
*/
|
|
9
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
10
|
+
import type { OAuthController, OAuthPrompt } from "./types";
|
|
11
|
+
|
|
12
|
+
const innerLogin = createApiKeyLogin({
|
|
13
|
+
providerLabel: "DeepSeek",
|
|
14
|
+
authUrl: "https://platform.deepseek.com/api_keys",
|
|
15
|
+
instructions: "Create or copy your API key from the DeepSeek dashboard",
|
|
16
|
+
promptMessage: "Paste your DeepSeek API key",
|
|
17
|
+
placeholder: "sk-...",
|
|
18
|
+
validation: {
|
|
19
|
+
kind: "models-endpoint",
|
|
20
|
+
provider: "deepseek",
|
|
21
|
+
modelsUrl: "https://api.deepseek.com/v1/models",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a pasted DeepSeek API key.
|
|
27
|
+
*
|
|
28
|
+
* Users frequently copy keys out of `curl` snippets that include the
|
|
29
|
+
* `Authorization: Bearer …` prefix. Strip it so validation does not fail
|
|
30
|
+
* with a confusing 401, and reject obviously empty input early.
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeDeepSeekApiKey(raw: string): string {
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
if (!trimmed) {
|
|
35
|
+
return trimmed; // let the shared factory throw the canonical "API key is required"
|
|
36
|
+
}
|
|
37
|
+
const stripped = trimmed.replace(/^bearer\b\s*/i, "");
|
|
38
|
+
if (!stripped) {
|
|
39
|
+
throw new Error("DeepSeek API key is empty after stripping Bearer prefix");
|
|
40
|
+
}
|
|
41
|
+
return stripped;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const loginDeepSeek = async (options: OAuthController): Promise<string> => {
|
|
45
|
+
const userOnPrompt = options.onPrompt;
|
|
46
|
+
const wrapped: OAuthController = userOnPrompt
|
|
47
|
+
? {
|
|
48
|
+
...options,
|
|
49
|
+
onPrompt: async (prompt: OAuthPrompt) => normalizeDeepSeekApiKey(await userOnPrompt(prompt)),
|
|
50
|
+
}
|
|
51
|
+
: options;
|
|
52
|
+
return innerLogin(wrapped);
|
|
53
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fire Pass login flow.
|
|
3
|
+
*
|
|
4
|
+
* Fire Pass is a Fireworks subscription product whose dedicated `fpk_…` API
|
|
5
|
+
* keys are scoped to the `accounts/fireworks/routers/kimi-k2p6-turbo` router
|
|
6
|
+
* (Kimi K2.6 Turbo). The key does NOT authorize `/v1/models`, so validation
|
|
7
|
+
* pings the chat completions endpoint with the router id directly.
|
|
8
|
+
* See https://docs.fireworks.ai/firepass.
|
|
9
|
+
*/
|
|
10
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
11
|
+
|
|
12
|
+
export const loginFirepass = createApiKeyLogin({
|
|
13
|
+
providerLabel: "Fire Pass",
|
|
14
|
+
authUrl: "https://app.fireworks.ai/settings/users/api-keys",
|
|
15
|
+
instructions: "Create a dedicated Fire Pass API key in the Fireworks dashboard",
|
|
16
|
+
promptMessage: "Paste your Fire Pass API key",
|
|
17
|
+
placeholder: "fpk_...",
|
|
18
|
+
validation: {
|
|
19
|
+
kind: "chat-completions",
|
|
20
|
+
provider: "Fire Pass",
|
|
21
|
+
baseUrl: "https://api.fireworks.ai/inference/v1",
|
|
22
|
+
model: "accounts/fireworks/routers/kimi-k2p6-turbo",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Fireworks login flow (API key paste against https://api.fireworks.ai/inference/v1). */
|
|
2
|
+
import { createApiKeyLogin } from "./api-key-login";
|
|
3
|
+
|
|
4
|
+
export const loginFireworks = createApiKeyLogin({
|
|
5
|
+
providerLabel: "Fireworks",
|
|
6
|
+
authUrl: "https://app.fireworks.ai/settings/users/api-keys",
|
|
7
|
+
instructions: "Create or copy your Fireworks API key",
|
|
8
|
+
promptMessage: "Paste your Fireworks API key",
|
|
9
|
+
placeholder: "fw_...",
|
|
10
|
+
validation: {
|
|
11
|
+
kind: "models-endpoint",
|
|
12
|
+
provider: "Fireworks",
|
|
13
|
+
modelsUrl: "https://api.fireworks.ai/inference/v1/models",
|
|
14
|
+
},
|
|
15
|
+
});
|