@linxiraos/pi-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5066 -0
- package/README.md +1195 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
- package/dist/types/auth-broker/client.d.ts +93 -0
- package/dist/types/auth-broker/discover.d.ts +39 -0
- package/dist/types/auth-broker/index.d.ts +7 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +136 -0
- package/dist/types/auth-broker/server.d.ts +48 -0
- package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
- package/dist/types/auth-broker/types.d.ts +152 -0
- package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
- package/dist/types/auth-gateway/http.d.ts +56 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +36 -0
- package/dist/types/auth-gateway/types.d.ts +127 -0
- package/dist/types/auth-retry.d.ts +150 -0
- package/dist/types/auth-storage.d.ts +1258 -0
- package/dist/types/dialect/anthropic.d.ts +15 -0
- package/dist/types/dialect/catalog.d.ts +3 -0
- package/dist/types/dialect/coercion.d.ts +23 -0
- package/dist/types/dialect/deepseek.d.ts +14 -0
- package/dist/types/dialect/demotion.d.ts +30 -0
- package/dist/types/dialect/examples.d.ts +18 -0
- package/dist/types/dialect/factory.d.ts +3 -0
- package/dist/types/dialect/fenced-thinking.d.ts +53 -0
- package/dist/types/dialect/gemini.d.ts +17 -0
- package/dist/types/dialect/gemma.d.ts +15 -0
- package/dist/types/dialect/glm.d.ts +9 -0
- package/dist/types/dialect/harmony.d.ts +8 -0
- package/dist/types/dialect/hermes.d.ts +9 -0
- package/dist/types/dialect/history.d.ts +3 -0
- package/dist/types/dialect/index.d.ts +11 -0
- package/dist/types/dialect/inventory.d.ts +9 -0
- package/dist/types/dialect/kimi.d.ts +14 -0
- package/dist/types/dialect/minimax.d.ts +3 -0
- package/dist/types/dialect/owned-stream.d.ts +4 -0
- package/dist/types/dialect/qwen3.d.ts +9 -0
- package/dist/types/dialect/rendering.d.ts +54 -0
- package/dist/types/dialect/thinking.d.ts +6 -0
- package/dist/types/dialect/types.d.ts +68 -0
- package/dist/types/dialect/xml.d.ts +9 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +20 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +27 -0
- package/dist/types/error/classes.d.ts +106 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +94 -0
- package/dist/types/error/format.d.ts +20 -0
- package/dist/types/error/gateway.d.ts +20 -0
- package/dist/types/error/index.d.ts +14 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/error/rate-limit.d.ts +80 -0
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/providers/amazon-bedrock.d.ts +39 -0
- package/dist/types/providers/anthropic-client.d.ts +106 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic-wire.d.ts +345 -0
- package/dist/types/providers/anthropic.d.ts +261 -0
- package/dist/types/providers/aws-credentials.d.ts +48 -0
- package/dist/types/providers/aws-eventstream.d.ts +39 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +16 -0
- package/dist/types/providers/bedrock-mantle.d.ts +13 -0
- package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
- package/dist/types/providers/cowork-fetch.d.ts +3 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor-pi-args.d.ts +105 -0
- package/dist/types/providers/cursor.d.ts +221 -0
- package/dist/types/providers/devin.d.ts +12 -0
- package/dist/types/providers/error-message.d.ts +25 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +120 -0
- package/dist/types/providers/google-shared.d.ts +203 -0
- package/dist/types/providers/google-types.d.ts +155 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +179 -0
- package/dist/types/providers/ollama.d.ts +8 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-chat-wire.d.ts +669 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
- package/dist/types/providers/openai-codex-responses.d.ts +238 -0
- package/dist/types/providers/openai-completions.d.ts +48 -0
- package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
- package/dist/types/providers/openai-responses.d.ts +131 -0
- package/dist/types/providers/openai-shared.d.ts +624 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +69 -0
- package/dist/types/providers/register-builtins.d.ts +37 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +32 -0
- package/dist/types/providers/vision-guard.d.ts +20 -0
- package/dist/types/registry/aiand.d.ts +7 -0
- package/dist/types/registry/aimlapi.d.ts +4 -0
- package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
- package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
- package/dist/types/registry/amazon-bedrock.d.ts +5 -0
- package/dist/types/registry/anthropic.d.ts +10 -0
- package/dist/types/registry/api-key-login.d.ts +42 -0
- package/dist/types/registry/api-key-validation.d.ts +43 -0
- package/dist/types/registry/aws.d.ts +13 -0
- package/dist/types/registry/azure.d.ts +4 -0
- package/dist/types/registry/baseten.d.ts +7 -0
- package/dist/types/registry/bedrock-mantle.d.ts +22 -0
- package/dist/types/registry/cerebras.d.ts +7 -0
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
- package/dist/types/registry/coreweave.d.ts +7 -0
- package/dist/types/registry/cursor.d.ts +7 -0
- package/dist/types/registry/deepseek.d.ts +8 -0
- package/dist/types/registry/derived.d.ts +5 -0
- package/dist/types/registry/devin.d.ts +8 -0
- package/dist/types/registry/exa.d.ts +8 -0
- package/dist/types/registry/firepass.d.ts +16 -0
- package/dist/types/registry/fireworks.d.ts +7 -0
- package/dist/types/registry/github-copilot.d.ts +7 -0
- package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
- package/dist/types/registry/gitlab-duo.d.ts +9 -0
- package/dist/types/registry/gmi-cloud.d.ts +7 -0
- package/dist/types/registry/google-antigravity.d.ts +9 -0
- package/dist/types/registry/google-gemini-cli.d.ts +9 -0
- package/dist/types/registry/google-vertex.d.ts +5 -0
- package/dist/types/registry/google.d.ts +4 -0
- package/dist/types/registry/groq.d.ts +4 -0
- package/dist/types/registry/huggingface.d.ts +7 -0
- package/dist/types/registry/index.d.ts +4 -0
- package/dist/types/registry/kagi.d.ts +14 -0
- package/dist/types/registry/kilo.d.ts +7 -0
- package/dist/types/registry/kimi-code.d.ts +7 -0
- package/dist/types/registry/litellm.d.ts +13 -0
- package/dist/types/registry/llama-cpp.d.ts +8 -0
- package/dist/types/registry/lm-studio.d.ts +8 -0
- package/dist/types/registry/meta.d.ts +7 -0
- package/dist/types/registry/minimax-code-cn.d.ts +6 -0
- package/dist/types/registry/minimax-code.d.ts +6 -0
- package/dist/types/registry/minimax.d.ts +4 -0
- package/dist/types/registry/mistral.d.ts +4 -0
- package/dist/types/registry/moonshot.d.ts +7 -0
- package/dist/types/registry/nanogpt.d.ts +7 -0
- package/dist/types/registry/novita.d.ts +6 -0
- package/dist/types/registry/nvidia.d.ts +7 -0
- package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
- package/dist/types/registry/oauth/anthropic.d.ts +24 -0
- package/dist/types/registry/oauth/callback-server.d.ts +74 -0
- package/dist/types/registry/oauth/cursor.d.ts +16 -0
- package/dist/types/registry/oauth/device-code.d.ts +25 -0
- package/dist/types/registry/oauth/devin.d.ts +5 -0
- package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
- package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
- package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
- package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
- package/dist/types/registry/oauth/index.d.ts +45 -0
- package/dist/types/registry/oauth/kimi.d.ts +21 -0
- package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
- package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
- package/dist/types/registry/oauth/opencode.d.ts +18 -0
- package/dist/types/registry/oauth/perplexity.d.ts +9 -0
- package/dist/types/registry/oauth/pkce.d.ts +8 -0
- package/dist/types/registry/oauth/types.d.ts +87 -0
- package/dist/types/registry/oauth/wafer.d.ts +1 -0
- package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
- package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
- package/dist/types/registry/oauth/zai.d.ts +25 -0
- package/dist/types/registry/ollama-cloud.d.ts +7 -0
- package/dist/types/registry/ollama.d.ts +12 -0
- package/dist/types/registry/openai-codex-device.d.ts +8 -0
- package/dist/types/registry/openai-codex.d.ts +9 -0
- package/dist/types/registry/openai.d.ts +4 -0
- package/dist/types/registry/opencode-go.d.ts +6 -0
- package/dist/types/registry/opencode-zen.d.ts +6 -0
- package/dist/types/registry/openrouter.d.ts +13 -0
- package/dist/types/registry/parallel.d.ts +14 -0
- package/dist/types/registry/perplexity.d.ts +7 -0
- package/dist/types/registry/qianfan.d.ts +7 -0
- package/dist/types/registry/qwen-portal.d.ts +7 -0
- package/dist/types/registry/registry.d.ts +367 -0
- package/dist/types/registry/sakana.d.ts +7 -0
- package/dist/types/registry/siliconflow-cn.d.ts +7 -0
- package/dist/types/registry/siliconflow.d.ts +7 -0
- package/dist/types/registry/synthetic.d.ts +6 -0
- package/dist/types/registry/tavily.d.ts +14 -0
- package/dist/types/registry/together.d.ts +6 -0
- package/dist/types/registry/types.d.ts +75 -0
- package/dist/types/registry/umans.d.ts +7 -0
- package/dist/types/registry/venice.d.ts +13 -0
- package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
- package/dist/types/registry/vllm.d.ts +7 -0
- package/dist/types/registry/wafer-serverless.d.ts +6 -0
- package/dist/types/registry/xai-oauth.d.ts +7 -0
- package/dist/types/registry/xai.d.ts +7 -0
- package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
- package/dist/types/registry/xiaomi.d.ts +6 -0
- package/dist/types/registry/zai.d.ts +15 -0
- package/dist/types/registry/zenmux.d.ts +7 -0
- package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
- package/dist/types/stream.d.ts +46 -0
- package/dist/types/types.d.ts +1064 -0
- package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
- package/dist/types/usage/claude.d.ts +4 -0
- package/dist/types/usage/cursor.d.ts +4 -0
- package/dist/types/usage/gemini.d.ts +2 -0
- package/dist/types/usage/github-copilot.d.ts +7 -0
- package/dist/types/usage/google-antigravity.d.ts +15 -0
- package/dist/types/usage/kimi.d.ts +2 -0
- package/dist/types/usage/minimax-code.d.ts +3 -0
- package/dist/types/usage/ollama.d.ts +5 -0
- package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
- package/dist/types/usage/openai-codex-reset.d.ts +88 -0
- package/dist/types/usage/openai-codex.d.ts +10 -0
- package/dist/types/usage/opencode-go.d.ts +2 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/synthetic.d.ts +2 -0
- package/dist/types/usage/umans.d.ts +2 -0
- package/dist/types/usage/xai-oauth.d.ts +12 -0
- package/dist/types/usage/zai.d.ts +3 -0
- package/dist/types/usage.d.ts +527 -0
- package/dist/types/utils/abort.d.ts +25 -0
- package/dist/types/utils/anthropic-auth.d.ts +35 -0
- package/dist/types/utils/aws-profile.d.ts +17 -0
- package/dist/types/utils/block-symbols.d.ts +62 -0
- package/dist/types/utils/deterministic-id.d.ts +16 -0
- package/dist/types/utils/empty-completion-retry.d.ts +21 -0
- package/dist/types/utils/event-stream.d.ts +39 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/google-validation.d.ts +2 -0
- package/dist/types/utils/harmony-leak.d.ts +135 -0
- package/dist/types/utils/http-inspector.d.ts +49 -0
- package/dist/types/utils/idle-iterator.d.ts +149 -0
- package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
- package/dist/types/utils/openai-http.d.ts +48 -0
- package/dist/types/utils/openrouter-headers.d.ts +1 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/proxy.d.ts +39 -0
- package/dist/types/utils/request-debug.d.ts +29 -0
- package/dist/types/utils/retry-after.d.ts +4 -0
- package/dist/types/utils/retry.d.ts +14 -0
- package/dist/types/utils/schema/adapt.d.ts +24 -0
- package/dist/types/utils/schema/compatibility.d.ts +30 -0
- package/dist/types/utils/schema/dereference.d.ts +11 -0
- package/dist/types/utils/schema/draft.d.ts +10 -0
- package/dist/types/utils/schema/equality.d.ts +4 -0
- package/dist/types/utils/schema/fields.d.ts +54 -0
- package/dist/types/utils/schema/index.d.ts +14 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +153 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +17 -0
- package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/typescript.d.ts +24 -0
- package/dist/types/utils/schema/wire.d.ts +52 -0
- package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
- package/dist/types/utils/sse-debug.d.ts +5 -0
- package/dist/types/utils/stream-markup-healing.d.ts +87 -0
- package/dist/types/utils/thinking-loop.d.ts +102 -0
- package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
- package/dist/types/utils/tool-choice.d.ts +52 -0
- package/dist/types/utils/validation.d.ts +28 -0
- package/dist/types/utils.d.ts +57 -0
- package/package.json +138 -0
- package/src/api-registry.ts +109 -0
- package/src/auth/sqlite-credential-store.ts +2066 -0
- package/src/auth-broker/client.ts +471 -0
- package/src/auth-broker/discover.ts +310 -0
- package/src/auth-broker/index.ts +7 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +1332 -0
- package/src/auth-broker/server.ts +898 -0
- package/src/auth-broker/snapshot-cache.ts +200 -0
- package/src/auth-broker/types.ts +193 -0
- package/src/auth-broker/wire-schema-resource.ts +487 -0
- package/src/auth-broker/wire-schemas.ts +43 -0
- package/src/auth-gateway/http.ts +227 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +836 -0
- package/src/auth-gateway/types.ts +153 -0
- package/src/auth-retry.ts +401 -0
- package/src/auth-storage.ts +6540 -0
- package/src/dialect/anthropic.md +31 -0
- package/src/dialect/anthropic.ts +608 -0
- package/src/dialect/catalog.ts +29 -0
- package/src/dialect/coercion.ts +136 -0
- package/src/dialect/deepseek.md +24 -0
- package/src/dialect/deepseek.ts +609 -0
- package/src/dialect/demotion.ts +40 -0
- package/src/dialect/examples.ts +71 -0
- package/src/dialect/factory.ts +34 -0
- package/src/dialect/fenced-thinking.ts +184 -0
- package/src/dialect/gemini.md +44 -0
- package/src/dialect/gemini.ts +583 -0
- package/src/dialect/gemma.md +33 -0
- package/src/dialect/gemma.ts +387 -0
- package/src/dialect/glm.md +32 -0
- package/src/dialect/glm.ts +579 -0
- package/src/dialect/harmony.md +31 -0
- package/src/dialect/harmony.ts +345 -0
- package/src/dialect/hermes.md +25 -0
- package/src/dialect/hermes.ts +206 -0
- package/src/dialect/history.ts +81 -0
- package/src/dialect/index.ts +15 -0
- package/src/dialect/inventory.ts +30 -0
- package/src/dialect/kimi.md +24 -0
- package/src/dialect/kimi.ts +340 -0
- package/src/dialect/minimax.md +31 -0
- package/src/dialect/minimax.ts +95 -0
- package/src/dialect/owned-stream.ts +481 -0
- package/src/dialect/prompt-template.md +12 -0
- package/src/dialect/qwen3.md +28 -0
- package/src/dialect/qwen3.ts +240 -0
- package/src/dialect/rendering.ts +304 -0
- package/src/dialect/thinking.ts +292 -0
- package/src/dialect/types.ts +56 -0
- package/src/dialect/xml.md +22 -0
- package/src/dialect/xml.ts +90 -0
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +47 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +35 -0
- package/src/error/classes.ts +281 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +602 -0
- package/src/error/format.ts +45 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +14 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +63 -0
- package/src/error/rate-limit.ts +303 -0
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +51 -0
- package/src/provider-details.ts +90 -0
- package/src/providers/amazon-bedrock.ts +1064 -0
- package/src/providers/anthropic-client.ts +317 -0
- package/src/providers/anthropic-messages-server-schema.ts +252 -0
- package/src/providers/anthropic-messages-server.ts +818 -0
- package/src/providers/anthropic-wire.ts +359 -0
- package/src/providers/anthropic.ts +4539 -0
- package/src/providers/aws-credentials.ts +772 -0
- package/src/providers/aws-eventstream.ts +181 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +438 -0
- package/src/providers/bedrock-mantle.ts +110 -0
- package/src/providers/claude-code-fingerprint.ts +20 -0
- package/src/providers/cowork-fetch.ts +201 -0
- package/src/providers/cursor/exec-modern.ts +496 -0
- package/src/providers/cursor/proto/agent.proto +4533 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor-pi-args.ts +165 -0
- package/src/providers/cursor.ts +4689 -0
- package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
- package/src/providers/devin/proto/buf.gen.yaml +33 -0
- package/src/providers/devin/proto/buf.yaml +17 -0
- package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
- package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
- package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
- package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
- package/src/providers/devin/proto/cel/expr/value.proto +41 -0
- package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
- package/src/providers/devin/proto/errorspb/errors.proto +56 -0
- package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
- package/src/providers/devin/proto/errorspb/markers.proto +10 -0
- package/src/providers/devin/proto/errorspb/tags.proto +12 -0
- package/src/providers/devin/proto/errorspb/testing.proto +6 -0
- package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
- package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
- package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
- package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
- package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
- package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
- package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
- package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
- package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
- package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
- package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
- package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
- package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
- package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
- package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
- package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
- package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
- package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
- package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
- package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
- package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
- package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
- package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
- package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
- package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
- package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
- package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
- package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
- package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
- package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
- package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
- package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
- package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
- package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
- package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
- package/src/providers/devin.ts +679 -0
- package/src/providers/error-message.ts +23 -0
- package/src/providers/github-copilot-headers.ts +141 -0
- package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
- package/src/providers/gitlab-duo-workflow.ts +3135 -0
- package/src/providers/gitlab-duo.ts +399 -0
- package/src/providers/google-auth.ts +330 -0
- package/src/providers/google-gemini-cli.ts +1370 -0
- package/src/providers/google-shared.ts +1122 -0
- package/src/providers/google-types.ts +180 -0
- package/src/providers/google-vertex.ts +135 -0
- package/src/providers/google.ts +47 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +51 -0
- package/src/providers/mock.ts +514 -0
- package/src/providers/ollama.ts +776 -0
- package/src/providers/openai-anthropic-shim.ts +166 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +752 -0
- package/src/providers/openai-chat-wire.ts +859 -0
- package/src/providers/openai-codex/request-transformer.ts +491 -0
- package/src/providers/openai-codex/response-handler.ts +102 -0
- package/src/providers/openai-codex-responses.ts +4716 -0
- package/src/providers/openai-completions.ts +2389 -0
- package/src/providers/openai-reasoning-fallback.ts +269 -0
- package/src/providers/openai-responses-server-schema.ts +397 -0
- package/src/providers/openai-responses-server.ts +1466 -0
- package/src/providers/openai-responses-wire.ts +6416 -0
- package/src/providers/openai-responses.ts +1393 -0
- package/src/providers/openai-shared.ts +3500 -0
- package/src/providers/pi-native-client.ts +275 -0
- package/src/providers/pi-native-server.ts +245 -0
- package/src/providers/register-builtins.ts +503 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +1083 -0
- package/src/providers/vision-guard.ts +54 -0
- package/src/registry/aiand.ts +22 -0
- package/src/registry/aimlapi.ts +6 -0
- package/src/registry/alibaba-coding-plan.ts +104 -0
- package/src/registry/alibaba-token-plan.ts +125 -0
- package/src/registry/amazon-bedrock.ts +22 -0
- package/src/registry/anthropic.ts +26 -0
- package/src/registry/api-key-login.ts +115 -0
- package/src/registry/api-key-validation.ts +145 -0
- package/src/registry/aws.ts +57 -0
- package/src/registry/azure.ts +6 -0
- package/src/registry/baseten.ts +22 -0
- package/src/registry/bedrock-mantle.ts +34 -0
- package/src/registry/cerebras.ts +23 -0
- package/src/registry/cloudflare-ai-gateway.ts +45 -0
- package/src/registry/coreweave.ts +40 -0
- package/src/registry/cursor.ts +20 -0
- package/src/registry/deepseek.ts +46 -0
- package/src/registry/derived.ts +9 -0
- package/src/registry/devin.ts +15 -0
- package/src/registry/exa.ts +19 -0
- package/src/registry/firepass.ts +32 -0
- package/src/registry/fireworks.ts +28 -0
- package/src/registry/github-copilot.ts +22 -0
- package/src/registry/gitlab-duo-workflow.ts +20 -0
- package/src/registry/gitlab-duo.ts +19 -0
- package/src/registry/gmi-cloud.ts +22 -0
- package/src/registry/google-antigravity.ts +22 -0
- package/src/registry/google-gemini-cli.ts +22 -0
- package/src/registry/google-vertex.ts +38 -0
- package/src/registry/google.ts +6 -0
- package/src/registry/groq.ts +6 -0
- package/src/registry/huggingface.ts +29 -0
- package/src/registry/index.ts +4 -0
- package/src/registry/kagi.ts +46 -0
- package/src/registry/kilo.ts +114 -0
- package/src/registry/kimi-code.ts +17 -0
- package/src/registry/litellm.ts +45 -0
- package/src/registry/llama-cpp.ts +35 -0
- package/src/registry/lm-studio.ts +31 -0
- package/src/registry/meta.ts +22 -0
- package/src/registry/minimax-code-cn.ts +12 -0
- package/src/registry/minimax-code.ts +12 -0
- package/src/registry/minimax.ts +6 -0
- package/src/registry/mistral.ts +6 -0
- package/src/registry/moonshot.ts +28 -0
- package/src/registry/nanogpt.ts +22 -0
- package/src/registry/novita.ts +25 -0
- package/src/registry/nvidia.ts +61 -0
- package/src/registry/oauth/anthropic-constants.ts +12 -0
- package/src/registry/oauth/anthropic.ts +346 -0
- package/src/registry/oauth/callback-server.ts +438 -0
- package/src/registry/oauth/cursor.ts +187 -0
- package/src/registry/oauth/device-code.ts +92 -0
- package/src/registry/oauth/devin.ts +124 -0
- package/src/registry/oauth/github-copilot.ts +369 -0
- package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
- package/src/registry/oauth/gitlab-duo.ts +222 -0
- package/src/registry/oauth/google-antigravity.ts +225 -0
- package/src/registry/oauth/google-gemini-cli.ts +297 -0
- package/src/registry/oauth/google-oauth-shared.ts +211 -0
- package/src/registry/oauth/index.ts +187 -0
- package/src/registry/oauth/kimi.ts +297 -0
- package/src/registry/oauth/minimax-code.ts +53 -0
- package/src/registry/oauth/oauth.html +317 -0
- package/src/registry/oauth/openai-codex.ts +384 -0
- package/src/registry/oauth/opencode.ts +50 -0
- package/src/registry/oauth/perplexity.ts +228 -0
- package/src/registry/oauth/pkce.ts +18 -0
- package/src/registry/oauth/types.ts +96 -0
- package/src/registry/oauth/wafer.ts +24 -0
- package/src/registry/oauth/xai-oauth.ts +559 -0
- package/src/registry/oauth/xiaomi.ts +211 -0
- package/src/registry/oauth/zai.ts +285 -0
- package/src/registry/ollama-cloud.ts +36 -0
- package/src/registry/ollama.ts +43 -0
- package/src/registry/openai-codex-device.ts +18 -0
- package/src/registry/openai-codex.ts +19 -0
- package/src/registry/openai.ts +6 -0
- package/src/registry/opencode-go.ts +12 -0
- package/src/registry/opencode-zen.ts +12 -0
- package/src/registry/openrouter.ts +28 -0
- package/src/registry/parallel.ts +45 -0
- package/src/registry/perplexity.ts +13 -0
- package/src/registry/qianfan.ts +27 -0
- package/src/registry/qwen-portal.ts +50 -0
- package/src/registry/registry.ts +182 -0
- package/src/registry/sakana.ts +22 -0
- package/src/registry/siliconflow-cn.ts +22 -0
- package/src/registry/siliconflow.ts +22 -0
- package/src/registry/synthetic.ts +21 -0
- package/src/registry/tavily.ts +45 -0
- package/src/registry/together.ts +22 -0
- package/src/registry/types.ts +86 -0
- package/src/registry/umans.ts +23 -0
- package/src/registry/venice.ts +33 -0
- package/src/registry/vercel-ai-gateway.ts +38 -0
- package/src/registry/vllm.ts +34 -0
- package/src/registry/wafer-serverless.ts +12 -0
- package/src/registry/xai-oauth.ts +17 -0
- package/src/registry/xai.ts +22 -0
- package/src/registry/xiaomi-token-plan-ams.ts +12 -0
- package/src/registry/xiaomi-token-plan-cn.ts +12 -0
- package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
- package/src/registry/xiaomi.ts +12 -0
- package/src/registry/zai.ts +41 -0
- package/src/registry/zenmux.ts +22 -0
- package/src/registry/zhipu-coding-plan.ts +27 -0
- package/src/stream.ts +1944 -0
- package/src/types.ts +1243 -0
- package/src/usage/alibaba-token-plan.ts +230 -0
- package/src/usage/claude.ts +830 -0
- package/src/usage/cursor.ts +335 -0
- package/src/usage/gemini.ts +258 -0
- package/src/usage/github-copilot.ts +424 -0
- package/src/usage/google-antigravity.ts +497 -0
- package/src/usage/kimi.ts +277 -0
- package/src/usage/minimax-code.ts +291 -0
- package/src/usage/ollama.ts +41 -0
- package/src/usage/openai-codex-base-url.ts +35 -0
- package/src/usage/openai-codex-reset.ts +205 -0
- package/src/usage/openai-codex.ts +627 -0
- package/src/usage/opencode-go.ts +89 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/synthetic.ts +180 -0
- package/src/usage/umans.ts +192 -0
- package/src/usage/xai-oauth.ts +414 -0
- package/src/usage/zai.ts +370 -0
- package/src/usage.ts +411 -0
- package/src/utils/abort.ts +67 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/aws-profile.ts +88 -0
- package/src/utils/block-symbols.ts +78 -0
- package/src/utils/deterministic-id.ts +20 -0
- package/src/utils/empty-completion-retry.ts +161 -0
- package/src/utils/event-stream.ts +202 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/google-validation.ts +25 -0
- package/src/utils/harmony-leak.ts +500 -0
- package/src/utils/http-inspector.ts +196 -0
- package/src/utils/idle-iterator.ts +531 -0
- package/src/utils/leaked-thinking-stream.ts +483 -0
- package/src/utils/openai-http.ts +119 -0
- package/src/utils/openrouter-headers.ts +12 -0
- package/src/utils/parse-bind.ts +56 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/proxy.ts +314 -0
- package/src/utils/request-debug.ts +351 -0
- package/src/utils/retry-after.ts +121 -0
- package/src/utils/retry.ts +77 -0
- package/src/utils/schema/CONSTRAINTS.md +168 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +210 -0
- package/src/utils/schema/index.ts +14 -0
- package/src/utils/schema/json-schema-validator.ts +595 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +2314 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +109 -0
- package/src/utils/schema/strict-tool-validation.ts +117 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/typescript.ts +212 -0
- package/src/utils/schema/wire.ts +662 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +18 -0
- package/src/utils/stream-markup-healing.ts +247 -0
- package/src/utils/thinking-loop.ts +552 -0
- package/src/utils/tool-call-loop-guard.ts +107 -0
- package/src/utils/tool-choice.ts +101 -0
- package/src/utils/validation.ts +1932 -0
- package/src/utils.ts +492 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex (ChatGPT OAuth) flow — browser and device-code flows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { OPENAI_HEADER_VALUES } from "@linxiraos/pi-catalog/wire/codex";
|
|
6
|
+
import * as AIError from "../../error";
|
|
7
|
+
import type { FetchImpl } from "../../types";
|
|
8
|
+
import { isRecord } from "../../utils";
|
|
9
|
+
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
10
|
+
import { generatePKCE } from "./pkce";
|
|
11
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
12
|
+
|
|
13
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
14
|
+
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
15
|
+
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
16
|
+
const CALLBACK_PORT = 1455;
|
|
17
|
+
const CALLBACK_PATH = "/auth/callback";
|
|
18
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
19
|
+
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
20
|
+
const JWT_PROFILE_CLAIM = "https://api.openai.com/profile";
|
|
21
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 15_000;
|
|
22
|
+
const DEVICE_USERCODE_URL = "https://auth.openai.com/api/accounts/deviceauth/usercode";
|
|
23
|
+
const DEVICE_TOKEN_URL = "https://auth.openai.com/api/accounts/deviceauth/token";
|
|
24
|
+
const DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
|
|
25
|
+
const DEVICE_AUTH_URL = "https://auth.openai.com/codex/device";
|
|
26
|
+
const DEVICE_POLL_INTERVAL_MS = 5_000;
|
|
27
|
+
const DEVICE_POLL_SAFETY_MARGIN_MS = 3_000;
|
|
28
|
+
/** Upper bound on device-code polling to avoid infinite loops on server errors. */
|
|
29
|
+
const DEVICE_MAX_POLLS = 120;
|
|
30
|
+
|
|
31
|
+
type JwtPayload = {
|
|
32
|
+
[JWT_CLAIM_PATH]?: {
|
|
33
|
+
chatgpt_account_id?: string;
|
|
34
|
+
chatgpt_plan_type?: string;
|
|
35
|
+
};
|
|
36
|
+
[JWT_PROFILE_CLAIM]?: {
|
|
37
|
+
email?: string;
|
|
38
|
+
};
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function decodeJwt<T = Record<string, unknown>>(token: string): T | null {
|
|
43
|
+
try {
|
|
44
|
+
const parts = token.split(".");
|
|
45
|
+
if (parts.length !== 3) return null;
|
|
46
|
+
const payload = parts[1] ?? "";
|
|
47
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
48
|
+
return JSON.parse(decoded) as T;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Identity slice decoded from the token claims. The ChatGPT workspace
|
|
56
|
+
* (`chatgpt_account_id`) is the subscription pool the token draws limits
|
|
57
|
+
* from — one account email can hold several (e.g. a personal Pro plan plus a
|
|
58
|
+
* Team seat). `chatgpt_plan_type` may only be present on the `id_token`.
|
|
59
|
+
*/
|
|
60
|
+
function getTokenProfile(
|
|
61
|
+
accessToken: string,
|
|
62
|
+
idToken?: string,
|
|
63
|
+
): { accountId?: string; email?: string; planType?: string } {
|
|
64
|
+
const payload = decodeJwt<JwtPayload>(accessToken);
|
|
65
|
+
const idPayload = idToken ? decodeJwt<JwtPayload>(idToken) : null;
|
|
66
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
67
|
+
const idAuth = idPayload?.[JWT_CLAIM_PATH];
|
|
68
|
+
const accountId = auth?.chatgpt_account_id;
|
|
69
|
+
const email = payload?.[JWT_PROFILE_CLAIM]?.email?.trim().toLowerCase();
|
|
70
|
+
const planType = (auth?.chatgpt_plan_type ?? idAuth?.chatgpt_plan_type)?.trim().toLowerCase();
|
|
71
|
+
return {
|
|
72
|
+
accountId: typeof accountId === "string" && accountId.length > 0 ? accountId : undefined,
|
|
73
|
+
email: typeof email === "string" && email.length > 0 ? email : undefined,
|
|
74
|
+
planType: typeof planType === "string" && planType.length > 0 ? planType : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface PKCE {
|
|
79
|
+
verifier: string;
|
|
80
|
+
challenge: string;
|
|
81
|
+
}
|
|
82
|
+
function describeTokenEndpointValue(value: unknown): string | undefined {
|
|
83
|
+
if (typeof value === "string") {
|
|
84
|
+
const trimmed = value.trim();
|
|
85
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
88
|
+
if (!isRecord(value)) return undefined;
|
|
89
|
+
|
|
90
|
+
const code = describeTokenEndpointValue(value.code ?? value.error);
|
|
91
|
+
const message = describeTokenEndpointValue(value.message ?? value.error_description ?? value.description);
|
|
92
|
+
if (code && message && code !== message) return `${code}: ${message}`;
|
|
93
|
+
return code ?? message ?? JSON.stringify(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Formats OpenAI Codex OAuth token endpoint errors for login and refresh failures. */
|
|
97
|
+
export function formatOpenAICodexTokenEndpointError(status: number, bodyText: string): string {
|
|
98
|
+
const trimmed = bodyText.trim();
|
|
99
|
+
if (trimmed.length === 0) return `${status}`;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const body: unknown = JSON.parse(trimmed);
|
|
103
|
+
if (!isRecord(body)) return `${status} ${trimmed}`;
|
|
104
|
+
|
|
105
|
+
const error = describeTokenEndpointValue(body.error);
|
|
106
|
+
const description = describeTokenEndpointValue(body.error_description);
|
|
107
|
+
if (error && description && error !== description) return `${status} ${error}: ${description}`;
|
|
108
|
+
return `${status} ${error ?? description ?? describeTokenEndpointValue(body.message) ?? trimmed}`;
|
|
109
|
+
} catch {
|
|
110
|
+
return `${status} ${trimmed}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Builds the Codex browser OAuth URL used by browser login; exported for auth regression tests. */
|
|
114
|
+
export function createOpenAICodexAuthorizationUrl(args: {
|
|
115
|
+
state: string;
|
|
116
|
+
redirectUri: string;
|
|
117
|
+
challenge: string;
|
|
118
|
+
originator?: string;
|
|
119
|
+
}): string {
|
|
120
|
+
const originator = args.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
121
|
+
const searchParams = new URLSearchParams({
|
|
122
|
+
response_type: "code",
|
|
123
|
+
client_id: CLIENT_ID,
|
|
124
|
+
redirect_uri: args.redirectUri,
|
|
125
|
+
scope: SCOPE,
|
|
126
|
+
code_challenge: args.challenge,
|
|
127
|
+
code_challenge_method: "S256",
|
|
128
|
+
state: args.state,
|
|
129
|
+
id_token_add_organizations: "true",
|
|
130
|
+
codex_cli_simplified_flow: "true",
|
|
131
|
+
originator,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return `${AUTHORIZE_URL}?${searchParams.toString()}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class OpenAICodexOAuthFlow extends OAuthCallbackFlow {
|
|
138
|
+
#pkce: PKCE;
|
|
139
|
+
#originator: string;
|
|
140
|
+
#fetch: FetchImpl;
|
|
141
|
+
|
|
142
|
+
constructor(ctrl: OAuthController, pkce: PKCE, originator: string, fetchImpl: FetchImpl) {
|
|
143
|
+
super(ctrl, {
|
|
144
|
+
preferredPort: CALLBACK_PORT,
|
|
145
|
+
callbackPath: CALLBACK_PATH,
|
|
146
|
+
// Enforce the fixed port: OpenAI only allows http://localhost:1455/auth/callback.
|
|
147
|
+
// Without this, a busy port 1455 falls back to a random port, and the token
|
|
148
|
+
// exchange would fail with 403 because the redirect_uri no longer matches the
|
|
149
|
+
// registered allowlist entry.
|
|
150
|
+
redirectUri: `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`,
|
|
151
|
+
} satisfies OAuthCallbackFlowOptions);
|
|
152
|
+
this.#pkce = pkce;
|
|
153
|
+
this.#originator = originator;
|
|
154
|
+
this.#fetch = fetchImpl;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
158
|
+
const url = createOpenAICodexAuthorizationUrl({
|
|
159
|
+
state,
|
|
160
|
+
redirectUri,
|
|
161
|
+
challenge: this.#pkce.challenge,
|
|
162
|
+
originator: this.#originator,
|
|
163
|
+
});
|
|
164
|
+
return { url, instructions: "A browser window should open. Complete login to finish." };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
|
|
168
|
+
return exchangeCodeForToken(code, this.#pkce.verifier, redirectUri, this.#fetch);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function exchangeCodeForToken(
|
|
173
|
+
code: string,
|
|
174
|
+
verifier: string,
|
|
175
|
+
redirectUri: string,
|
|
176
|
+
fetchImpl: FetchImpl = fetch,
|
|
177
|
+
): Promise<OAuthCredentials> {
|
|
178
|
+
const tokenResponse = await fetchImpl(TOKEN_URL, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
181
|
+
body: new URLSearchParams({
|
|
182
|
+
grant_type: "authorization_code",
|
|
183
|
+
client_id: CLIENT_ID,
|
|
184
|
+
code,
|
|
185
|
+
code_verifier: verifier,
|
|
186
|
+
redirect_uri: redirectUri,
|
|
187
|
+
}),
|
|
188
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!tokenResponse.ok) {
|
|
192
|
+
const bodyText = await tokenResponse.text();
|
|
193
|
+
throw new AIError.OAuthError(
|
|
194
|
+
`Token exchange failed: ${formatOpenAICodexTokenEndpointError(tokenResponse.status, bodyText)}`,
|
|
195
|
+
{ kind: "token-exchange", status: tokenResponse.status },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const tokenData = (await tokenResponse.json()) as {
|
|
200
|
+
access_token?: string;
|
|
201
|
+
refresh_token?: string;
|
|
202
|
+
id_token?: string;
|
|
203
|
+
expires_in?: number;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") {
|
|
207
|
+
throw new AIError.OAuthError("Token response missing required fields", { kind: "validation" });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { accountId, email, planType } = getTokenProfile(tokenData.access_token, tokenData.id_token);
|
|
211
|
+
if (!accountId) {
|
|
212
|
+
throw new AIError.OAuthError("Failed to extract accountId from token", { kind: "validation" });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
access: tokenData.access_token,
|
|
217
|
+
refresh: tokenData.refresh_token,
|
|
218
|
+
expires: Date.now() + tokenData.expires_in * 1000,
|
|
219
|
+
accountId,
|
|
220
|
+
email,
|
|
221
|
+
orgId: accountId,
|
|
222
|
+
orgName: planType,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Login with OpenAI Codex OAuth
|
|
228
|
+
*/
|
|
229
|
+
export type OpenAICodexLoginOptions = OAuthController & {
|
|
230
|
+
/** Optional originator value for OpenAI Codex OAuth. Default matches OMP Codex request headers. */
|
|
231
|
+
originator?: string;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export async function loginOpenAICodex(options: OpenAICodexLoginOptions): Promise<OAuthCredentials> {
|
|
235
|
+
const pkce = await generatePKCE();
|
|
236
|
+
const originator = options.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
237
|
+
const flow = new OpenAICodexOAuthFlow(options, pkce, originator, options.fetch ?? fetch);
|
|
238
|
+
|
|
239
|
+
return flow.login();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Login with OpenAI Codex using the device-code (headless) flow.
|
|
244
|
+
*
|
|
245
|
+
* Avoids a local callback server entirely — useful when port 1455 is unavailable
|
|
246
|
+
* or when the browser callback flow fails with 403 (e.g. network/proxy issues).
|
|
247
|
+
*/
|
|
248
|
+
export async function loginOpenAICodexDevice(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
249
|
+
ctrl.onProgress?.("Initiating device authorization…");
|
|
250
|
+
|
|
251
|
+
const initResponse = await fetch(DEVICE_USERCODE_URL, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: { "Content-Type": "application/json" },
|
|
254
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
255
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (!initResponse.ok) {
|
|
259
|
+
throw new AIError.OAuthError(`Device authorization initiation failed: ${initResponse.status}`, {
|
|
260
|
+
kind: "device-auth",
|
|
261
|
+
status: initResponse.status,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const initData = (await initResponse.json()) as {
|
|
266
|
+
device_auth_id?: string;
|
|
267
|
+
user_code?: string;
|
|
268
|
+
interval?: string | number;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (!initData.device_auth_id || !initData.user_code) {
|
|
272
|
+
throw new AIError.OAuthError("Device authorization response missing required fields", { kind: "validation" });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const userCode = initData.user_code;
|
|
276
|
+
const pollIntervalMs =
|
|
277
|
+
(typeof initData.interval === "number"
|
|
278
|
+
? initData.interval
|
|
279
|
+
: parseInt(String(initData.interval ?? "5"), 10) || 5) *
|
|
280
|
+
1000 +
|
|
281
|
+
DEVICE_POLL_SAFETY_MARGIN_MS;
|
|
282
|
+
|
|
283
|
+
ctrl.onAuth?.({
|
|
284
|
+
url: DEVICE_AUTH_URL,
|
|
285
|
+
instructions: `Enter code: ${userCode}`,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
ctrl.onProgress?.(`Waiting for browser authorization (code: ${userCode})…`);
|
|
289
|
+
|
|
290
|
+
for (let poll = 0; poll < DEVICE_MAX_POLLS; poll++) {
|
|
291
|
+
await Bun.sleep(poll === 0 ? Math.min(pollIntervalMs, DEVICE_POLL_INTERVAL_MS) : pollIntervalMs);
|
|
292
|
+
|
|
293
|
+
if (ctrl.signal?.aborted) {
|
|
294
|
+
throw new AIError.LoginCancelledError("Device authorization cancelled");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const pollResponse = await fetch(DEVICE_TOKEN_URL, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: { "Content-Type": "application/json" },
|
|
300
|
+
body: JSON.stringify({
|
|
301
|
+
device_auth_id: initData.device_auth_id,
|
|
302
|
+
user_code: userCode,
|
|
303
|
+
}),
|
|
304
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// 403/404 = authorization pending, keep polling
|
|
308
|
+
if (pollResponse.status === 403 || pollResponse.status === 404) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!pollResponse.ok) {
|
|
313
|
+
throw new AIError.OAuthError(`Device token polling failed: ${pollResponse.status}`, {
|
|
314
|
+
kind: "polling",
|
|
315
|
+
status: pollResponse.status,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const pollData = (await pollResponse.json()) as {
|
|
320
|
+
authorization_code?: string;
|
|
321
|
+
code_verifier?: string;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
if (!pollData.authorization_code || !pollData.code_verifier) {
|
|
325
|
+
throw new AIError.OAuthError("Device token response missing authorization_code or code_verifier", {
|
|
326
|
+
kind: "validation",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
ctrl.onProgress?.("Exchanging authorization code for tokens…");
|
|
331
|
+
return exchangeCodeForToken(pollData.authorization_code, pollData.code_verifier, DEVICE_REDIRECT_URI);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
throw new AIError.OAuthError("Device authorization timed out — user did not complete login in time", {
|
|
335
|
+
kind: "timeout",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Refresh OpenAI Codex OAuth token
|
|
341
|
+
*/
|
|
342
|
+
export async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {
|
|
343
|
+
const response = await fetch(TOKEN_URL, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
346
|
+
body: new URLSearchParams({
|
|
347
|
+
grant_type: "refresh_token",
|
|
348
|
+
refresh_token: refreshToken,
|
|
349
|
+
client_id: CLIENT_ID,
|
|
350
|
+
}),
|
|
351
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
const bodyText = await response.text();
|
|
356
|
+
throw new AIError.OAuthError(
|
|
357
|
+
`OpenAI Codex token refresh failed: ${formatOpenAICodexTokenEndpointError(response.status, bodyText)}`,
|
|
358
|
+
{ kind: "token-refresh", status: response.status },
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const tokenData = (await response.json()) as {
|
|
363
|
+
access_token?: string;
|
|
364
|
+
refresh_token?: string;
|
|
365
|
+
expires_in?: number;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") {
|
|
369
|
+
throw new AIError.OAuthError("Token response missing required fields", { kind: "validation" });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const { accountId, email } = getTokenProfile(tokenData.access_token);
|
|
373
|
+
|
|
374
|
+
// Deliberately no org fields on the result: the workspace a credential is
|
|
375
|
+
// scoped to is fixed at login. Callers merge refresh results over the
|
|
376
|
+
// stored credential, so omitting org here preserves it verbatim.
|
|
377
|
+
return {
|
|
378
|
+
access: tokenData.access_token,
|
|
379
|
+
refresh: tokenData.refresh_token || refreshToken,
|
|
380
|
+
expires: Date.now() + tokenData.expires_in * 1000,
|
|
381
|
+
accountId: accountId ?? undefined,
|
|
382
|
+
email,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Zen login flow.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode Zen is a subscription service that provides access to various AI models
|
|
5
|
+
* (GPT-5.x, Claude 4.x, Gemini 3, etc.) through a unified API at opencode.ai/zen.
|
|
6
|
+
* This is not OAuth - it's a simple API key flow:
|
|
7
|
+
* 1. Open browser to https://opencode.ai/auth
|
|
8
|
+
* 2. User logs in and copies their API key
|
|
9
|
+
* 3. User pastes the API key back into the CLI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as AIError from "../../error";
|
|
13
|
+
import type { OAuthController } from "./types";
|
|
14
|
+
|
|
15
|
+
const AUTH_URL = "https://opencode.ai/auth";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Login to OpenCode Zen.
|
|
19
|
+
*
|
|
20
|
+
* Opens browser to auth page, prompts user to paste their API key.
|
|
21
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
22
|
+
*/
|
|
23
|
+
export async function loginOpenCode(options: OAuthController): Promise<string> {
|
|
24
|
+
if (!options.onPrompt) {
|
|
25
|
+
throw new AIError.OnPromptRequiredError("OpenCode Zen");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Open browser to auth page
|
|
29
|
+
options.onAuth?.({
|
|
30
|
+
url: AUTH_URL,
|
|
31
|
+
instructions: "Log in and copy your API key",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Prompt user to paste their API key
|
|
35
|
+
const apiKey = await options.onPrompt({
|
|
36
|
+
message: "Paste your OpenCode Zen API key",
|
|
37
|
+
placeholder: "sk-...",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (options.signal?.aborted) {
|
|
41
|
+
throw new AIError.LoginCancelledError();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const trimmed = apiKey.trim();
|
|
45
|
+
if (!trimmed) {
|
|
46
|
+
throw new AIError.ApiKeyRequiredError();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perplexity login and token refresh.
|
|
3
|
+
*
|
|
4
|
+
* Login paths (in priority order):
|
|
5
|
+
* 1. macOS native app: reads JWT from NSUserDefaults (`defaults read ai.perplexity.mac authToken`)
|
|
6
|
+
* 2. HTTP email OTP: `GET /api/auth/csrf` → `POST /api/auth/signin-email` → `POST /api/auth/signin-otp`
|
|
7
|
+
*
|
|
8
|
+
* No browser or manual cookie paste required.
|
|
9
|
+
* Refresh: Socket.IO `refreshJWT` RPC over authenticated WebSocket connection.
|
|
10
|
+
*
|
|
11
|
+
* Protocol: Engine.IO v4 + Socket.IO v4 over WebSocket (bypasses Cloudflare managed challenge).
|
|
12
|
+
* Architecture reverse-engineered from Perplexity macOS app (ai.perplexity.mac).
|
|
13
|
+
*/
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import { $env } from "@linxiraos/pi-utils";
|
|
16
|
+
import { $ } from "bun";
|
|
17
|
+
import * as AIError from "../../error";
|
|
18
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
19
|
+
|
|
20
|
+
const API_VERSION = "2.18";
|
|
21
|
+
const NATIVE_APP_BUNDLE = "ai.perplexity.mac";
|
|
22
|
+
const APP_USER_AGENT = "Perplexity/641 CFNetwork/1568 Darwin/25.2.0";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// JWT helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract expiry from a JWT. Perplexity tokens generally lack an `exp` claim
|
|
30
|
+
* (their sessions are server-side and effectively non-expiring from the client's
|
|
31
|
+
* point of view), so we return a far-future sentinel when no `exp` is present.
|
|
32
|
+
* When `exp` IS present, subtract a 5-minute safety margin.
|
|
33
|
+
*/
|
|
34
|
+
const NEVER_EXPIRES = 8.64e15; // max safe Date value
|
|
35
|
+
function getJwtExpiry(token: string): number {
|
|
36
|
+
try {
|
|
37
|
+
const parts = token.split(".");
|
|
38
|
+
if (parts.length !== 3) return NEVER_EXPIRES;
|
|
39
|
+
const payload = parts[1] ?? "";
|
|
40
|
+
const decoded = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
41
|
+
if (typeof decoded?.exp === "number" && Number.isFinite(decoded.exp)) {
|
|
42
|
+
return decoded.exp * 1000 - 5 * 60_000;
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Ignore decode errors
|
|
46
|
+
}
|
|
47
|
+
return NEVER_EXPIRES;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Build OAuthCredentials from a Perplexity JWT string. */
|
|
51
|
+
function jwtToCredentials(jwt: string, email?: string): OAuthCredentials {
|
|
52
|
+
return {
|
|
53
|
+
access: jwt,
|
|
54
|
+
refresh: jwt,
|
|
55
|
+
expires: getJwtExpiry(jwt),
|
|
56
|
+
email,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Desktop app extraction
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read the Perplexity JWT from the native macOS Catalyst app's UserDefaults.
|
|
66
|
+
* Tokens are stored in NSUserDefaults (not Keychain), readable by any same-UID process.
|
|
67
|
+
*/
|
|
68
|
+
async function extractFromNativeApp(): Promise<string | null> {
|
|
69
|
+
if (os.platform() !== "darwin") return null;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await $`defaults read ${NATIVE_APP_BUNDLE} authToken`.quiet().nothrow();
|
|
73
|
+
if (result.exitCode !== 0) return null;
|
|
74
|
+
const token = result.text().trim();
|
|
75
|
+
if (!token || token === "(null)") return null;
|
|
76
|
+
return token;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Socket.IO email OTP login
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send email OTP and exchange it for a Perplexity JWT via HTTP endpoints.
|
|
88
|
+
*/
|
|
89
|
+
async function httpEmailLogin(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
90
|
+
if (!ctrl.onPrompt) {
|
|
91
|
+
throw new AIError.OnPromptRequiredError("Perplexity");
|
|
92
|
+
}
|
|
93
|
+
const email = await ctrl.onPrompt({
|
|
94
|
+
message: "Enter your Perplexity email address",
|
|
95
|
+
placeholder: "user@example.com",
|
|
96
|
+
});
|
|
97
|
+
const trimmedEmail = email.trim();
|
|
98
|
+
if (!trimmedEmail)
|
|
99
|
+
throw new AIError.OAuthError("Email is required for Perplexity login", {
|
|
100
|
+
kind: "validation",
|
|
101
|
+
provider: "perplexity",
|
|
102
|
+
});
|
|
103
|
+
if (ctrl.signal?.aborted) throw new AIError.LoginCancelledError();
|
|
104
|
+
|
|
105
|
+
ctrl.onProgress?.("Fetching Perplexity CSRF token...");
|
|
106
|
+
const csrfResponse = await fetch("https://www.perplexity.ai/api/auth/csrf", {
|
|
107
|
+
headers: {
|
|
108
|
+
"User-Agent": APP_USER_AGENT,
|
|
109
|
+
"X-App-ApiVersion": API_VERSION,
|
|
110
|
+
},
|
|
111
|
+
signal: ctrl.signal,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!csrfResponse.ok) {
|
|
115
|
+
throw new AIError.ProviderHttpError(
|
|
116
|
+
`Perplexity CSRF request failed: ${csrfResponse.status}`,
|
|
117
|
+
csrfResponse.status,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const csrfData = (await csrfResponse.json()) as { csrfToken?: string };
|
|
122
|
+
if (!csrfData.csrfToken) {
|
|
123
|
+
throw new AIError.OAuthError("Perplexity CSRF response missing csrfToken", {
|
|
124
|
+
kind: "validation",
|
|
125
|
+
provider: "perplexity",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
ctrl.onProgress?.("Sending login code to your email...");
|
|
129
|
+
const sendResponse = await fetch("https://www.perplexity.ai/api/auth/signin-email", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
"User-Agent": APP_USER_AGENT,
|
|
134
|
+
"X-App-ApiVersion": API_VERSION,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
email: trimmedEmail,
|
|
138
|
+
csrfToken: csrfData.csrfToken,
|
|
139
|
+
}),
|
|
140
|
+
signal: ctrl.signal,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!sendResponse.ok) {
|
|
144
|
+
const body = await sendResponse.text();
|
|
145
|
+
throw new AIError.ProviderHttpError(
|
|
146
|
+
`Perplexity send login code failed (${sendResponse.status}): ${body}`,
|
|
147
|
+
sendResponse.status,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const otp = await ctrl.onPrompt({
|
|
151
|
+
message: "Enter the code sent to your email",
|
|
152
|
+
placeholder: "123456",
|
|
153
|
+
});
|
|
154
|
+
const trimmedOtp = otp.trim();
|
|
155
|
+
if (!trimmedOtp)
|
|
156
|
+
throw new AIError.OAuthError("OTP code is required", { kind: "validation", provider: "perplexity" });
|
|
157
|
+
if (ctrl.signal?.aborted) throw new AIError.LoginCancelledError();
|
|
158
|
+
ctrl.onProgress?.("Verifying login code...");
|
|
159
|
+
const verifyResponse = await fetch("https://www.perplexity.ai/api/auth/signin-otp", {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: {
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
"User-Agent": APP_USER_AGENT,
|
|
164
|
+
"X-App-ApiVersion": API_VERSION,
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
email: trimmedEmail,
|
|
168
|
+
otp: trimmedOtp,
|
|
169
|
+
csrfToken: csrfData.csrfToken,
|
|
170
|
+
}),
|
|
171
|
+
signal: ctrl.signal,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const verifyData = (await verifyResponse.json()) as {
|
|
175
|
+
token?: string;
|
|
176
|
+
status?: string;
|
|
177
|
+
error_code?: string;
|
|
178
|
+
text?: string;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (!verifyResponse.ok) {
|
|
182
|
+
const reason = verifyData.text ?? verifyData.error_code ?? verifyData.status ?? "OTP verification failed";
|
|
183
|
+
throw new AIError.OAuthError(`Perplexity OTP verification failed: ${reason}`, {
|
|
184
|
+
kind: "validation",
|
|
185
|
+
provider: "perplexity",
|
|
186
|
+
status: verifyResponse.status,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!verifyData.token) {
|
|
191
|
+
throw new AIError.OAuthError("Perplexity OTP verification response missing token", {
|
|
192
|
+
kind: "validation",
|
|
193
|
+
provider: "perplexity",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return jwtToCredentials(verifyData.token, trimmedEmail);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Public API
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Login to Perplexity.
|
|
206
|
+
*
|
|
207
|
+
* Tries auto-extraction from the desktop app, then runs HTTP email OTP login.
|
|
208
|
+
*
|
|
209
|
+
* No browser/manual token paste fallback is used.
|
|
210
|
+
*/
|
|
211
|
+
export async function loginPerplexity(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
212
|
+
if (!ctrl.onPrompt) {
|
|
213
|
+
throw new AIError.OnPromptRequiredError("Perplexity");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Path 1: Native macOS app JWT (skip if PI_AUTH_NO_BORROW=1)
|
|
217
|
+
if (!$env.PI_AUTH_NO_BORROW) {
|
|
218
|
+
ctrl.onProgress?.("Checking for Perplexity desktop app...");
|
|
219
|
+
const nativeJwt = await extractFromNativeApp();
|
|
220
|
+
if (nativeJwt) {
|
|
221
|
+
ctrl.onProgress?.("Found Perplexity JWT from native app");
|
|
222
|
+
return jwtToCredentials(nativeJwt);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Path 2: HTTP email OTP
|
|
227
|
+
return httpEmailLogin(ctrl);
|
|
228
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate PKCE code verifier and challenge.
|
|
3
|
+
* Uses Web Crypto API for cross-platform compatibility.
|
|
4
|
+
*/
|
|
5
|
+
export async function generatePKCE(): Promise<{ verifier: string; challenge: string }> {
|
|
6
|
+
// Generate random verifier
|
|
7
|
+
const verifierBytes = new Uint8Array(96);
|
|
8
|
+
crypto.getRandomValues(verifierBytes);
|
|
9
|
+
const verifier = Buffer.from(verifierBytes).toString("base64url");
|
|
10
|
+
|
|
11
|
+
// Compute SHA-256 challenge
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const data = encoder.encode(verifier);
|
|
14
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
15
|
+
const challenge = Buffer.from(hashBuffer).toString("base64url");
|
|
16
|
+
|
|
17
|
+
return { verifier, challenge };
|
|
18
|
+
}
|