@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,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot OAuth flow (opencode OAuth app)
|
|
3
|
+
*/
|
|
4
|
+
import { scheduler } from "node:timers/promises";
|
|
5
|
+
import { getBundledModels } from "../../models";
|
|
6
|
+
import type { OAuthCredentials } from "./types";
|
|
7
|
+
|
|
8
|
+
const CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
9
|
+
|
|
10
|
+
export const COPILOT_USER_AGENT = "opencode/1.3.15" as const;
|
|
11
|
+
|
|
12
|
+
export const OPENCODE_HEADERS = {
|
|
13
|
+
"User-Agent": COPILOT_USER_AGENT,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
|
|
17
|
+
const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
|
|
18
|
+
type DeviceCodeResponse = {
|
|
19
|
+
device_code: string;
|
|
20
|
+
user_code: string;
|
|
21
|
+
verification_uri: string;
|
|
22
|
+
interval: number;
|
|
23
|
+
expires_in: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type DeviceTokenSuccessResponse = {
|
|
27
|
+
access_token: string;
|
|
28
|
+
token_type?: string;
|
|
29
|
+
scope?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type DeviceTokenErrorResponse = {
|
|
33
|
+
error: string;
|
|
34
|
+
error_description?: string;
|
|
35
|
+
interval?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type GitHubCopilotApiKeyPayload = {
|
|
39
|
+
token?: unknown;
|
|
40
|
+
enterpriseUrl?: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ParsedGitHubCopilotApiKey = {
|
|
44
|
+
accessToken: string;
|
|
45
|
+
enterpriseUrl?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const PUBLIC_GITHUB_HOSTS = new Set(["api.github.com", "github.com", "www.github.com"]);
|
|
49
|
+
|
|
50
|
+
function isPublicGitHubHost(host: string): boolean {
|
|
51
|
+
return PUBLIC_GITHUB_HOSTS.has(host.trim().toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined): string | undefined {
|
|
55
|
+
const trimmed = input?.trim();
|
|
56
|
+
if (!trimmed) return undefined;
|
|
57
|
+
const normalized = normalizeDomain(trimmed) ?? trimmed.toLowerCase();
|
|
58
|
+
if (!normalized || isPublicGitHubHost(normalized)) return undefined;
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilotApiKey {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(apiKeyRaw) as GitHubCopilotApiKeyPayload;
|
|
65
|
+
if (typeof parsed.token === "string") {
|
|
66
|
+
return {
|
|
67
|
+
accessToken: parsed.token,
|
|
68
|
+
enterpriseUrl:
|
|
69
|
+
typeof parsed.enterpriseUrl === "string"
|
|
70
|
+
? normalizeGitHubCopilotEnterpriseDomain(parsed.enterpriseUrl)
|
|
71
|
+
: undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
|
|
76
|
+
return { accessToken: apiKeyRaw };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function normalizeDomain(input: string): string | null {
|
|
80
|
+
const trimmed = input.trim();
|
|
81
|
+
if (!trimmed) return null;
|
|
82
|
+
try {
|
|
83
|
+
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
84
|
+
return url.hostname;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getUrls(domain: string): {
|
|
91
|
+
deviceCodeUrl: string;
|
|
92
|
+
accessTokenUrl: string;
|
|
93
|
+
} {
|
|
94
|
+
return {
|
|
95
|
+
deviceCodeUrl: `https://${domain}/login/device/code`,
|
|
96
|
+
accessTokenUrl: `https://${domain}/login/oauth/access_token`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getGitHubCopilotBaseUrl(enterpriseDomain?: string): string {
|
|
101
|
+
const normalizedEnterpriseDomain = normalizeGitHubCopilotEnterpriseDomain(enterpriseDomain);
|
|
102
|
+
if (!normalizedEnterpriseDomain) return "https://api.githubcopilot.com";
|
|
103
|
+
const host = normalizedEnterpriseDomain.startsWith("copilot-api.")
|
|
104
|
+
? normalizedEnterpriseDomain
|
|
105
|
+
: `copilot-api.${normalizedEnterpriseDomain}`;
|
|
106
|
+
return `https://${host}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function fetchJson(url: string, init: RequestInit): Promise<unknown> {
|
|
110
|
+
const response = await fetch(url, init);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const text = await response.text();
|
|
113
|
+
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
114
|
+
}
|
|
115
|
+
return response.json();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {
|
|
119
|
+
const urls = getUrls(domain);
|
|
120
|
+
const data = await fetchJson(urls.deviceCodeUrl, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
Accept: "application/json",
|
|
124
|
+
"Content-Type": "application/json",
|
|
125
|
+
...OPENCODE_HEADERS,
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
client_id: CLIENT_ID,
|
|
129
|
+
scope: "read:user",
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!data || typeof data !== "object") {
|
|
134
|
+
throw new Error("Invalid device code response");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const deviceCode = (data as Record<string, unknown>).device_code;
|
|
138
|
+
const userCode = (data as Record<string, unknown>).user_code;
|
|
139
|
+
const verificationUri = (data as Record<string, unknown>).verification_uri;
|
|
140
|
+
const interval = (data as Record<string, unknown>).interval;
|
|
141
|
+
const expiresIn = (data as Record<string, unknown>).expires_in;
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
typeof deviceCode !== "string" ||
|
|
145
|
+
typeof userCode !== "string" ||
|
|
146
|
+
typeof verificationUri !== "string" ||
|
|
147
|
+
typeof interval !== "number" ||
|
|
148
|
+
typeof expiresIn !== "number"
|
|
149
|
+
) {
|
|
150
|
+
throw new Error("Invalid device code response fields");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
device_code: deviceCode,
|
|
155
|
+
user_code: userCode,
|
|
156
|
+
verification_uri: verificationUri,
|
|
157
|
+
interval,
|
|
158
|
+
expires_in: expiresIn,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function pollForGitHubAccessToken(
|
|
163
|
+
domain: string,
|
|
164
|
+
deviceCode: string,
|
|
165
|
+
intervalSeconds: number,
|
|
166
|
+
expiresIn: number,
|
|
167
|
+
signal?: AbortSignal,
|
|
168
|
+
pollIntervalFloorMs = 1000,
|
|
169
|
+
pollIntervalScaleMs = 1000,
|
|
170
|
+
) {
|
|
171
|
+
const urls = getUrls(domain);
|
|
172
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
173
|
+
let intervalMs = Math.max(pollIntervalFloorMs, Math.floor(intervalSeconds * pollIntervalScaleMs));
|
|
174
|
+
let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;
|
|
175
|
+
let slowDownResponses = 0;
|
|
176
|
+
|
|
177
|
+
while (Date.now() < deadline) {
|
|
178
|
+
if (signal?.aborted) {
|
|
179
|
+
throw new Error("Login cancelled");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const remainingMs = deadline - Date.now();
|
|
183
|
+
const waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);
|
|
184
|
+
try {
|
|
185
|
+
await scheduler.wait(waitMs, { signal });
|
|
186
|
+
} catch {
|
|
187
|
+
throw new Error("Login cancelled");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const raw = await fetchJson(urls.accessTokenUrl, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
Accept: "application/json",
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
...OPENCODE_HEADERS,
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
client_id: CLIENT_ID,
|
|
199
|
+
device_code: deviceCode,
|
|
200
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (raw && typeof raw === "object" && typeof (raw as DeviceTokenSuccessResponse).access_token === "string") {
|
|
205
|
+
return (raw as DeviceTokenSuccessResponse).access_token;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (raw && typeof raw === "object" && typeof (raw as DeviceTokenErrorResponse).error === "string") {
|
|
209
|
+
const { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;
|
|
210
|
+
if (error === "authorization_pending") {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (error === "slow_down") {
|
|
215
|
+
slowDownResponses += 1;
|
|
216
|
+
intervalMs =
|
|
217
|
+
typeof interval === "number" && interval > 0
|
|
218
|
+
? Math.max(pollIntervalFloorMs, interval * pollIntervalScaleMs)
|
|
219
|
+
: Math.max(pollIntervalFloorMs, intervalMs + 5 * pollIntervalScaleMs);
|
|
220
|
+
intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
225
|
+
throw new Error(`Device flow failed: ${error}${descriptionSuffix}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (slowDownResponses > 0) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error("Device flow timed out");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Far-future expiry (10 years). GitHub OAuth tokens are long-lived; no JWT exchange needed. */
|
|
239
|
+
const FAR_FUTURE_MS = Date.now() + 10 * 365.25 * 24 * 60 * 60 * 1000;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Refresh GitHub Copilot token.
|
|
243
|
+
* With the opencode OAuth flow, the GitHub token is used directly — no JWT exchange needed.
|
|
244
|
+
*/
|
|
245
|
+
export function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string): OAuthCredentials {
|
|
246
|
+
return {
|
|
247
|
+
refresh: refreshToken,
|
|
248
|
+
access: refreshToken,
|
|
249
|
+
expires: FAR_FUTURE_MS,
|
|
250
|
+
enterpriseUrl: enterpriseDomain,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Enable a model for the user's GitHub Copilot account.
|
|
256
|
+
* This is required for some models (like Claude, Grok) before they can be used.
|
|
257
|
+
*/
|
|
258
|
+
async function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {
|
|
259
|
+
const baseUrl = getGitHubCopilotBaseUrl(enterpriseDomain);
|
|
260
|
+
const url = `${baseUrl}/models/${modelId}/policy`;
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(url, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json",
|
|
267
|
+
Authorization: `Bearer ${token}`,
|
|
268
|
+
...OPENCODE_HEADERS,
|
|
269
|
+
"openai-intent": "chat-policy",
|
|
270
|
+
"x-interaction-type": "chat-policy",
|
|
271
|
+
},
|
|
272
|
+
body: JSON.stringify({ state: "enabled" }),
|
|
273
|
+
});
|
|
274
|
+
return response.ok;
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
282
|
+
* Called after successful login to ensure all models are available.
|
|
283
|
+
*/
|
|
284
|
+
async function enableAllGitHubCopilotModels(
|
|
285
|
+
token: string,
|
|
286
|
+
enterpriseDomain?: string,
|
|
287
|
+
onProgress?: (model: string, success: boolean) => void,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
const models = getBundledModels("github-copilot");
|
|
290
|
+
const BATCH_SIZE = 5;
|
|
291
|
+
for (let i = 0; i < models.length; i += BATCH_SIZE) {
|
|
292
|
+
const batch = models.slice(i, i + BATCH_SIZE);
|
|
293
|
+
await Promise.all(
|
|
294
|
+
batch.map(async model => {
|
|
295
|
+
const success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
|
|
296
|
+
onProgress?.(model.id, success);
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Login with GitHub Copilot OAuth (device code flow)
|
|
304
|
+
*
|
|
305
|
+
* @param options.onAuth - Callback with URL and optional instructions (user code)
|
|
306
|
+
* @param options.onPrompt - Callback to prompt user for input
|
|
307
|
+
* @param options.onProgress - Optional progress callback
|
|
308
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
309
|
+
*/
|
|
310
|
+
export async function loginGitHubCopilot(options: {
|
|
311
|
+
onAuth: (url: string, instructions?: string) => void;
|
|
312
|
+
onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;
|
|
313
|
+
onProgress?: (message: string) => void;
|
|
314
|
+
signal?: AbortSignal;
|
|
315
|
+
pollIntervalFloorMs?: number;
|
|
316
|
+
pollIntervalScaleMs?: number;
|
|
317
|
+
}): Promise<OAuthCredentials> {
|
|
318
|
+
const input = await options.onPrompt({
|
|
319
|
+
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
320
|
+
placeholder: "company.ghe.com",
|
|
321
|
+
allowEmpty: true,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (options.signal?.aborted) {
|
|
325
|
+
throw new Error("Login cancelled");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const trimmed = input.trim();
|
|
329
|
+
const normalizedDomain = normalizeDomain(input);
|
|
330
|
+
if (trimmed && !normalizedDomain) {
|
|
331
|
+
throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
332
|
+
}
|
|
333
|
+
const enterpriseDomain = normalizeGitHubCopilotEnterpriseDomain(normalizedDomain ?? undefined);
|
|
334
|
+
const domain =
|
|
335
|
+
normalizedDomain && isPublicGitHubHost(normalizedDomain) ? "github.com" : (normalizedDomain ?? "github.com");
|
|
336
|
+
|
|
337
|
+
const device = await startDeviceFlow(domain);
|
|
338
|
+
options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
|
|
339
|
+
|
|
340
|
+
const githubAccessToken = await pollForGitHubAccessToken(
|
|
341
|
+
domain,
|
|
342
|
+
device.device_code,
|
|
343
|
+
device.interval,
|
|
344
|
+
device.expires_in,
|
|
345
|
+
options.signal,
|
|
346
|
+
options.pollIntervalFloorMs,
|
|
347
|
+
options.pollIntervalScaleMs,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// With opencode OAuth, the GitHub token is used directly for all API requests
|
|
351
|
+
const credentials: OAuthCredentials = {
|
|
352
|
+
refresh: githubAccessToken,
|
|
353
|
+
access: githubAccessToken,
|
|
354
|
+
expires: FAR_FUTURE_MS,
|
|
355
|
+
enterpriseUrl: enterpriseDomain ?? undefined,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Enable all models after successful login
|
|
359
|
+
options.onProgress?.("Enabling models...");
|
|
360
|
+
await enableAllGitHubCopilotModels(githubAccessToken, enterpriseDomain ?? undefined);
|
|
361
|
+
return credentials;
|
|
362
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { clearGitLabDuoDirectAccessCache } from "../../providers/gitlab-duo";
|
|
2
|
+
import { OAuthCallbackFlow } from "./callback-server";
|
|
3
|
+
import { generatePKCE } from "./pkce";
|
|
4
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "./types";
|
|
5
|
+
|
|
6
|
+
const GITLAB_COM_URL = "https://gitlab.com";
|
|
7
|
+
const BUNDLED_CLIENT_ID = "da4edff2e6ebd2bc3208611e2768bc1c1dd7be791dc5ff26ca34ca9ee44f7d4b";
|
|
8
|
+
const OAUTH_SCOPES = ["api"];
|
|
9
|
+
const CALLBACK_PORT = 8080;
|
|
10
|
+
const CALLBACK_PATH = "/callback";
|
|
11
|
+
|
|
12
|
+
interface PKCEPair {
|
|
13
|
+
verifier: string;
|
|
14
|
+
challenge: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function mapTokenResponse(payload: {
|
|
18
|
+
access_token?: string;
|
|
19
|
+
refresh_token?: string;
|
|
20
|
+
expires_in?: number;
|
|
21
|
+
created_at?: number;
|
|
22
|
+
}): OAuthCredentials {
|
|
23
|
+
if (!payload.access_token || !payload.refresh_token || typeof payload.expires_in !== "number") {
|
|
24
|
+
throw new Error("GitLab OAuth token response missing required fields");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const createdAtMs =
|
|
28
|
+
typeof payload.created_at === "number" && Number.isFinite(payload.created_at)
|
|
29
|
+
? payload.created_at * 1000
|
|
30
|
+
: Date.now();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
access: payload.access_token,
|
|
34
|
+
refresh: payload.refresh_token,
|
|
35
|
+
expires: createdAtMs + payload.expires_in * 1000 - 5 * 60 * 1000,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class GitLabDuoOAuthFlow extends OAuthCallbackFlow {
|
|
40
|
+
#pkce: PKCEPair;
|
|
41
|
+
|
|
42
|
+
constructor(ctrl: OAuthLoginCallbacks, pkce: PKCEPair) {
|
|
43
|
+
super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
|
|
44
|
+
this.#pkce = pkce;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
48
|
+
const authParams = new URLSearchParams({
|
|
49
|
+
client_id: BUNDLED_CLIENT_ID,
|
|
50
|
+
redirect_uri: redirectUri,
|
|
51
|
+
response_type: "code",
|
|
52
|
+
scope: OAUTH_SCOPES.join(" "),
|
|
53
|
+
code_challenge: this.#pkce.challenge,
|
|
54
|
+
code_challenge_method: "S256",
|
|
55
|
+
state,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
url: `${GITLAB_COM_URL}/oauth/authorize?${authParams.toString()}`,
|
|
60
|
+
instructions: "Complete GitLab login in browser. Authentication will finish automatically.",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
|
|
65
|
+
const response = await fetch(`${GITLAB_COM_URL}/oauth/token`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
68
|
+
body: new URLSearchParams({
|
|
69
|
+
client_id: BUNDLED_CLIENT_ID,
|
|
70
|
+
grant_type: "authorization_code",
|
|
71
|
+
code,
|
|
72
|
+
code_verifier: this.#pkce.verifier,
|
|
73
|
+
redirect_uri: redirectUri,
|
|
74
|
+
}).toString(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(`GitLab OAuth token exchange failed: ${response.status} ${await response.text()}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
clearGitLabDuoDirectAccessCache();
|
|
82
|
+
return mapTokenResponse(
|
|
83
|
+
(await response.json()) as {
|
|
84
|
+
access_token?: string;
|
|
85
|
+
refresh_token?: string;
|
|
86
|
+
expires_in?: number;
|
|
87
|
+
created_at?: number;
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function loginGitLabDuo(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
94
|
+
const pkce = await generatePKCE();
|
|
95
|
+
const flow = new GitLabDuoOAuthFlow(callbacks, pkce);
|
|
96
|
+
return flow.login();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function refreshGitLabDuoToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
100
|
+
const response = await fetch(`${GITLAB_COM_URL}/oauth/token`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
103
|
+
body: new URLSearchParams({
|
|
104
|
+
client_id: BUNDLED_CLIENT_ID,
|
|
105
|
+
grant_type: "refresh_token",
|
|
106
|
+
refresh_token: credentials.refresh,
|
|
107
|
+
}).toString(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`GitLab OAuth refresh failed: ${response.status} ${await response.text()}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
clearGitLabDuoDirectAccessCache();
|
|
115
|
+
return mapTokenResponse(
|
|
116
|
+
(await response.json()) as {
|
|
117
|
+
access_token?: string;
|
|
118
|
+
refresh_token?: string;
|
|
119
|
+
expires_in?: number;
|
|
120
|
+
created_at?: number;
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
3
|
+
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
|
|
4
|
+
*/
|
|
5
|
+
import { getAntigravityUserAgent } from "../../providers/google-gemini-headers";
|
|
6
|
+
import { runGoogleOAuthLogin } from "./google-oauth-shared";
|
|
7
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
8
|
+
|
|
9
|
+
const decode = (s: string) => atob(s);
|
|
10
|
+
const CLIENT_ID = decode(
|
|
11
|
+
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
|
|
12
|
+
);
|
|
13
|
+
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
|
|
14
|
+
const CALLBACK_PORT = 51121;
|
|
15
|
+
const CALLBACK_PATH = "/oauth-callback";
|
|
16
|
+
|
|
17
|
+
const SCOPES = [
|
|
18
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
19
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
20
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
21
|
+
"https://www.googleapis.com/auth/cclog",
|
|
22
|
+
"https://www.googleapis.com/auth/experimentsandconfigs",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
26
|
+
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
27
|
+
const CLOUD_CODE_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
28
|
+
const TIER_LEGACY = "legacy-tier";
|
|
29
|
+
const PROJECT_ONBOARD_MAX_ATTEMPTS = 5;
|
|
30
|
+
const PROJECT_ONBOARD_INTERVAL_MS = 2000;
|
|
31
|
+
|
|
32
|
+
interface LoadCodeAssistPayload {
|
|
33
|
+
cloudaicompanionProject?: string | { id?: string };
|
|
34
|
+
currentTier?: { id?: string };
|
|
35
|
+
allowedTiers?: Array<{ id?: string; isDefault?: boolean }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface LongRunningOperationResponse {
|
|
39
|
+
done?: boolean;
|
|
40
|
+
response?: {
|
|
41
|
+
cloudaicompanionProject?: string | { id?: string };
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA = Object.freeze({
|
|
46
|
+
ideType: "ANTIGRAVITY",
|
|
47
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
48
|
+
pluginType: "GEMINI",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function readProjectId(value: string | { id?: string } | undefined): string | undefined {
|
|
52
|
+
if (typeof value === "string" && value.length > 0) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
if (value && typeof value === "object" && typeof value.id === "string" && value.id.length > 0) {
|
|
56
|
+
return value.id;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string {
|
|
62
|
+
if (!allowedTiers || allowedTiers.length === 0) {
|
|
63
|
+
return TIER_LEGACY;
|
|
64
|
+
}
|
|
65
|
+
const defaultTier = allowedTiers.find(tier => tier.isDefault && typeof tier.id === "string" && tier.id.length > 0);
|
|
66
|
+
if (defaultTier?.id) {
|
|
67
|
+
return defaultTier.id;
|
|
68
|
+
}
|
|
69
|
+
return TIER_LEGACY;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function onboardProjectWithRetries(
|
|
73
|
+
endpoint: string,
|
|
74
|
+
headers: Record<string, string>,
|
|
75
|
+
onboardBody: { tierId: string; metadata: typeof ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA },
|
|
76
|
+
onProgress?: (message: string) => void,
|
|
77
|
+
): Promise<string> {
|
|
78
|
+
for (let attempt = 1; attempt <= PROJECT_ONBOARD_MAX_ATTEMPTS; attempt += 1) {
|
|
79
|
+
if (attempt > 1) {
|
|
80
|
+
onProgress?.(`Waiting for project provisioning (attempt ${attempt}/${PROJECT_ONBOARD_MAX_ATTEMPTS})...`);
|
|
81
|
+
await Bun.sleep(PROJECT_ONBOARD_INTERVAL_MS);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const onboardResponse = await fetch(`${endpoint}/v1internal:onboardUser`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers,
|
|
87
|
+
body: JSON.stringify(onboardBody),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!onboardResponse.ok) {
|
|
91
|
+
const errorText = await onboardResponse.text();
|
|
92
|
+
throw new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const operation = (await onboardResponse.json()) as LongRunningOperationResponse;
|
|
96
|
+
if (!operation.done) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const projectId = readProjectId(operation.response?.cloudaicompanionProject);
|
|
101
|
+
if (projectId) {
|
|
102
|
+
return projectId;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
`onboardUser did not return a provisioned project id after ${PROJECT_ONBOARD_MAX_ATTEMPTS} attempts`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {
|
|
112
|
+
const headers = {
|
|
113
|
+
Authorization: `Bearer ${accessToken}`,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
onProgress?.("Checking for existing project...");
|
|
119
|
+
const endpoint = CLOUD_CODE_ENDPOINT;
|
|
120
|
+
try {
|
|
121
|
+
const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers,
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!loadResponse.ok) {
|
|
130
|
+
const errorText = await loadResponse.text();
|
|
131
|
+
throw new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const loadPayload = (await loadResponse.json()) as LoadCodeAssistPayload;
|
|
135
|
+
const existingProject = readProjectId(loadPayload.cloudaicompanionProject);
|
|
136
|
+
if (existingProject) {
|
|
137
|
+
return existingProject;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const tierId = getDefaultTierId(loadPayload.allowedTiers);
|
|
141
|
+
onProgress?.("Provisioning project...");
|
|
142
|
+
const onboardBody = {
|
|
143
|
+
tierId,
|
|
144
|
+
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
145
|
+
};
|
|
146
|
+
const provisionedProject = await onboardProjectWithRetries(endpoint, headers, onboardBody, onProgress);
|
|
147
|
+
return provisionedProject;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Could not discover or provision an Antigravity project. ${error instanceof Error ? error.message : String(error)}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function loginAntigravity(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
156
|
+
return runGoogleOAuthLogin(ctrl, {
|
|
157
|
+
clientId: CLIENT_ID,
|
|
158
|
+
clientSecret: CLIENT_SECRET,
|
|
159
|
+
authUrl: AUTH_URL,
|
|
160
|
+
tokenUrl: TOKEN_URL,
|
|
161
|
+
scopes: SCOPES,
|
|
162
|
+
callbackPort: CALLBACK_PORT,
|
|
163
|
+
callbackPath: CALLBACK_PATH,
|
|
164
|
+
discoverProject,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Refresh Antigravity token
|
|
170
|
+
*/
|
|
171
|
+
export async function refreshAntigravityToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {
|
|
172
|
+
const response = await fetch(TOKEN_URL, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
175
|
+
body: new URLSearchParams({
|
|
176
|
+
client_id: CLIENT_ID,
|
|
177
|
+
client_secret: CLIENT_SECRET,
|
|
178
|
+
refresh_token: refreshToken,
|
|
179
|
+
grant_type: "refresh_token",
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
const error = await response.text();
|
|
185
|
+
throw new Error(`Antigravity token refresh failed: ${error}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = (await response.json()) as {
|
|
189
|
+
access_token: string;
|
|
190
|
+
expires_in: number;
|
|
191
|
+
refresh_token?: string;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
refresh: data.refresh_token || refreshToken,
|
|
196
|
+
access: data.access_token,
|
|
197
|
+
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
198
|
+
projectId,
|
|
199
|
+
};
|
|
200
|
+
}
|