@prometheus-ai/ai 0.5.4 → 0.5.8
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/dist/types/auth-broker/remote-store.d.ts +2 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +4 -1
- package/dist/types/auth-gateway/server.d.ts +19 -0
- package/dist/types/auth-gateway/types.d.ts +9 -3
- package/dist/types/auth-retry.d.ts +119 -0
- package/dist/types/auth-storage.d.ts +217 -8
- package/dist/types/errors.d.ts +24 -0
- package/dist/types/index.d.ts +5 -9
- package/dist/types/provider-details.d.ts +1 -1
- package/dist/types/providers/amazon-bedrock.d.ts +12 -6
- package/dist/types/providers/anthropic-client.d.ts +10 -3
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +2 -2
- package/dist/types/providers/anthropic-messages-server.d.ts +3 -3
- package/dist/types/providers/anthropic-wire.d.ts +3 -3
- package/dist/types/providers/anthropic.d.ts +41 -34
- package/dist/types/providers/aws-credentials.d.ts +8 -0
- package/dist/types/providers/azure-openai-responses.d.ts +1 -0
- package/dist/types/providers/google-gemini-cli.d.ts +22 -1
- package/dist/types/providers/google-shared.d.ts +22 -0
- package/dist/types/providers/google-types.d.ts +13 -1
- package/dist/types/providers/mock.d.ts +8 -3
- package/dist/types/providers/ollama.d.ts +6 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +6 -3
- package/dist/types/providers/openai-chat-server.d.ts +3 -3
- package/dist/types/providers/openai-chat-wire.d.ts +644 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +8 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +9 -0
- package/dist/types/providers/openai-codex-responses.d.ts +31 -2
- package/dist/types/providers/openai-completions-compat.d.ts +2 -25
- package/dist/types/providers/openai-completions.d.ts +2 -10
- package/dist/types/providers/openai-responses-server-schema.d.ts +4 -4
- package/dist/types/providers/openai-responses-server.d.ts +2 -2
- package/dist/types/providers/openai-responses-shared.d.ts +49 -9
- package/dist/types/providers/openai-responses-wire.d.ts +6065 -0
- package/dist/types/providers/openai-responses.d.ts +13 -4
- package/dist/types/providers/prometheus-native-client.d.ts +9 -0
- package/dist/types/providers/prometheus-native-server.d.ts +4 -3
- package/dist/types/providers/transform-messages.d.ts +1 -2
- package/dist/types/rate-limit-utils.d.ts +3 -2
- package/dist/types/registry/aimlapi.d.ts +4 -0
- package/dist/types/registry/alibaba-coding-plan.d.ts +7 -0
- package/dist/types/registry/amazon-bedrock.d.ts +5 -0
- package/dist/types/registry/anthropic.d.ts +10 -0
- package/dist/types/{utils/oauth → registry}/api-key-login.d.ts +8 -2
- package/dist/types/{utils/oauth → registry}/api-key-validation.d.ts +15 -0
- package/dist/types/registry/cerebras.d.ts +7 -0
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
- package/dist/types/registry/cursor.d.ts +7 -0
- package/dist/types/registry/deepseek.d.ts +8 -0
- package/dist/types/registry/derived.d.ts +5 -0
- package/dist/types/registry/firepass.d.ts +16 -0
- package/dist/types/registry/fireworks.d.ts +7 -0
- package/dist/types/registry/github-copilot.d.ts +7 -0
- package/dist/types/registry/gitlab-duo.d.ts +9 -0
- package/dist/types/registry/google-antigravity.d.ts +9 -0
- package/dist/types/registry/google-gemini-cli.d.ts +9 -0
- package/dist/types/registry/google-vertex.d.ts +5 -0
- package/dist/types/registry/google.d.ts +4 -0
- package/dist/types/registry/groq.d.ts +4 -0
- package/dist/types/registry/huggingface.d.ts +7 -0
- package/dist/types/registry/index.d.ts +4 -0
- package/dist/types/registry/kagi.d.ts +14 -0
- package/dist/types/registry/kilo.d.ts +7 -0
- package/dist/types/registry/kimi-code.d.ts +7 -0
- package/dist/types/registry/litellm.d.ts +13 -0
- package/dist/types/registry/lm-studio.d.ts +8 -0
- package/dist/types/registry/minimax-code-cn.d.ts +6 -0
- package/dist/types/registry/minimax-code.d.ts +6 -0
- package/dist/types/registry/minimax.d.ts +4 -0
- package/dist/types/registry/mistral.d.ts +4 -0
- package/dist/types/registry/moonshot.d.ts +7 -0
- package/dist/types/registry/nanogpt.d.ts +7 -0
- package/dist/types/registry/nvidia.d.ts +7 -0
- package/dist/types/registry/oauth/__tests__/xai-oauth.test.d.ts +1 -0
- package/dist/types/{utils → registry}/oauth/anthropic.d.ts +2 -1
- package/dist/types/{utils → registry}/oauth/github-copilot.d.ts +15 -23
- package/dist/types/{utils → registry}/oauth/index.d.ts +1 -0
- package/dist/types/{utils → registry}/oauth/minimax-code.d.ts +5 -5
- package/dist/types/{utils → registry}/oauth/types.d.ts +6 -1
- package/dist/types/{utils → registry}/oauth/xai-oauth.d.ts +2 -1
- package/dist/types/registry/ollama-cloud.d.ts +7 -0
- package/dist/types/registry/ollama.d.ts +12 -0
- package/dist/types/registry/openai-codex-device.d.ts +8 -0
- package/dist/types/registry/openai-codex.d.ts +9 -0
- package/dist/types/registry/openai.d.ts +4 -0
- package/dist/types/registry/opencode-go.d.ts +6 -0
- package/dist/types/registry/opencode-zen.d.ts +6 -0
- package/dist/types/registry/openrouter.d.ts +13 -0
- package/dist/types/registry/parallel.d.ts +14 -0
- package/dist/types/registry/perplexity.d.ts +7 -0
- package/dist/types/registry/qianfan.d.ts +7 -0
- package/dist/types/registry/qwen-portal.d.ts +7 -0
- package/dist/types/registry/registry.d.ts +272 -0
- package/dist/types/registry/synthetic.d.ts +6 -0
- package/dist/types/registry/tavily.d.ts +14 -0
- package/dist/types/registry/together.d.ts +6 -0
- package/dist/types/registry/types.d.ts +51 -0
- package/dist/types/registry/venice.d.ts +13 -0
- package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
- package/dist/types/registry/vllm.d.ts +7 -0
- package/dist/types/registry/wafer-pass.d.ts +6 -0
- package/dist/types/registry/wafer-serverless.d.ts +6 -0
- package/dist/types/registry/xai-oauth.d.ts +7 -0
- package/dist/types/registry/xai.d.ts +4 -0
- package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
- package/dist/types/registry/xiaomi.d.ts +6 -0
- package/dist/types/registry/zai.d.ts +7 -0
- package/dist/types/registry/zenmux.d.ts +7 -0
- package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
- package/dist/types/stream.d.ts +9 -1
- package/dist/types/types.d.ts +56 -295
- package/dist/types/usage/google-antigravity.d.ts +15 -1
- package/dist/types/usage/openai-codex-reset.d.ts +79 -0
- package/dist/types/usage/openai-codex.d.ts +1 -0
- package/dist/types/usage.d.ts +77 -4
- package/dist/types/utils/abort.d.ts +6 -0
- package/dist/types/utils/event-stream.d.ts +2 -0
- package/dist/types/utils/http-inspector.d.ts +0 -1
- package/dist/types/utils/idle-iterator.d.ts +35 -0
- package/dist/types/utils/openai-http.d.ts +58 -0
- package/dist/types/utils/request-debug.d.ts +3 -0
- package/dist/types/utils/retry-after.d.ts +1 -0
- package/dist/types/utils/schema/fields.d.ts +5 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +7 -15
- package/dist/types/utils/sse-debug.d.ts +0 -5
- package/dist/types/utils/stream-markup-healing.d.ts +2 -0
- package/dist/types/utils.d.ts +1 -5
- package/package.json +17 -29
- package/src/auth-broker/remote-store.ts +10 -1
- package/src/auth-broker/snapshot-cache.ts +1 -1
- package/src/auth-broker/wire-schemas.ts +1 -1
- package/src/auth-gateway/http.ts +1 -1
- package/src/auth-gateway/server.ts +95 -30
- package/src/auth-gateway/types.ts +10 -2
- package/src/auth-retry.ts +238 -0
- package/src/auth-storage.ts +935 -430
- package/src/errors.ts +32 -0
- package/src/index.ts +9 -14
- package/src/provider-details.ts +1 -1
- package/src/providers/__tests__/google-auth.test.ts +144 -0
- package/src/providers/amazon-bedrock.ts +70 -40
- package/src/providers/anthropic-client.ts +15 -13
- package/src/providers/anthropic-messages-server-schema.ts +17 -7
- package/src/providers/anthropic-messages-server.ts +88 -20
- package/src/providers/anthropic-wire.ts +4 -3
- package/src/providers/anthropic.ts +1234 -621
- package/src/providers/aws-credentials.ts +47 -5
- package/src/providers/aws-eventstream.ts +5 -0
- package/src/providers/azure-openai-responses.ts +117 -67
- package/src/providers/cursor.ts +30 -30
- package/src/providers/github-copilot-headers.ts +1 -1
- package/src/providers/gitlab-duo.ts +36 -29
- package/src/providers/google-auth.ts +71 -8
- package/src/providers/google-gemini-cli.ts +118 -22
- package/src/providers/google-shared.ts +163 -43
- package/src/providers/google-types.ts +10 -1
- package/src/providers/kimi.ts +1 -1
- package/src/providers/mock.ts +11 -3
- package/src/providers/ollama.ts +64 -7
- package/src/providers/openai-anthropic-shim.ts +17 -8
- package/src/providers/openai-chat-server-schema.ts +9 -3
- package/src/providers/openai-chat-server.ts +82 -16
- package/src/providers/openai-chat-wire.ts +847 -0
- package/src/providers/openai-codex/request-transformer.ts +129 -34
- package/src/providers/openai-codex/response-handler.ts +22 -1
- package/src/providers/openai-codex-responses.ts +699 -247
- package/src/providers/openai-completions-compat.ts +8 -308
- package/src/providers/openai-completions.ts +416 -267
- package/src/providers/openai-responses-server-schema.ts +15 -9
- package/src/providers/openai-responses-server.ts +162 -114
- package/src/providers/openai-responses-shared.ts +320 -82
- package/src/providers/openai-responses-wire.ts +6391 -0
- package/src/providers/openai-responses.ts +382 -176
- package/src/providers/prometheus-native-client.ts +27 -11
- package/src/providers/prometheus-native-server.ts +44 -17
- package/src/providers/transform-messages.ts +311 -120
- package/src/providers/vision-guard.ts +5 -3
- package/src/rate-limit-utils.ts +13 -3
- package/src/registry/aimlapi.ts +6 -0
- package/src/{utils/oauth → registry}/alibaba-coding-plan.ts +8 -18
- package/src/registry/amazon-bedrock.ts +22 -0
- package/src/registry/anthropic.ts +26 -0
- package/src/{utils/oauth → registry}/api-key-login.ts +25 -3
- package/src/{utils/oauth → registry}/api-key-validation.ts +62 -2
- package/src/{utils/oauth → registry}/cerebras.ts +8 -1
- package/src/{utils/oauth → registry}/cloudflare-ai-gateway.ts +8 -12
- package/src/registry/cursor.ts +20 -0
- package/src/{utils/oauth → registry}/deepseek.ts +9 -17
- package/src/registry/derived.ts +9 -0
- package/src/{utils/oauth → registry}/firepass.ts +10 -2
- package/src/{utils/oauth → registry}/fireworks.ts +8 -1
- package/src/registry/github-copilot.ts +22 -0
- package/src/registry/gitlab-duo.ts +19 -0
- package/src/registry/google-antigravity.ts +21 -0
- package/src/registry/google-gemini-cli.ts +21 -0
- package/src/registry/google-vertex.ts +38 -0
- package/src/registry/google.ts +6 -0
- package/src/registry/groq.ts +6 -0
- package/src/{utils/oauth → registry}/huggingface.ts +8 -19
- package/src/registry/index.ts +4 -0
- package/src/{utils/oauth → registry}/kagi.ts +9 -11
- package/src/{utils/oauth → registry}/kilo.ts +11 -6
- package/src/registry/kimi-code.ts +17 -0
- package/src/{utils/oauth → registry}/litellm.ts +8 -12
- package/src/{utils/oauth → registry}/lm-studio.ts +9 -17
- package/src/registry/minimax-code-cn.ts +12 -0
- package/src/registry/minimax-code.ts +12 -0
- package/src/registry/minimax.ts +6 -0
- package/src/registry/mistral.ts +6 -0
- package/src/{utils/oauth → registry}/moonshot.ts +8 -9
- package/src/{utils/oauth → registry}/nanogpt.ts +8 -1
- package/src/{utils/oauth → registry}/nvidia.ts +8 -18
- package/src/{utils → registry}/oauth/__tests__/xai-oauth.test.ts +4 -7
- package/src/{utils → registry}/oauth/anthropic.ts +38 -17
- package/src/{utils → registry}/oauth/github-copilot.ts +79 -115
- package/src/registry/oauth/gitlab-duo.ts +198 -0
- package/src/{utils → registry}/oauth/google-antigravity.ts +1 -4
- package/src/{utils → registry}/oauth/google-gemini-cli.ts +1 -4
- package/src/registry/oauth/index.ts +164 -0
- package/src/{utils → registry}/oauth/minimax-code.ts +16 -14
- package/src/{utils → registry}/oauth/types.ts +7 -51
- package/src/{utils → registry}/oauth/wafer.ts +1 -1
- package/src/{utils → registry}/oauth/xai-oauth.ts +16 -8
- package/src/{utils → registry}/oauth/xiaomi.ts +9 -4
- package/src/{utils/oauth → registry}/ollama-cloud.ts +8 -1
- package/src/{utils/oauth → registry}/ollama.ts +8 -13
- package/src/registry/openai-codex-device.ts +18 -0
- package/src/registry/openai-codex.ts +19 -0
- package/src/registry/openai.ts +6 -0
- package/src/registry/opencode-go.ts +12 -0
- package/src/registry/opencode-zen.ts +12 -0
- package/src/{utils/oauth → registry}/openrouter.ts +10 -2
- package/src/{utils/oauth → registry}/parallel.ts +9 -11
- package/src/registry/perplexity.ts +13 -0
- package/src/{utils/oauth → registry}/qianfan.ts +8 -17
- package/src/{utils/oauth → registry}/qwen-portal.ts +8 -19
- package/src/registry/registry.ts +149 -0
- package/src/{utils/oauth → registry}/synthetic.ts +7 -1
- package/src/{utils/oauth → registry}/tavily.ts +10 -12
- package/src/{utils/oauth → registry}/together.ts +7 -1
- package/src/registry/types.ts +56 -0
- package/src/{utils/oauth → registry}/venice.ts +8 -12
- package/src/{utils/oauth → registry}/vercel-ai-gateway.ts +8 -18
- package/src/{utils/oauth → registry}/vllm.ts +9 -16
- package/src/registry/wafer-pass.ts +12 -0
- package/src/registry/wafer-serverless.ts +12 -0
- package/src/registry/xai-oauth.ts +17 -0
- package/src/registry/xai.ts +6 -0
- package/src/registry/xiaomi-token-plan-ams.ts +12 -0
- package/src/registry/xiaomi-token-plan-cn.ts +12 -0
- package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
- package/src/registry/xiaomi.ts +12 -0
- package/src/{utils/oauth → registry}/zai.ts +10 -22
- package/src/{utils/oauth → registry}/zenmux.ts +8 -1
- package/src/{utils/oauth/zhipu.ts → registry/zhipu-coding-plan.ts} +9 -21
- package/src/stream.ts +229 -199
- package/src/types.ts +63 -384
- package/src/usage/claude.ts +4 -2
- package/src/usage/github-copilot.ts +4 -2
- package/src/usage/google-antigravity.ts +196 -28
- package/src/usage/kimi.ts +1 -1
- package/src/usage/minimax-code.ts +5 -6
- package/src/usage/openai-codex-reset.ts +174 -0
- package/src/usage/openai-codex.ts +19 -2
- package/src/usage/zai.ts +2 -1
- package/src/usage.ts +93 -4
- package/src/utils/abort.ts +14 -0
- package/src/utils/event-stream.ts +17 -0
- package/src/utils/http-inspector.ts +4 -12
- package/src/utils/idle-iterator.ts +250 -79
- package/src/utils/openai-http.ts +157 -0
- package/src/utils/request-debug.ts +67 -19
- package/src/utils/retry-after.ts +1 -1
- package/src/utils/retry.ts +23 -2
- package/src/utils/schema/CONSTRAINTS.md +4 -2
- package/src/utils/schema/fields.ts +16 -0
- package/src/utils/schema/json-schema-validator.ts +19 -1
- package/src/utils/schema/normalize.ts +80 -8
- package/src/utils/schema/stamps.ts +22 -10
- package/src/utils/schema/wire.ts +2 -2
- package/src/utils/sse-debug.ts +0 -271
- package/src/utils/stream-markup-healing.ts +50 -8
- package/src/utils/validation.ts +49 -13
- package/src/utils.ts +2 -26
- package/dist/types/model-cache.d.ts +0 -17
- package/dist/types/model-manager.d.ts +0 -64
- package/dist/types/model-thinking.d.ts +0 -100
- package/dist/types/models.d.ts +0 -12
- package/dist/types/provider-models/bundled-references.d.ts +0 -4
- package/dist/types/provider-models/descriptors.d.ts +0 -50
- package/dist/types/provider-models/google.d.ts +0 -24
- package/dist/types/provider-models/index.d.ts +0 -5
- package/dist/types/provider-models/ollama.d.ts +0 -7
- package/dist/types/provider-models/openai-compat.d.ts +0 -323
- package/dist/types/provider-models/special.d.ts +0 -16
- package/dist/types/utils/discovery/antigravity.d.ts +0 -61
- package/dist/types/utils/discovery/codex.d.ts +0 -38
- package/dist/types/utils/discovery/cursor.d.ts +0 -23
- package/dist/types/utils/discovery/gemini.d.ts +0 -25
- package/dist/types/utils/discovery/index.d.ts +0 -4
- package/dist/types/utils/discovery/openai-compatible.d.ts +0 -72
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
- package/dist/types/utils/oauth/cerebras.d.ts +0 -1
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/deepseek.d.ts +0 -10
- package/dist/types/utils/oauth/firepass.d.ts +0 -1
- package/dist/types/utils/oauth/fireworks.d.ts +0 -1
- package/dist/types/utils/oauth/huggingface.d.ts +0 -19
- package/dist/types/utils/oauth/kagi.d.ts +0 -17
- package/dist/types/utils/oauth/kilo.d.ts +0 -5
- package/dist/types/utils/oauth/litellm.d.ts +0 -18
- package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
- package/dist/types/utils/oauth/moonshot.d.ts +0 -1
- package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
- package/dist/types/utils/oauth/nvidia.d.ts +0 -18
- package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
- package/dist/types/utils/oauth/ollama.d.ts +0 -18
- package/dist/types/utils/oauth/openrouter.d.ts +0 -1
- package/dist/types/utils/oauth/parallel.d.ts +0 -17
- package/dist/types/utils/oauth/qianfan.d.ts +0 -17
- package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
- package/dist/types/utils/oauth/synthetic.d.ts +0 -1
- package/dist/types/utils/oauth/tavily.d.ts +0 -17
- package/dist/types/utils/oauth/together.d.ts +0 -1
- package/dist/types/utils/oauth/venice.d.ts +0 -18
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/vllm.d.ts +0 -16
- package/dist/types/utils/oauth/zai.d.ts +0 -18
- package/dist/types/utils/oauth/zenmux.d.ts +0 -1
- package/dist/types/utils/oauth/zhipu.d.ts +0 -18
- package/src/model-cache.ts +0 -129
- package/src/model-manager.ts +0 -469
- package/src/model-thinking.ts +0 -756
- package/src/models.json +0 -60287
- package/src/models.json.d.ts +0 -9
- package/src/models.ts +0 -56
- package/src/provider-models/bundled-references.ts +0 -38
- package/src/provider-models/descriptors.ts +0 -364
- package/src/provider-models/google.ts +0 -88
- package/src/provider-models/index.ts +0 -5
- package/src/provider-models/ollama.ts +0 -153
- package/src/provider-models/openai-compat.ts +0 -2904
- package/src/provider-models/special.ts +0 -67
- package/src/utils/discovery/antigravity.ts +0 -261
- package/src/utils/discovery/codex.ts +0 -371
- package/src/utils/discovery/cursor.ts +0 -306
- package/src/utils/discovery/gemini.ts +0 -248
- package/src/utils/discovery/index.ts +0 -4
- package/src/utils/discovery/openai-compatible.ts +0 -224
- package/src/utils/oauth/gitlab-duo.ts +0 -123
- package/src/utils/oauth/index.ts +0 -502
- /package/dist/types/{utils/oauth/__tests__/xai-oauth.test.d.ts → providers/__tests__/google-auth.test.d.ts} +0 -0
- /package/dist/types/{utils → registry}/oauth/callback-server.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/cursor.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/gitlab-duo.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/google-antigravity.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/google-gemini-cli.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/google-oauth-shared.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/kimi.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/openai-codex.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/opencode.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/perplexity.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/pkce.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/wafer.d.ts +0 -0
- /package/dist/types/{utils → registry}/oauth/xiaomi.d.ts +0 -0
- /package/src/{utils → registry}/oauth/callback-server.ts +0 -0
- /package/src/{utils → registry}/oauth/cursor.ts +0 -0
- /package/src/{utils → registry}/oauth/google-oauth-shared.ts +0 -0
- /package/src/{utils → registry}/oauth/kimi.ts +0 -0
- /package/src/{utils → registry}/oauth/oauth.html +0 -0
- /package/src/{utils → registry}/oauth/openai-codex.ts +0 -0
- /package/src/{utils → registry}/oauth/opencode.ts +0 -0
- /package/src/{utils → registry}/oauth/perplexity.ts +0 -0
- /package/src/{utils → registry}/oauth/pkce.ts +0 -0
|
@@ -2,19 +2,32 @@
|
|
|
2
2
|
* GitHub Copilot OAuth flow (opencode OAuth app)
|
|
3
3
|
*/
|
|
4
4
|
import { scheduler } from "node:timers/promises";
|
|
5
|
-
import { getBundledModels } from "
|
|
5
|
+
import { getBundledModels } from "@prometheus-ai/catalog/models";
|
|
6
|
+
import {
|
|
7
|
+
COPILOT_API_HEADERS,
|
|
8
|
+
getGitHubCopilotBaseUrl,
|
|
9
|
+
isPublicGitHubHost,
|
|
10
|
+
normalizeDomain,
|
|
11
|
+
normalizeGitHubCopilotEnterpriseDomain,
|
|
12
|
+
OPENCODE_HEADERS,
|
|
13
|
+
} from "@prometheus-ai/catalog/wire/github-copilot";
|
|
14
|
+
import type { FetchImpl } from "../../types";
|
|
6
15
|
import type { OAuthCredentials } from "./types";
|
|
7
16
|
|
|
8
17
|
const CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
9
18
|
|
|
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
19
|
const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
|
|
17
20
|
const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
|
|
21
|
+
|
|
22
|
+
type GitHubCopilotLoginOptions = {
|
|
23
|
+
onAuth: (url: string, instructions?: string) => void;
|
|
24
|
+
onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;
|
|
25
|
+
onProgress?: (message: string) => void;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
pollIntervalFloorMs?: number;
|
|
28
|
+
pollIntervalScaleMs?: number;
|
|
29
|
+
fetch?: FetchImpl;
|
|
30
|
+
};
|
|
18
31
|
type DeviceCodeResponse = {
|
|
19
32
|
device_code: string;
|
|
20
33
|
user_code: string;
|
|
@@ -35,58 +48,6 @@ type DeviceTokenErrorResponse = {
|
|
|
35
48
|
interval?: number;
|
|
36
49
|
};
|
|
37
50
|
|
|
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
51
|
function getUrls(domain: string): {
|
|
91
52
|
deviceCodeUrl: string;
|
|
92
53
|
accessTokenUrl: string;
|
|
@@ -97,17 +58,8 @@ function getUrls(domain: string): {
|
|
|
97
58
|
};
|
|
98
59
|
}
|
|
99
60
|
|
|
100
|
-
|
|
101
|
-
const
|
|
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);
|
|
61
|
+
async function fetchJson(url: string, init: RequestInit, fetchImpl: FetchImpl): Promise<unknown> {
|
|
62
|
+
const response = await fetchImpl(url, init);
|
|
111
63
|
if (!response.ok) {
|
|
112
64
|
const text = await response.text();
|
|
113
65
|
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
@@ -115,20 +67,24 @@ async function fetchJson(url: string, init: RequestInit): Promise<unknown> {
|
|
|
115
67
|
return response.json();
|
|
116
68
|
}
|
|
117
69
|
|
|
118
|
-
async function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {
|
|
70
|
+
async function startDeviceFlow(domain: string, fetchImpl: FetchImpl): Promise<DeviceCodeResponse> {
|
|
119
71
|
const urls = getUrls(domain);
|
|
120
|
-
const data = await fetchJson(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
72
|
+
const data = await fetchJson(
|
|
73
|
+
urls.deviceCodeUrl,
|
|
74
|
+
{
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
Accept: "application/json",
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
...OPENCODE_HEADERS,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
client_id: CLIENT_ID,
|
|
83
|
+
scope: "read:user",
|
|
84
|
+
}),
|
|
126
85
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
scope: "read:user",
|
|
130
|
-
}),
|
|
131
|
-
});
|
|
86
|
+
fetchImpl,
|
|
87
|
+
);
|
|
132
88
|
|
|
133
89
|
if (!data || typeof data !== "object") {
|
|
134
90
|
throw new Error("Invalid device code response");
|
|
@@ -164,7 +120,8 @@ async function pollForGitHubAccessToken(
|
|
|
164
120
|
deviceCode: string,
|
|
165
121
|
intervalSeconds: number,
|
|
166
122
|
expiresIn: number,
|
|
167
|
-
signal
|
|
123
|
+
signal: AbortSignal | undefined,
|
|
124
|
+
fetchImpl: FetchImpl,
|
|
168
125
|
pollIntervalFloorMs = 1000,
|
|
169
126
|
pollIntervalScaleMs = 1000,
|
|
170
127
|
) {
|
|
@@ -187,19 +144,23 @@ async function pollForGitHubAccessToken(
|
|
|
187
144
|
throw new Error("Login cancelled");
|
|
188
145
|
}
|
|
189
146
|
|
|
190
|
-
const raw = await fetchJson(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
147
|
+
const raw = await fetchJson(
|
|
148
|
+
urls.accessTokenUrl,
|
|
149
|
+
{
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
Accept: "application/json",
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
...OPENCODE_HEADERS,
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
client_id: CLIENT_ID,
|
|
158
|
+
device_code: deviceCode,
|
|
159
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
160
|
+
}),
|
|
196
161
|
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
device_code: deviceCode,
|
|
200
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
201
|
-
}),
|
|
202
|
-
});
|
|
162
|
+
fetchImpl,
|
|
163
|
+
);
|
|
203
164
|
|
|
204
165
|
if (raw && typeof raw === "object" && typeof (raw as DeviceTokenSuccessResponse).access_token === "string") {
|
|
205
166
|
return (raw as DeviceTokenSuccessResponse).access_token;
|
|
@@ -255,17 +216,22 @@ export function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain
|
|
|
255
216
|
* Enable a model for the user's GitHub Copilot account.
|
|
256
217
|
* This is required for some models (like Claude, Grok) before they can be used.
|
|
257
218
|
*/
|
|
258
|
-
async function enableGitHubCopilotModel(
|
|
219
|
+
async function enableGitHubCopilotModel(
|
|
220
|
+
token: string,
|
|
221
|
+
modelId: string,
|
|
222
|
+
fetchImpl: FetchImpl,
|
|
223
|
+
enterpriseDomain?: string,
|
|
224
|
+
): Promise<boolean> {
|
|
259
225
|
const baseUrl = getGitHubCopilotBaseUrl(enterpriseDomain);
|
|
260
226
|
const url = `${baseUrl}/models/${modelId}/policy`;
|
|
261
227
|
|
|
262
228
|
try {
|
|
263
|
-
const response = await
|
|
229
|
+
const response = await fetchImpl(url, {
|
|
264
230
|
method: "POST",
|
|
265
231
|
headers: {
|
|
266
232
|
"Content-Type": "application/json",
|
|
267
233
|
Authorization: `Bearer ${token}`,
|
|
268
|
-
...
|
|
234
|
+
...COPILOT_API_HEADERS,
|
|
269
235
|
"openai-intent": "chat-policy",
|
|
270
236
|
"x-interaction-type": "chat-policy",
|
|
271
237
|
},
|
|
@@ -283,17 +249,20 @@ async function enableGitHubCopilotModel(token: string, modelId: string, enterpri
|
|
|
283
249
|
*/
|
|
284
250
|
async function enableAllGitHubCopilotModels(
|
|
285
251
|
token: string,
|
|
286
|
-
enterpriseDomain
|
|
252
|
+
enterpriseDomain: string | undefined,
|
|
253
|
+
fetchImpl: FetchImpl,
|
|
287
254
|
onProgress?: (model: string, success: boolean) => void,
|
|
288
255
|
): Promise<void> {
|
|
289
|
-
|
|
256
|
+
// Synthesized catalog variants (Copilot long-context `-1m` entries) share
|
|
257
|
+
// the upstream model id; enable each wire id exactly once.
|
|
258
|
+
const wireModelIds = [...new Set(getBundledModels("github-copilot").map(model => model.requestModelId ?? model.id))];
|
|
290
259
|
const BATCH_SIZE = 5;
|
|
291
|
-
for (let i = 0; i <
|
|
292
|
-
const batch =
|
|
260
|
+
for (let i = 0; i < wireModelIds.length; i += BATCH_SIZE) {
|
|
261
|
+
const batch = wireModelIds.slice(i, i + BATCH_SIZE);
|
|
293
262
|
await Promise.all(
|
|
294
|
-
batch.map(async
|
|
295
|
-
const success = await enableGitHubCopilotModel(token,
|
|
296
|
-
onProgress?.(
|
|
263
|
+
batch.map(async modelId => {
|
|
264
|
+
const success = await enableGitHubCopilotModel(token, modelId, fetchImpl, enterpriseDomain);
|
|
265
|
+
onProgress?.(modelId, success);
|
|
297
266
|
}),
|
|
298
267
|
);
|
|
299
268
|
}
|
|
@@ -307,14 +276,8 @@ async function enableAllGitHubCopilotModels(
|
|
|
307
276
|
* @param options.onProgress - Optional progress callback
|
|
308
277
|
* @param options.signal - Optional AbortSignal for cancellation
|
|
309
278
|
*/
|
|
310
|
-
export async function loginGitHubCopilot(options: {
|
|
311
|
-
|
|
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> {
|
|
279
|
+
export async function loginGitHubCopilot(options: GitHubCopilotLoginOptions): Promise<OAuthCredentials> {
|
|
280
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
318
281
|
const input = await options.onPrompt({
|
|
319
282
|
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
320
283
|
placeholder: "company.ghe.com",
|
|
@@ -334,7 +297,7 @@ export async function loginGitHubCopilot(options: {
|
|
|
334
297
|
const domain =
|
|
335
298
|
normalizedDomain && isPublicGitHubHost(normalizedDomain) ? "github.com" : (normalizedDomain ?? "github.com");
|
|
336
299
|
|
|
337
|
-
const device = await startDeviceFlow(domain);
|
|
300
|
+
const device = await startDeviceFlow(domain, fetchImpl);
|
|
338
301
|
options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
|
|
339
302
|
|
|
340
303
|
const githubAccessToken = await pollForGitHubAccessToken(
|
|
@@ -343,6 +306,7 @@ export async function loginGitHubCopilot(options: {
|
|
|
343
306
|
device.interval,
|
|
344
307
|
device.expires_in,
|
|
345
308
|
options.signal,
|
|
309
|
+
fetchImpl,
|
|
346
310
|
options.pollIntervalFloorMs,
|
|
347
311
|
options.pollIntervalScaleMs,
|
|
348
312
|
);
|
|
@@ -357,6 +321,6 @@ export async function loginGitHubCopilot(options: {
|
|
|
357
321
|
|
|
358
322
|
// Enable all models after successful login
|
|
359
323
|
options.onProgress?.("Enabling models...");
|
|
360
|
-
await enableAllGitHubCopilotModels(githubAccessToken, enterpriseDomain ?? undefined);
|
|
324
|
+
await enableAllGitHubCopilotModels(githubAccessToken, enterpriseDomain ?? undefined, fetchImpl);
|
|
361
325
|
return credentials;
|
|
362
326
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { clearGitLabDuoDirectAccessCache } from "../../providers/gitlab-duo";
|
|
2
|
+
import type { FetchImpl } from "../../types";
|
|
3
|
+
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
4
|
+
import { generatePKCE } from "./pkce";
|
|
5
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "./types";
|
|
6
|
+
|
|
7
|
+
const GITLAB_COM_URL = "https://gitlab.com";
|
|
8
|
+
/**
|
|
9
|
+
* Default OAuth client id baked into the bundled GitLab Duo login flow. GitLab
|
|
10
|
+
* authorize requests are rejected outright (`The redirect URI included is not
|
|
11
|
+
* valid`) whenever this client id's registered redirect URI list drifts from
|
|
12
|
+
* `http://localhost:8080/callback`. Users hitting that case can either:
|
|
13
|
+
*
|
|
14
|
+
* - register their own GitLab OAuth application and override the bundled
|
|
15
|
+
* credentials with `GITLAB_CLIENT_ID` + `GITLAB_REDIRECT_URI`, or
|
|
16
|
+
* - skip OAuth entirely and supply a Personal Access Token via `GITLAB_TOKEN`.
|
|
17
|
+
*
|
|
18
|
+
* @see https://github.com/uttamtrivedi/prometheus/issues/2424
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_CLIENT_ID = "da4edff2e6ebd2bc3208611e2768bc1c1dd7be791dc5ff26ca34ca9ee44f7d4b";
|
|
21
|
+
const OAUTH_SCOPES = ["api"];
|
|
22
|
+
const DEFAULT_CALLBACK_PORT = 8080;
|
|
23
|
+
const DEFAULT_CALLBACK_PATH = "/callback";
|
|
24
|
+
const DEFAULT_CALLBACK_HOSTNAME = "localhost";
|
|
25
|
+
|
|
26
|
+
interface PKCEPair {
|
|
27
|
+
verifier: string;
|
|
28
|
+
challenge: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the OAuth client id, preferring `GITLAB_CLIENT_ID` when set so users
|
|
33
|
+
* with their own GitLab OAuth application can bypass the bundled credentials.
|
|
34
|
+
*/
|
|
35
|
+
function resolveClientId(): string {
|
|
36
|
+
const env = process.env.GITLAB_CLIENT_ID?.trim();
|
|
37
|
+
return env && env.length > 0 ? env : DEFAULT_CLIENT_ID;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve callback-server options from `GITLAB_REDIRECT_URI`. When set, the
|
|
42
|
+
* exact string is advertised to GitLab (strict matching), random-port fallback
|
|
43
|
+
* is disabled, and HTTP loopback URIs bind the listener to the URI's host/port
|
|
44
|
+
* so the browser callback lands on us. HTTPS loopback URIs are rejected because
|
|
45
|
+
* the local callback server is plaintext HTTP. Non-loopback URIs bind a random
|
|
46
|
+
* local port — only the paste-code path can complete in that case.
|
|
47
|
+
*/
|
|
48
|
+
function resolveCallbackOptions(): OAuthCallbackFlowOptions {
|
|
49
|
+
const raw = process.env.GITLAB_REDIRECT_URI?.trim();
|
|
50
|
+
if (!raw) {
|
|
51
|
+
return {
|
|
52
|
+
preferredPort: DEFAULT_CALLBACK_PORT,
|
|
53
|
+
callbackPath: DEFAULT_CALLBACK_PATH,
|
|
54
|
+
callbackHostname: DEFAULT_CALLBACK_HOSTNAME,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let parsed: URL;
|
|
59
|
+
try {
|
|
60
|
+
parsed = new URL(raw);
|
|
61
|
+
} catch {
|
|
62
|
+
throw new Error(`Invalid GITLAB_REDIRECT_URI: ${raw}`);
|
|
63
|
+
}
|
|
64
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
65
|
+
throw new Error(`GITLAB_REDIRECT_URI must use http:// or https://, got: ${raw}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
69
|
+
if (isLoopback && parsed.protocol !== "http:") {
|
|
70
|
+
throw new Error(`GITLAB_REDIRECT_URI loopback callbacks must use http://, got: ${raw}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
preferredPort: isLoopback ? port : 0,
|
|
77
|
+
callbackPath: parsed.pathname || DEFAULT_CALLBACK_PATH,
|
|
78
|
+
callbackHostname: isLoopback ? parsed.hostname : DEFAULT_CALLBACK_HOSTNAME,
|
|
79
|
+
redirectUri: raw,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function mapTokenResponse(payload: {
|
|
84
|
+
access_token?: string;
|
|
85
|
+
refresh_token?: string;
|
|
86
|
+
expires_in?: number;
|
|
87
|
+
created_at?: number;
|
|
88
|
+
}): OAuthCredentials {
|
|
89
|
+
if (!payload.access_token || !payload.refresh_token || typeof payload.expires_in !== "number") {
|
|
90
|
+
throw new Error("GitLab OAuth token response missing required fields");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const createdAtMs =
|
|
94
|
+
typeof payload.created_at === "number" && Number.isFinite(payload.created_at)
|
|
95
|
+
? payload.created_at * 1000
|
|
96
|
+
: Date.now();
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
access: payload.access_token,
|
|
100
|
+
refresh: payload.refresh_token,
|
|
101
|
+
expires: createdAtMs + payload.expires_in * 1000 - 5 * 60 * 1000,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class GitLabDuoOAuthFlow extends OAuthCallbackFlow {
|
|
106
|
+
#pkce: PKCEPair;
|
|
107
|
+
#clientId: string;
|
|
108
|
+
#fetch: FetchImpl;
|
|
109
|
+
|
|
110
|
+
constructor(ctrl: OAuthLoginCallbacks, pkce: PKCEPair, clientId: string, options: OAuthCallbackFlowOptions) {
|
|
111
|
+
super(ctrl, options);
|
|
112
|
+
this.#pkce = pkce;
|
|
113
|
+
this.#clientId = clientId;
|
|
114
|
+
this.#fetch = ctrl.fetch ?? fetch;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
118
|
+
const authParams = new URLSearchParams({
|
|
119
|
+
client_id: this.#clientId,
|
|
120
|
+
redirect_uri: redirectUri,
|
|
121
|
+
response_type: "code",
|
|
122
|
+
scope: OAUTH_SCOPES.join(" "),
|
|
123
|
+
code_challenge: this.#pkce.challenge,
|
|
124
|
+
code_challenge_method: "S256",
|
|
125
|
+
state,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
url: `${GITLAB_COM_URL}/oauth/authorize?${authParams.toString()}`,
|
|
130
|
+
instructions:
|
|
131
|
+
'Complete GitLab login in browser. If GitLab responds with "The redirect URI included is not valid", ' +
|
|
132
|
+
"register your own GitLab OAuth application and set GITLAB_CLIENT_ID + GITLAB_REDIRECT_URI, or use a " +
|
|
133
|
+
"Personal Access Token via GITLAB_TOKEN.",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
|
|
138
|
+
const response = await this.#fetch(`${GITLAB_COM_URL}/oauth/token`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
141
|
+
body: new URLSearchParams({
|
|
142
|
+
client_id: this.#clientId,
|
|
143
|
+
grant_type: "authorization_code",
|
|
144
|
+
code,
|
|
145
|
+
code_verifier: this.#pkce.verifier,
|
|
146
|
+
redirect_uri: redirectUri,
|
|
147
|
+
}).toString(),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
throw new Error(`GitLab OAuth token exchange failed: ${response.status} ${await response.text()}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
clearGitLabDuoDirectAccessCache();
|
|
155
|
+
return mapTokenResponse(
|
|
156
|
+
(await response.json()) as {
|
|
157
|
+
access_token?: string;
|
|
158
|
+
refresh_token?: string;
|
|
159
|
+
expires_in?: number;
|
|
160
|
+
created_at?: number;
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function loginGitLabDuo(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
167
|
+
const pkce = await generatePKCE();
|
|
168
|
+
const clientId = resolveClientId();
|
|
169
|
+
const options = resolveCallbackOptions();
|
|
170
|
+
const flow = new GitLabDuoOAuthFlow(callbacks, pkce, clientId, options);
|
|
171
|
+
return flow.login();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function refreshGitLabDuoToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
175
|
+
const response = await fetch(`${GITLAB_COM_URL}/oauth/token`, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
178
|
+
body: new URLSearchParams({
|
|
179
|
+
client_id: resolveClientId(),
|
|
180
|
+
grant_type: "refresh_token",
|
|
181
|
+
refresh_token: credentials.refresh,
|
|
182
|
+
}).toString(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new Error(`GitLab OAuth refresh failed: ${response.status} ${await response.text()}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
clearGitLabDuoDirectAccessCache();
|
|
190
|
+
return mapTokenResponse(
|
|
191
|
+
(await response.json()) as {
|
|
192
|
+
access_token?: string;
|
|
193
|
+
refresh_token?: string;
|
|
194
|
+
expires_in?: number;
|
|
195
|
+
created_at?: number;
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
3
3
|
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
|
|
4
4
|
*/
|
|
5
|
-
import { getAntigravityUserAgent } from "
|
|
5
|
+
import { getAntigravityUserAgent } from "@prometheus-ai/catalog/wire/gemini-headers";
|
|
6
6
|
import { runGoogleOAuthLogin } from "./google-oauth-shared";
|
|
7
7
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
8
8
|
|
|
9
9
|
const decode = (s: string) => atob(s);
|
|
10
|
-
// Native-app OAuth credentials identify the public Google/Antigravity client.
|
|
11
|
-
// They are not user OAuth tokens, and installed apps cannot keep client
|
|
12
|
-
// credentials confidential. See docs/secrets.md for the release hygiene note.
|
|
13
10
|
const CLIENT_ID = decode(
|
|
14
11
|
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
|
|
15
12
|
);
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
* Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getGeminiCliHeaders } from "@prometheus-ai/catalog/wire/gemini-headers";
|
|
6
7
|
import { $env } from "@prometheus-ai/utils";
|
|
7
|
-
import { getGeminiCliHeaders } from "../../providers/google-gemini-headers";
|
|
8
8
|
import { runGoogleOAuthLogin } from "./google-oauth-shared";
|
|
9
9
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
10
10
|
|
|
11
11
|
const decode = (s: string) => atob(s);
|
|
12
|
-
// Native-app OAuth credentials identify the public Google/Gemini CLI client.
|
|
13
|
-
// They are not user OAuth tokens, and installed apps cannot keep client
|
|
14
|
-
// credentials confidential. See docs/secrets.md for the release hygiene note.
|
|
15
12
|
const CLIENT_ID = decode(
|
|
16
13
|
"NjgxMjU1ODA5Mzk1LW9vOGZ0Mm9wcmRybnA5ZTNhcWY2YXYzaG1kaWIxMzVqLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t",
|
|
17
14
|
);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// High-level API
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { getProviderDefinition, PROVIDER_REGISTRY } from "../registry";
|
|
6
|
+
import type {
|
|
7
|
+
OAuthCredentials,
|
|
8
|
+
OAuthProvider,
|
|
9
|
+
OAuthProviderId,
|
|
10
|
+
OAuthProviderInfo,
|
|
11
|
+
OAuthProviderInterface,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
export type * from "./types";
|
|
15
|
+
|
|
16
|
+
const builtInOAuthProviders: OAuthProviderInfo[] = PROVIDER_REGISTRY.filter(
|
|
17
|
+
provider => provider.login && provider.showInLoginList !== false,
|
|
18
|
+
).map(provider => ({
|
|
19
|
+
id: provider.id,
|
|
20
|
+
name: provider.name,
|
|
21
|
+
available: provider.available ?? true,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const customOAuthProviders = new Map<string, OAuthProviderInterface>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a custom OAuth provider.
|
|
28
|
+
*/
|
|
29
|
+
export function registerOAuthProvider(provider: OAuthProviderInterface): void {
|
|
30
|
+
customOAuthProviders.set(provider.id, provider);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get a custom OAuth provider by ID.
|
|
35
|
+
*/
|
|
36
|
+
export function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {
|
|
37
|
+
return customOAuthProviders.get(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Remove all custom OAuth providers registered by a source.
|
|
42
|
+
*/
|
|
43
|
+
export function unregisterOAuthProviders(sourceId: string): void {
|
|
44
|
+
for (const [id, provider] of customOAuthProviders.entries()) {
|
|
45
|
+
if (provider.sourceId === sourceId) {
|
|
46
|
+
customOAuthProviders.delete(id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Refresh token for any OAuth provider.
|
|
53
|
+
* Saves the new credentials and returns the new access token.
|
|
54
|
+
*/
|
|
55
|
+
export async function refreshOAuthToken(
|
|
56
|
+
provider: OAuthProvider,
|
|
57
|
+
credentials: OAuthCredentials,
|
|
58
|
+
): Promise<OAuthCredentials> {
|
|
59
|
+
if (!credentials) {
|
|
60
|
+
throw new Error(`No OAuth credentials found for ${provider}`);
|
|
61
|
+
}
|
|
62
|
+
const def = getProviderDefinition(provider);
|
|
63
|
+
if (!def?.login) {
|
|
64
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
65
|
+
}
|
|
66
|
+
// Providers without a real refresher (static bearer tokens / API keys that
|
|
67
|
+
// don't expire) return the credentials unchanged.
|
|
68
|
+
return def.refreshToken ? def.refreshToken(credentials) : credentials;
|
|
69
|
+
}
|
|
70
|
+
function getPerplexityJwtExpiryMs(token: string): number | undefined {
|
|
71
|
+
const parts = token.split(".");
|
|
72
|
+
if (parts.length !== 3) return undefined;
|
|
73
|
+
const payload = parts[1];
|
|
74
|
+
if (!payload) return undefined;
|
|
75
|
+
try {
|
|
76
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: unknown };
|
|
77
|
+
if (typeof decoded.exp !== "number" || !Number.isFinite(decoded.exp)) return undefined;
|
|
78
|
+
return decoded.exp * 1000 - 5 * 60_000;
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build API-key bytes for a provider from an already-fresh OAuth credential.
|
|
86
|
+
*
|
|
87
|
+
* Refresh is owned by AuthStorage. This helper deliberately refuses expired
|
|
88
|
+
* credentials so it cannot POST broker redaction sentinels to upstream token
|
|
89
|
+
* endpoints as a side channel.
|
|
90
|
+
*
|
|
91
|
+
* For providers that need credential metadata at request time, returns
|
|
92
|
+
* JSON-encoded credentials plus expiry metadata for diagnostics/edge guards.
|
|
93
|
+
* @returns API key string, or null if no credentials
|
|
94
|
+
* @throws Error if the credential is expired and must be refreshed upstream
|
|
95
|
+
*/
|
|
96
|
+
export async function getOAuthApiKey(
|
|
97
|
+
provider: OAuthProvider,
|
|
98
|
+
credentials: Record<string, OAuthCredentials>,
|
|
99
|
+
): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {
|
|
100
|
+
let creds = credentials[provider];
|
|
101
|
+
if (!creds) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (provider === "perplexity") {
|
|
106
|
+
// Perplexity JWTs usually omit `exp` (server-side sessions). Trust the JWT
|
|
107
|
+
// claim when present; otherwise treat the credential as non-expiring rather
|
|
108
|
+
// than honoring a stale stored `expires` (older logins wrote loginTime+1h).
|
|
109
|
+
const NEVER_EXPIRES = 8.64e15;
|
|
110
|
+
const normalizedExpires =
|
|
111
|
+
creds.expires > 0 && creds.expires < 10_000_000_000 ? creds.expires * 1000 : creds.expires;
|
|
112
|
+
const jwtExpiry = getPerplexityJwtExpiryMs(creds.access);
|
|
113
|
+
const expires = jwtExpiry ?? Math.max(normalizedExpires, NEVER_EXPIRES);
|
|
114
|
+
if (expires !== creds.expires) {
|
|
115
|
+
creds = { ...creds, expires };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Refresh is the sole responsibility of `AuthStorage` (which calls
|
|
119
|
+
// `refreshOAuthToken` directly with broker-aware single-flighting). If we
|
|
120
|
+
// reach here with an expired credential, the outer pipeline failed to
|
|
121
|
+
// refresh before this call OR the refresh slot is the broker sentinel —
|
|
122
|
+
// either way, posting the credential to a provider endpoint would only
|
|
123
|
+
// trigger a `__remote__`-against-real-provider failure that gets classified
|
|
124
|
+
// as `invalid_grant` and disables the row. Refuse loudly instead.
|
|
125
|
+
if (Date.now() >= creds.expires) {
|
|
126
|
+
if (provider === "perplexity") {
|
|
127
|
+
const jwtExpiry = getPerplexityJwtExpiryMs(creds.access);
|
|
128
|
+
if (jwtExpiry && Date.now() < jwtExpiry) {
|
|
129
|
+
const fallbackCredentials = { ...creds, expires: jwtExpiry };
|
|
130
|
+
return { newCredentials: fallbackCredentials, apiKey: fallbackCredentials.access };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw new Error(
|
|
134
|
+
`OAuth credential for ${provider} is expired and must be refreshed via AuthStorage before getOAuthApiKey is called`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
// For providers that need request-time credential metadata, return JSON.
|
|
138
|
+
const needsStructuredApiKey =
|
|
139
|
+
provider === "github-copilot" || provider === "google-gemini-cli" || provider === "google-antigravity";
|
|
140
|
+
const apiKey = needsStructuredApiKey
|
|
141
|
+
? JSON.stringify({
|
|
142
|
+
token: creds.access,
|
|
143
|
+
enterpriseUrl: creds.enterpriseUrl,
|
|
144
|
+
projectId: creds.projectId,
|
|
145
|
+
refreshToken: creds.refresh,
|
|
146
|
+
expiresAt: creds.expires,
|
|
147
|
+
email: creds.email,
|
|
148
|
+
accountId: creds.accountId,
|
|
149
|
+
})
|
|
150
|
+
: creds.access;
|
|
151
|
+
return { newCredentials: creds, apiKey };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get list of OAuth providers.
|
|
156
|
+
*/
|
|
157
|
+
export function getOAuthProviders(): OAuthProviderInfo[] {
|
|
158
|
+
const customProviders = Array.from(customOAuthProviders.values(), provider => ({
|
|
159
|
+
id: provider.id,
|
|
160
|
+
name: provider.name,
|
|
161
|
+
available: true,
|
|
162
|
+
}));
|
|
163
|
+
return [...builtInOAuthProviders, ...customProviders];
|
|
164
|
+
}
|