Q87: provider errors keep status and provider code; text never decides recovery
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ecffe84e3
commit
4668396f25
28 files changed
+285
-108
No files matched your search
@@ -8,6 +8,7 @@
|
||||
import { startOAuthCallbackServer } from "./oauth-callback-server";
|
||||
import { generatePkce } from "./pkce";
|
||||
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
|
||||
import { httpFailure } from "../provider-http";
|
||||
|
||||
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
||||
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
||||
@@ -61,7 +62,7 @@ export async function refreshClaudePro(refreshToken: string): Promise<OAuthCrede
|
||||
body: JSON.stringify({ grant_type: "refresh_token", client_id: CLIENT_ID, refresh_token: refreshToken }),
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) throw new Error(`Claude token refresh failed: ${response.status} ${text}`);
|
||||
if (!response.ok) throw httpFailure(`Claude token refresh failed: ${response.status} ${text}`, response, text);
|
||||
const json = JSON.parse(text) as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
||||
if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") {
|
||||
throw new Error("Claude token refresh response was missing required fields.");
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
import { listProviderModels } from "../../../shared/provider-models";
|
||||
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
|
||||
import { httpFailure } from "../provider-http";
|
||||
|
||||
// Decoded from the published GitHub Copilot client id used by the official CLI.
|
||||
const CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
||||
@@ -47,7 +48,7 @@ async function fetchJson(url: string, init: RequestInit): Promise<Record<string,
|
||||
const response = await fetch(url, init);
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
||||
throw httpFailure(`${response.status} ${response.statusText}: ${text}`, response, text);
|
||||
}
|
||||
return (await response.json()) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { startOAuthCallbackServer } from "./oauth-callback-server";
|
||||
import { generatePkce } from "./pkce";
|
||||
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
|
||||
import { httpFailure } from "../provider-http";
|
||||
|
||||
const CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
||||
const CALLBACK_PORT = 51121;
|
||||
@@ -115,7 +116,7 @@ export async function refreshAntigravity(refreshToken: string, projectId: string
|
||||
}),
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) throw new Error(`Antigravity token refresh failed: ${response.status} ${text}`);
|
||||
if (!response.ok) throw httpFailure(`Antigravity token refresh failed: ${response.status} ${text}`, response, text);
|
||||
const json = JSON.parse(text) as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
||||
if (typeof json.access_token !== "string" || typeof json.expires_in !== "number") {
|
||||
throw new Error("Antigravity token refresh response was missing required fields.");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { hostname, platform as osPlatform, release as osRelease, userInfo } from "node:os";
|
||||
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
|
||||
import { httpFailure } from "../provider-http";
|
||||
|
||||
// Published public client id of the official Kimi CLI; reused by third-party
|
||||
// coding agents per Kimi's device-flow integration guidance.
|
||||
@@ -47,7 +48,7 @@ function deviceHeaders(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
async function postForm(path: string, form: Record<string, string>): Promise<{ status: number; data: Record<string, unknown> }> {
|
||||
async function postForm(path: string, form: Record<string, string>): Promise<{ status: number; data: Record<string, unknown>; response: Response; text: string }> {
|
||||
const response = await fetch(`${OAUTH_HOST}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -62,12 +63,12 @@ async function postForm(path: string, form: Record<string, string>): Promise<{ s
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error(`Kimi OAuth endpoint returned a non-JSON response: ${response.status} ${text.slice(0, 200)}`);
|
||||
throw httpFailure(`Kimi OAuth endpoint returned a non-JSON response: ${response.status} ${text.slice(0, 200)}`, response, text);
|
||||
}
|
||||
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
||||
throw new Error(`Kimi OAuth endpoint returned an unexpected payload: ${response.status}`);
|
||||
throw httpFailure(`Kimi OAuth endpoint returned an unexpected payload: ${response.status}`, response, text);
|
||||
}
|
||||
return { status: response.status, data: data as Record<string, unknown> };
|
||||
return { status: response.status, data: data as Record<string, unknown>, response, text };
|
||||
}
|
||||
|
||||
interface DeviceAuthorization {
|
||||
@@ -167,12 +168,12 @@ async function pollForToken(device: DeviceAuthorization, signal?: AbortSignal):
|
||||
|
||||
/** Refresh an expired Kimi Code access token (they live ~15 minutes). */
|
||||
export async function refreshKimiCode(refreshToken: string): Promise<OAuthCredentials> {
|
||||
const { status, data } = await postForm("/api/oauth/token", {
|
||||
const { status, data, response, text } = await postForm("/api/oauth/token", {
|
||||
client_id: CLIENT_ID,
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
if (status !== 200) throw new Error(`Kimi token refresh failed: ${status} ${JSON.stringify(data)}`);
|
||||
if (status !== 200) throw httpFailure(`Kimi token refresh failed: ${status} ${JSON.stringify(data)}`, response, text);
|
||||
return credentialsFromToken(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { startOAuthCallbackServer } from "./oauth-callback-server";
|
||||
import { generatePkce } from "./pkce";
|
||||
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
|
||||
import { httpFailure } from "../provider-http";
|
||||
|
||||
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
||||
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
||||
@@ -84,7 +85,7 @@ export async function refreshCodex(refreshToken: string): Promise<OAuthCredentia
|
||||
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: CLIENT_ID }),
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) throw new Error(`Codex token refresh failed: ${response.status} ${text}`);
|
||||
if (!response.ok) throw httpFailure(`Codex token refresh failed: ${response.status} ${text}`, response, text);
|
||||
const json = JSON.parse(text) as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
||||
if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") {
|
||||
throw new Error("Codex token refresh response was missing required fields.");
|
||||
|
||||
Reference in new issue
Block a user