@oh-my-pi/pi-ai 16.2.1 → 16.2.2
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 +27 -0
- package/dist/types/auth-gateway/server.d.ts +0 -19
- package/dist/types/auth-retry.d.ts +2 -10
- package/dist/types/auth-storage.d.ts +1 -2
- package/dist/types/dialect/demotion.d.ts +22 -0
- package/dist/types/dialect/index.d.ts +2 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +16 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +23 -0
- package/dist/types/error/classes.d.ts +102 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +70 -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 +13 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/{rate-limit-utils.d.ts → error/rate-limit.d.ts} +14 -1
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +1 -3
- package/dist/types/providers/amazon-bedrock.d.ts +0 -5
- package/dist/types/providers/anthropic-client.d.ts +2 -16
- package/dist/types/providers/anthropic.d.ts +6 -1
- package/dist/types/providers/aws-eventstream.d.ts +2 -1
- package/dist/types/providers/cursor.d.ts +8 -7
- package/dist/types/providers/google-gemini-cli.d.ts +0 -5
- package/dist/types/providers/google-shared.d.ts +0 -5
- package/dist/types/providers/ollama.d.ts +0 -5
- package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -2
- package/dist/types/providers/openai-codex/response-handler.d.ts +1 -1
- package/dist/types/providers/openai-codex-responses.d.ts +2 -2
- package/dist/types/providers/openai-responses-server-schema.d.ts +6 -0
- package/dist/types/providers/openai-responses-wire.d.ts +1 -1
- package/dist/types/providers/openai-responses.d.ts +1 -0
- package/dist/types/providers/openai-shared.d.ts +8 -6
- package/dist/types/providers/pi-native-client.d.ts +0 -9
- package/dist/types/registry/oauth/xai-oauth.d.ts +0 -9
- package/dist/types/types.d.ts +6 -0
- package/dist/types/utils/block-symbols.d.ts +20 -0
- package/dist/types/utils/openai-http.d.ts +4 -8
- package/dist/types/utils/retry.d.ts +2 -14
- package/dist/types/utils/thinking-loop.d.ts +3 -1
- package/package.json +8 -4
- package/src/api-registry.ts +3 -1
- package/src/auth-broker/discover.ts +7 -2
- package/src/auth-broker/remote-store.ts +8 -7
- package/src/auth-gateway/server.ts +27 -115
- package/src/auth-retry.ts +9 -21
- package/src/auth-storage.ts +34 -49
- package/src/dialect/demotion.ts +31 -0
- package/src/dialect/factory.ts +0 -2
- package/src/dialect/index.ts +2 -0
- package/src/dialect/owned-stream.ts +0 -1
- package/src/dialect/thinking.ts +22 -10
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +30 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +31 -0
- package/src/error/classes.ts +186 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +486 -0
- package/src/error/format.ts +36 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +13 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +56 -0
- package/src/{rate-limit-utils.ts → error/rate-limit.ts} +9 -3
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +1 -3
- package/src/providers/amazon-bedrock.ts +61 -57
- package/src/providers/anthropic-client.ts +13 -41
- package/src/providers/anthropic-messages-server.ts +9 -2
- package/src/providers/anthropic.ts +84 -147
- package/src/providers/aws-credentials.ts +41 -11
- package/src/providers/aws-eventstream.ts +12 -21
- package/src/providers/azure-openai-responses.ts +27 -17
- package/src/providers/cursor.ts +59 -53
- package/src/providers/devin.ts +28 -20
- package/src/providers/gitlab-duo-workflow.ts +30 -10
- package/src/providers/gitlab-duo.ts +22 -6
- package/src/providers/google-auth.ts +15 -5
- package/src/providers/google-gemini-cli.ts +46 -60
- package/src/providers/google-shared.ts +40 -38
- package/src/providers/google-vertex.ts +3 -2
- package/src/providers/google.ts +5 -1
- package/src/providers/mock.ts +10 -7
- package/src/providers/ollama.ts +34 -29
- package/src/providers/openai-chat-server.ts +2 -1
- package/src/providers/openai-codex/request-transformer.ts +13 -12
- package/src/providers/openai-codex/response-handler.ts +1 -1
- package/src/providers/openai-codex-responses.ts +931 -1012
- package/src/providers/openai-completions.ts +46 -36
- package/src/providers/openai-responses-server-schema.ts +3 -0
- package/src/providers/openai-responses-server.ts +82 -30
- package/src/providers/openai-responses-wire.ts +1 -1
- package/src/providers/openai-responses.ts +46 -22
- package/src/providers/openai-shared.ts +118 -51
- package/src/providers/pi-native-client.ts +12 -18
- package/src/providers/pi-native-server.ts +9 -6
- package/src/providers/register-builtins.ts +5 -2
- package/src/providers/transform-messages.ts +31 -63
- package/src/registry/alibaba-coding-plan.ts +6 -5
- package/src/registry/api-key-login.ts +4 -3
- package/src/registry/api-key-validation.ts +4 -3
- package/src/registry/cloudflare-ai-gateway.ts +4 -3
- package/src/registry/coreweave.ts +2 -1
- package/src/registry/deepseek.ts +2 -1
- package/src/registry/google-antigravity.ts +2 -1
- package/src/registry/google-gemini-cli.ts +2 -1
- package/src/registry/kagi.ts +4 -3
- package/src/registry/kilo.ts +32 -10
- package/src/registry/litellm.ts +4 -3
- package/src/registry/llama-cpp.ts +3 -2
- package/src/registry/lm-studio.ts +3 -2
- package/src/registry/nvidia.ts +7 -7
- package/src/registry/oauth/anthropic.ts +23 -6
- package/src/registry/oauth/callback-server.ts +5 -3
- package/src/registry/oauth/cursor.ts +18 -4
- package/src/registry/oauth/devin.ts +14 -3
- package/src/registry/oauth/github-copilot.ts +21 -10
- package/src/registry/oauth/gitlab-duo-workflow.ts +15 -3
- package/src/registry/oauth/gitlab-duo.ts +30 -6
- package/src/registry/oauth/google-antigravity.ts +14 -5
- package/src/registry/oauth/google-gemini-cli.ts +24 -7
- package/src/registry/oauth/google-oauth-shared.ts +6 -3
- package/src/registry/oauth/index.ts +19 -8
- package/src/registry/oauth/kimi.ts +40 -10
- package/src/registry/oauth/openai-codex.ts +26 -11
- package/src/registry/oauth/opencode.ts +4 -3
- package/src/registry/oauth/perplexity.ts +33 -11
- package/src/registry/oauth/xai-oauth.ts +62 -18
- package/src/registry/oauth/xiaomi.ts +21 -9
- package/src/registry/ollama-cloud.ts +5 -4
- package/src/registry/ollama.ts +3 -2
- package/src/registry/parallel.ts +4 -3
- package/src/registry/qwen-portal.ts +4 -3
- package/src/registry/tavily.ts +4 -3
- package/src/registry/vercel-ai-gateway.ts +4 -3
- package/src/registry/vllm.ts +3 -2
- package/src/stream.ts +79 -24
- package/src/types.ts +6 -0
- package/src/usage/claude.ts +2 -1
- package/src/usage/github-copilot.ts +4 -3
- package/src/usage/google-antigravity.ts +2 -1
- package/src/utils/abort.ts +4 -2
- package/src/utils/block-symbols.ts +32 -0
- package/src/utils/event-stream.ts +15 -3
- package/src/utils/http-inspector.ts +4 -4
- package/src/utils/idle-iterator.ts +6 -5
- package/src/utils/openai-http.ts +11 -46
- package/src/utils/parse-bind.ts +7 -5
- package/src/utils/proxy.ts +2 -1
- package/src/utils/retry.ts +7 -23
- package/src/utils/schema/normalize.ts +3 -2
- package/src/utils/thinking-loop.ts +15 -11
- package/src/utils/validation.ts +4 -3
- package/dist/types/dialect/pi.d.ts +0 -9
- package/dist/types/errors.d.ts +0 -24
- package/dist/types/utils/overflow.d.ts +0 -55
- package/src/dialect/pi.md +0 -55
- package/src/dialect/pi.ts +0 -600
- package/src/errors.ts +0 -32
- package/src/utils/overflow.ts +0 -140
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import * as apiKeyValidation from "./api-key-validation";
|
|
2
3
|
import type { OAuthController, OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
|
|
3
4
|
import type { ProviderDefinition } from "./types";
|
|
@@ -10,7 +11,7 @@ const VALIDATION_MODEL = "qwen3.5-plus";
|
|
|
10
11
|
|
|
11
12
|
export async function loginAlibabaCodingPlan(options: OAuthController): Promise<OAuthCredentials> {
|
|
12
13
|
if (!options.onPrompt) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new AIError.OnPromptRequiredError("Alibaba Coding Plan");
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
// Ask which endpoint to use
|
|
@@ -21,7 +22,7 @@ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<
|
|
|
21
22
|
|
|
22
23
|
// Check for abort after endpoint selection (Escape returns "")
|
|
23
24
|
if (options.signal?.aborted) {
|
|
24
|
-
throw new
|
|
25
|
+
throw new AIError.LoginCancelledError();
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const choice = endpointChoice.trim();
|
|
@@ -39,7 +40,7 @@ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<
|
|
|
39
40
|
});
|
|
40
41
|
const trimmedUrl = customUrl.trim().replace(/\/+$/, "");
|
|
41
42
|
if (!trimmedUrl) {
|
|
42
|
-
throw new
|
|
43
|
+
throw new AIError.ConfigurationError("Custom URL is required for option 3");
|
|
43
44
|
}
|
|
44
45
|
baseUrl = trimmedUrl;
|
|
45
46
|
authUrl = DEFAULT_AUTH_URL;
|
|
@@ -61,12 +62,12 @@ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<
|
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
if (options.signal?.aborted) {
|
|
64
|
-
throw new
|
|
65
|
+
throw new AIError.LoginCancelledError();
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
const trimmed = apiKey.trim();
|
|
68
69
|
if (!trimmed) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new AIError.ApiKeyRequiredError();
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
options.onProgress?.("Validating API key...");
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* optionally validate it, and return the trimmed key.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import * as AIError from "../error";
|
|
9
10
|
import {
|
|
10
11
|
validateAnthropicCompatibleApiKey,
|
|
11
12
|
validateApiKeyAgainstModelsEndpoint,
|
|
@@ -52,7 +53,7 @@ export type ApiKeyLoginConfig = {
|
|
|
52
53
|
export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthController) => Promise<string> {
|
|
53
54
|
return async function login(options: OAuthController): Promise<string> {
|
|
54
55
|
if (!options.onPrompt) {
|
|
55
|
-
throw new
|
|
56
|
+
throw new AIError.OnPromptRequiredError(config.providerLabel);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
options.onAuth?.({
|
|
@@ -66,12 +67,12 @@ export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthCon
|
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
if (options.signal?.aborted) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new AIError.LoginCancelledError();
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const trimmed = apiKey.trim();
|
|
73
74
|
if (!trimmed) {
|
|
74
|
-
throw new
|
|
75
|
+
throw new AIError.ApiKeyRequiredError();
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
if (config.validation) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { FetchImpl } from "../types";
|
|
2
3
|
|
|
3
4
|
type OpenAICompatibleValidationOptions = {
|
|
@@ -78,7 +79,7 @@ export async function validateOpenAICompatibleApiKey(options: OpenAICompatibleVa
|
|
|
78
79
|
const message = details
|
|
79
80
|
? `${options.provider} API key validation failed (${response.status}): ${details}`
|
|
80
81
|
: `${options.provider} API key validation failed (${response.status})`;
|
|
81
|
-
throw new
|
|
82
|
+
throw new AIError.ApiKeyRequiredError(message);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -119,7 +120,7 @@ export async function validateAnthropicCompatibleApiKey(options: AnthropicCompat
|
|
|
119
120
|
const message = details
|
|
120
121
|
? `${options.provider} API key validation failed (${response.status}): ${details}`
|
|
121
122
|
: `${options.provider} API key validation failed (${response.status})`;
|
|
122
|
-
throw new
|
|
123
|
+
throw new AIError.ApiKeyRequiredError(message);
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
/**
|
|
@@ -156,5 +157,5 @@ export async function validateApiKeyAgainstModelsEndpoint(options: ModelListVali
|
|
|
156
157
|
const message = details
|
|
157
158
|
? `${options.provider} API key validation failed (${response.status}): ${details}`
|
|
158
159
|
: `${options.provider} API key validation failed (${response.status})`;
|
|
159
|
-
throw new
|
|
160
|
+
throw new AIError.ApiKeyRequiredError(message);
|
|
160
161
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ const AUTH_URL = "https://developers.cloudflare.com/ai-gateway/configuration/aut
|
|
|
11
12
|
*/
|
|
12
13
|
export async function loginCloudflareAiGateway(options: OAuthController): Promise<string> {
|
|
13
14
|
if (!options.onPrompt) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new AIError.OnPromptRequiredError("Cloudflare AI Gateway");
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
options.onAuth?.({
|
|
@@ -26,12 +27,12 @@ export async function loginCloudflareAiGateway(options: OAuthController): Promis
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
if (options.signal?.aborted) {
|
|
29
|
-
throw new
|
|
30
|
+
throw new AIError.LoginCancelledError();
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const trimmed = apiKey.trim();
|
|
33
34
|
if (!trimmed) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new AIError.ApiKeyRequiredError();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
return trimmed;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { coreWeaveProjectHeaders } from "@oh-my-pi/pi-catalog/wire/coreweave";
|
|
2
2
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import * as AIError from "../error";
|
|
3
4
|
import { createApiKeyLogin } from "./api-key-login";
|
|
4
5
|
import type { OAuthLoginCallbacks } from "./oauth/types";
|
|
5
6
|
import type { ProviderDefinition } from "./types";
|
|
@@ -10,7 +11,7 @@ const PROJECT_SETUP_INSTRUCTIONS =
|
|
|
10
11
|
function requireCoreWeaveProjectHeaders(): Record<string, string> {
|
|
11
12
|
const headers = coreWeaveProjectHeaders($env);
|
|
12
13
|
if (!headers) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new AIError.ConfigurationError(
|
|
14
15
|
"CoreWeave Serverless Inference requires OpenAI-Project. Set COREWEAVE_PROJECT=<team>/<project> before running /login coreweave.",
|
|
15
16
|
);
|
|
16
17
|
}
|
package/src/registry/deepseek.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import { createApiKeyLogin } from "./api-key-login";
|
|
2
3
|
import type { OAuthController, OAuthLoginCallbacks, OAuthPrompt } from "./oauth/types";
|
|
3
4
|
import type { ProviderDefinition } from "./types";
|
|
@@ -22,7 +23,7 @@ export function normalizeDeepSeekApiKey(raw: string): string {
|
|
|
22
23
|
}
|
|
23
24
|
const stripped = trimmed.replace(/^bearer\b\s*/i, "");
|
|
24
25
|
if (!stripped) {
|
|
25
|
-
throw new
|
|
26
|
+
throw new AIError.ApiKeyRequiredError("DeepSeek API key is empty after stripping Bearer prefix");
|
|
26
27
|
}
|
|
27
28
|
return stripped;
|
|
28
29
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ export const googleAntigravityProvider = {
|
|
|
11
12
|
},
|
|
12
13
|
refreshToken: async (credentials: OAuthCredentials) => {
|
|
13
14
|
if (!credentials.projectId) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new AIError.ConfigurationError("Antigravity credentials missing projectId");
|
|
15
16
|
}
|
|
16
17
|
const { refreshAntigravityToken } = await import("./oauth/google-antigravity");
|
|
17
18
|
return refreshAntigravityToken(credentials.refresh, credentials.projectId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ export const googleGeminiCliProvider = {
|
|
|
11
12
|
},
|
|
12
13
|
refreshToken: async (credentials: OAuthCredentials) => {
|
|
13
14
|
if (!credentials.projectId) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new AIError.ConfigurationError("Google Cloud credentials missing projectId");
|
|
15
16
|
}
|
|
16
17
|
const { refreshGoogleCloudToken } = await import("./oauth/google-gemini-cli");
|
|
17
18
|
return refreshGoogleCloudToken(credentials.refresh, credentials.projectId);
|
package/src/registry/kagi.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ const AUTH_URL = "https://kagi.com/settings/api";
|
|
|
11
12
|
*/
|
|
12
13
|
export async function loginKagi(options: OAuthController): Promise<string> {
|
|
13
14
|
if (!options.onPrompt) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new AIError.OnPromptRequiredError("Kagi");
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
options.onAuth?.({
|
|
@@ -26,12 +27,12 @@ export async function loginKagi(options: OAuthController): Promise<string> {
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
if (options.signal?.aborted) {
|
|
29
|
-
throw new
|
|
30
|
+
throw new AIError.LoginCancelledError();
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const trimmed = apiKey.trim();
|
|
33
34
|
if (!trimmed) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new AIError.ApiKeyRequiredError();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
return trimmed;
|
package/src/registry/kilo.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthCredentials } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -25,9 +26,17 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
|
|
|
25
26
|
|
|
26
27
|
if (!initiateResponse.ok) {
|
|
27
28
|
if (initiateResponse.status === 429) {
|
|
28
|
-
throw new
|
|
29
|
+
throw new AIError.OAuthError("Too many pending authorization requests. Please try again later.", {
|
|
30
|
+
kind: "polling",
|
|
31
|
+
provider: "kilo",
|
|
32
|
+
status: initiateResponse.status,
|
|
33
|
+
});
|
|
29
34
|
}
|
|
30
|
-
throw new
|
|
35
|
+
throw new AIError.OAuthError(`Failed to initiate device authorization: ${initiateResponse.status}`, {
|
|
36
|
+
kind: "device-auth",
|
|
37
|
+
provider: "kilo",
|
|
38
|
+
status: initiateResponse.status,
|
|
39
|
+
});
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
const initiateData = (await initiateResponse.json()) as KiloDeviceAuthCodeResponse;
|
|
@@ -35,7 +44,10 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
|
|
|
35
44
|
const verificationUrl = initiateData.verificationUrl;
|
|
36
45
|
const expiresInSeconds = initiateData.expiresIn;
|
|
37
46
|
if (!userCode || !verificationUrl || typeof expiresInSeconds !== "number" || expiresInSeconds <= 0) {
|
|
38
|
-
throw new
|
|
47
|
+
throw new AIError.OAuthError("Kilo device authorization response missing required fields", {
|
|
48
|
+
kind: "validation",
|
|
49
|
+
provider: "kilo",
|
|
50
|
+
});
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
callbacks.onAuth?.({
|
|
@@ -46,7 +58,7 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
|
|
|
46
58
|
const deadline = Date.now() + expiresInSeconds * 1000;
|
|
47
59
|
while (Date.now() < deadline) {
|
|
48
60
|
if (callbacks.signal?.aborted) {
|
|
49
|
-
throw new
|
|
61
|
+
throw new AIError.LoginCancelledError();
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
const pollResponse = await fetchImpl(`${KILO_DEVICE_AUTH_BASE_URL}/codes/${encodeURIComponent(userCode)}`);
|
|
@@ -55,13 +67,20 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
|
|
|
55
67
|
continue;
|
|
56
68
|
}
|
|
57
69
|
if (pollResponse.status === 403) {
|
|
58
|
-
throw new
|
|
70
|
+
throw new AIError.OAuthError("Authorization was denied", { kind: "device-auth", provider: "kilo" });
|
|
59
71
|
}
|
|
60
72
|
if (pollResponse.status === 410) {
|
|
61
|
-
throw new
|
|
73
|
+
throw new AIError.OAuthError("Authorization code expired. Please try again.", {
|
|
74
|
+
kind: "device-auth",
|
|
75
|
+
provider: "kilo",
|
|
76
|
+
});
|
|
62
77
|
}
|
|
63
78
|
if (!pollResponse.ok) {
|
|
64
|
-
throw new
|
|
79
|
+
throw new AIError.OAuthError(`Failed to poll device authorization: ${pollResponse.status}`, {
|
|
80
|
+
kind: "polling",
|
|
81
|
+
provider: "kilo",
|
|
82
|
+
status: pollResponse.status,
|
|
83
|
+
});
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
const pollData = (await pollResponse.json()) as KiloDeviceAuthPollResponse;
|
|
@@ -73,16 +92,19 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
|
|
|
73
92
|
};
|
|
74
93
|
}
|
|
75
94
|
if (pollData.status === "denied") {
|
|
76
|
-
throw new
|
|
95
|
+
throw new AIError.OAuthError("Authorization was denied", { kind: "device-auth", provider: "kilo" });
|
|
77
96
|
}
|
|
78
97
|
if (pollData.status === "expired") {
|
|
79
|
-
throw new
|
|
98
|
+
throw new AIError.OAuthError("Authorization code expired. Please try again.", {
|
|
99
|
+
kind: "device-auth",
|
|
100
|
+
provider: "kilo",
|
|
101
|
+
});
|
|
80
102
|
}
|
|
81
103
|
|
|
82
104
|
await Bun.sleep(POLL_INTERVAL_MS);
|
|
83
105
|
}
|
|
84
106
|
|
|
85
|
-
throw new
|
|
107
|
+
throw new AIError.OAuthError("Authentication timed out. Please try again.", { kind: "timeout", provider: "kilo" });
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
export const kiloProvider = {
|
package/src/registry/litellm.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ const AUTH_URL = "https://docs.litellm.ai/docs/proxy/deploy";
|
|
|
11
12
|
*/
|
|
12
13
|
export async function loginLiteLLM(options: OAuthController): Promise<string> {
|
|
13
14
|
if (!options.onPrompt) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new AIError.OnPromptRequiredError("LiteLLM");
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
options.onAuth?.({
|
|
@@ -26,12 +27,12 @@ export async function loginLiteLLM(options: OAuthController): Promise<string> {
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
if (options.signal?.aborted) {
|
|
29
|
-
throw new
|
|
30
|
+
throw new AIError.LoginCancelledError();
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const trimmed = apiKey.trim();
|
|
33
34
|
if (!trimmed) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new AIError.ApiKeyRequiredError();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
return trimmed;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -8,7 +9,7 @@ const DEFAULT_LOCAL_TOKEN = "llama-cpp-local";
|
|
|
8
9
|
|
|
9
10
|
export async function loginLlamaCpp(options: OAuthController): Promise<string> {
|
|
10
11
|
if (!options.onPrompt) {
|
|
11
|
-
throw new
|
|
12
|
+
throw new AIError.OnPromptRequiredError(PROVIDER_ID);
|
|
12
13
|
}
|
|
13
14
|
options.onAuth?.({
|
|
14
15
|
url: AUTH_URL,
|
|
@@ -20,7 +21,7 @@ export async function loginLlamaCpp(options: OAuthController): Promise<string> {
|
|
|
20
21
|
allowEmpty: true,
|
|
21
22
|
});
|
|
22
23
|
if (options.signal?.aborted) {
|
|
23
|
-
throw new
|
|
24
|
+
throw new AIError.LoginCancelledError();
|
|
24
25
|
}
|
|
25
26
|
const trimmed = apiKey.trim();
|
|
26
27
|
return trimmed || DEFAULT_LOCAL_TOKEN;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
3
|
import type { ProviderDefinition } from "./types";
|
|
3
4
|
|
|
@@ -6,7 +7,7 @@ export const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
|
|
|
6
7
|
|
|
7
8
|
export async function loginLmStudio(options: OAuthController): Promise<string> {
|
|
8
9
|
if (!options.onPrompt) {
|
|
9
|
-
throw new
|
|
10
|
+
throw new AIError.OnPromptRequiredError(PROVIDER_ID);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const apiKey = await options.onPrompt({
|
|
@@ -16,7 +17,7 @@ export async function loginLmStudio(options: OAuthController): Promise<string> {
|
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
if (options.signal?.aborted) {
|
|
19
|
-
throw new
|
|
20
|
+
throw new AIError.LoginCancelledError();
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const trimmed = apiKey.trim();
|
package/src/registry/nvidia.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../error";
|
|
1
2
|
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
|
|
2
3
|
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
3
4
|
import type { ProviderDefinition } from "./types";
|
|
@@ -9,7 +10,7 @@ const PROVIDER_ID = "nvidia";
|
|
|
9
10
|
|
|
10
11
|
export async function loginNvidia(options: OAuthController): Promise<string> {
|
|
11
12
|
if (!options.onPrompt) {
|
|
12
|
-
throw new
|
|
13
|
+
throw new AIError.OnPromptRequiredError("NVIDIA");
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
options.onAuth?.({
|
|
@@ -23,12 +24,12 @@ export async function loginNvidia(options: OAuthController): Promise<string> {
|
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
if (options.signal?.aborted) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new AIError.LoginCancelledError();
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const trimmed = apiKey.trim();
|
|
30
31
|
if (!trimmed) {
|
|
31
|
-
throw new
|
|
32
|
+
throw new AIError.ApiKeyRequiredError();
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
options.onProgress?.("Validating API key (optional)...");
|
|
@@ -42,10 +43,9 @@ export async function loginNvidia(options: OAuthController): Promise<string> {
|
|
|
42
43
|
fetch: options.fetch,
|
|
43
44
|
});
|
|
44
45
|
} catch (error) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (statusCode === "401" || statusCode === "403") {
|
|
46
|
+
// A real auth rejection (401/403) is fatal; any other validation-endpoint
|
|
47
|
+
// failure is non-fatal — skip validation and trust the supplied key.
|
|
48
|
+
if (AIError.is(AIError.classify(error), AIError.Flag.AuthFailed)) {
|
|
49
49
|
throw error;
|
|
50
50
|
}
|
|
51
51
|
options.onProgress?.("Skipping NVIDIA validation endpoint; continuing with provided API key.");
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Anthropic OAuth flow (Claude Pro/Max)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as AIError from "../../error";
|
|
5
6
|
import { claudeCodeVersion } from "../../providers/anthropic";
|
|
6
7
|
import type { FetchImpl } from "../../types";
|
|
7
8
|
import { OAuthCallbackFlow } from "./callback-server";
|
|
@@ -59,7 +60,10 @@ async function postJson(
|
|
|
59
60
|
|
|
60
61
|
const responseBody = await response.text();
|
|
61
62
|
if (!response.ok) {
|
|
62
|
-
throw new
|
|
63
|
+
throw new AIError.ProviderHttpError(
|
|
64
|
+
`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`,
|
|
65
|
+
response.status,
|
|
66
|
+
);
|
|
63
67
|
}
|
|
64
68
|
return responseBody;
|
|
65
69
|
}
|
|
@@ -88,8 +92,9 @@ function parseOAuthTokenResponse(responseBody: string, operation: string): Anthr
|
|
|
88
92
|
try {
|
|
89
93
|
return JSON.parse(responseBody) as AnthropicTokenResponse;
|
|
90
94
|
} catch (error) {
|
|
91
|
-
throw new
|
|
95
|
+
throw new AIError.OAuthError(
|
|
92
96
|
`Anthropic ${operation} returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,
|
|
97
|
+
{ kind: "validation", provider: "anthropic", cause: error },
|
|
93
98
|
);
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -131,14 +136,18 @@ async function fetchBootstrapIdentity(
|
|
|
131
136
|
});
|
|
132
137
|
const responseBody = await response.text();
|
|
133
138
|
if (!response.ok) {
|
|
134
|
-
throw new
|
|
139
|
+
throw new AIError.ProviderHttpError(
|
|
140
|
+
`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`,
|
|
141
|
+
response.status,
|
|
142
|
+
);
|
|
135
143
|
}
|
|
136
144
|
let data: AnthropicBootstrapResponse;
|
|
137
145
|
try {
|
|
138
146
|
data = JSON.parse(responseBody) as AnthropicBootstrapResponse;
|
|
139
147
|
} catch (error) {
|
|
140
|
-
throw new
|
|
148
|
+
throw new AIError.OAuthError(
|
|
141
149
|
`Anthropic bootstrap returned invalid JSON. url=${url}; body=${responseBody}; details=${formatErrorDetails(error)}`,
|
|
150
|
+
{ kind: "validation", provider: "anthropic", cause: error },
|
|
142
151
|
);
|
|
143
152
|
}
|
|
144
153
|
const accountUuid = data.oauth_account?.account_uuid;
|
|
@@ -227,8 +236,9 @@ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
|
|
|
227
236
|
this.#fetch,
|
|
228
237
|
);
|
|
229
238
|
} catch (error) {
|
|
230
|
-
throw new
|
|
239
|
+
throw new AIError.OAuthError(
|
|
231
240
|
`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,
|
|
241
|
+
{ kind: "token-exchange", provider: "anthropic", cause: error },
|
|
232
242
|
);
|
|
233
243
|
}
|
|
234
244
|
|
|
@@ -278,7 +288,14 @@ export async function refreshAnthropicToken(
|
|
|
278
288
|
},
|
|
279
289
|
);
|
|
280
290
|
} catch (error) {
|
|
281
|
-
throw new
|
|
291
|
+
throw new AIError.OAuthError(
|
|
292
|
+
`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`,
|
|
293
|
+
{
|
|
294
|
+
kind: "token-refresh",
|
|
295
|
+
provider: "anthropic",
|
|
296
|
+
cause: error,
|
|
297
|
+
},
|
|
298
|
+
);
|
|
282
299
|
}
|
|
283
300
|
|
|
284
301
|
const data = parseOAuthTokenResponse(responseBody, "token refresh");
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - generateAuthUrl(): Build provider-specific authorization URL
|
|
11
11
|
* - exchangeToken(): Exchange authorization code for tokens
|
|
12
12
|
*/
|
|
13
|
+
import * as AIError from "../../error";
|
|
13
14
|
import templateHtml from "./oauth.html" with { type: "text" };
|
|
14
15
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
15
16
|
|
|
@@ -127,7 +128,7 @@ export abstract class OAuthCallbackFlow {
|
|
|
127
128
|
return { server, redirectUri };
|
|
128
129
|
} catch {
|
|
129
130
|
if (this.redirectUri) {
|
|
130
|
-
throw new
|
|
131
|
+
throw new AIError.ConfigurationError(
|
|
131
132
|
`OAuth callback port ${this.preferredPort} unavailable; cannot fall back to a random port when oauth.redirectUri is set`,
|
|
132
133
|
);
|
|
133
134
|
}
|
|
@@ -143,8 +144,9 @@ export abstract class OAuthCallbackFlow {
|
|
|
143
144
|
* Create HTTP server for OAuth callback.
|
|
144
145
|
*/
|
|
145
146
|
#createServer(port: number, expectedState: string): Bun.Server<unknown> {
|
|
147
|
+
const hostname = this.callbackHostname === DEFAULT_HOSTNAME ? undefined : this.callbackHostname;
|
|
146
148
|
return Bun.serve({
|
|
147
|
-
hostname:
|
|
149
|
+
...(hostname === undefined ? {} : { hostname }),
|
|
148
150
|
port,
|
|
149
151
|
reusePort: false,
|
|
150
152
|
fetch: req => this.#handleCallback(req, expectedState),
|
|
@@ -214,7 +216,7 @@ export abstract class OAuthCallbackFlow {
|
|
|
214
216
|
signal.addEventListener("abort", () => {
|
|
215
217
|
this.#callbackResolve = undefined;
|
|
216
218
|
this.#callbackReject = undefined;
|
|
217
|
-
reject(new
|
|
219
|
+
reject(new AIError.LoginCancelledError(`OAuth callback cancelled: ${signal.reason}`));
|
|
218
220
|
});
|
|
219
221
|
});
|
|
220
222
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../../error";
|
|
1
2
|
import { generatePKCE } from "./pkce";
|
|
2
3
|
import type { OAuthCredentials } from "./types";
|
|
3
4
|
|
|
@@ -63,16 +64,26 @@ export async function pollCursorAuth(
|
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
throw new
|
|
67
|
+
throw new AIError.OAuthError(`Poll failed: ${response.status}`, {
|
|
68
|
+
kind: "polling",
|
|
69
|
+
provider: "cursor",
|
|
70
|
+
status: response.status,
|
|
71
|
+
});
|
|
67
72
|
} catch {
|
|
68
73
|
consecutiveErrors++;
|
|
69
74
|
if (consecutiveErrors >= 3) {
|
|
70
|
-
throw new
|
|
75
|
+
throw new AIError.OAuthError("Too many consecutive errors during Cursor auth polling", {
|
|
76
|
+
kind: "polling",
|
|
77
|
+
provider: "cursor",
|
|
78
|
+
});
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
throw new
|
|
83
|
+
throw new AIError.OAuthError("Cursor authentication polling timeout", {
|
|
84
|
+
kind: "timeout",
|
|
85
|
+
provider: "cursor",
|
|
86
|
+
});
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
export async function loginCursor(
|
|
@@ -107,7 +118,10 @@ export async function refreshCursorToken(apiKeyOrRefreshToken: string): Promise<
|
|
|
107
118
|
|
|
108
119
|
if (!response.ok) {
|
|
109
120
|
const error = await response.text();
|
|
110
|
-
throw new
|
|
121
|
+
throw new AIError.OAuthError(`Cursor token refresh failed: ${error}`, {
|
|
122
|
+
kind: "token-refresh",
|
|
123
|
+
provider: "cursor",
|
|
124
|
+
});
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
const data = (await response.json()) as {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AIError from "../../error";
|
|
1
2
|
import { OAuthCallbackFlow } from "./callback-server";
|
|
2
3
|
import { generatePKCE } from "./pkce";
|
|
3
4
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
@@ -54,7 +55,10 @@ class DevinOAuthFlow extends OAuthCallbackFlow {
|
|
|
54
55
|
|
|
55
56
|
async exchangeToken(code: string): Promise<OAuthCredentials> {
|
|
56
57
|
if (!this.#pkce) {
|
|
57
|
-
throw new
|
|
58
|
+
throw new AIError.OAuthError("Devin PKCE verifier was not initialized", {
|
|
59
|
+
kind: "configuration",
|
|
60
|
+
provider: "devin",
|
|
61
|
+
});
|
|
58
62
|
}
|
|
59
63
|
const token = await exchangeDevinCliToken(code, this.#pkce.verifier, this.ctrl.fetch);
|
|
60
64
|
|
|
@@ -87,12 +91,19 @@ export async function exchangeDevinCliToken(
|
|
|
87
91
|
|
|
88
92
|
if (!response.ok) {
|
|
89
93
|
const error = await response.text();
|
|
90
|
-
throw new
|
|
94
|
+
throw new AIError.OAuthError(`Devin CLI token exchange failed: ${response.status} ${error}`.trim(), {
|
|
95
|
+
kind: "token-exchange",
|
|
96
|
+
provider: "devin",
|
|
97
|
+
status: response.status,
|
|
98
|
+
});
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
const data = (await response.json()) as { token?: unknown };
|
|
94
102
|
if (typeof data.token !== "string" || data.token.length === 0) {
|
|
95
|
-
throw new
|
|
103
|
+
throw new AIError.OAuthError("Devin CLI token exchange returned an empty token", {
|
|
104
|
+
kind: "validation",
|
|
105
|
+
provider: "devin",
|
|
106
|
+
});
|
|
96
107
|
}
|
|
97
108
|
return data.token;
|
|
98
109
|
}
|