fix(ai): replace curl with fetch in Anthropic OAuth token exchange
This commit is contained in:
@@ -257,7 +257,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"anthropic.claude-sonnet-4-20250514-v1:0": {
|
"anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||||
@@ -308,7 +308,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.3,
|
cacheRead: 0.3,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 3.75,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"deepseek.r1-v1:0": {
|
"deepseek.r1-v1:0": {
|
||||||
@@ -410,7 +410,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"eu.anthropic.claude-sonnet-4-20250514-v1:0": {
|
"eu.anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||||
@@ -461,7 +461,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.3,
|
cacheRead: 0.3,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 3.75,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"global.anthropic.claude-haiku-4-5-20251001-v1:0": {
|
"global.anthropic.claude-haiku-4-5-20251001-v1:0": {
|
||||||
@@ -512,7 +512,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"global.anthropic.claude-sonnet-4-20250514-v1:0": {
|
"global.anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||||
@@ -563,7 +563,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.3,
|
cacheRead: 0.3,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 3.75,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"google.gemma-3-27b-it": {
|
"google.gemma-3-27b-it": {
|
||||||
@@ -1294,7 +1294,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"us.anthropic.claude-sonnet-4-20250514-v1:0": {
|
"us.anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||||
@@ -1345,7 +1345,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.3,
|
cacheRead: 0.3,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 3.75,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"bedrock-converse-stream">,
|
} satisfies Model<"bedrock-converse-stream">,
|
||||||
"writer.palmyra-x4-v1:0": {
|
"writer.palmyra-x4-v1:0": {
|
||||||
@@ -1721,7 +1721,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"anthropic-messages">,
|
} satisfies Model<"anthropic-messages">,
|
||||||
"claude-sonnet-4-0": {
|
"claude-sonnet-4-0": {
|
||||||
@@ -5898,7 +5898,7 @@ export const MODELS = {
|
|||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"anthropic-messages">,
|
} satisfies Model<"anthropic-messages">,
|
||||||
"claude-sonnet-4": {
|
"claude-sonnet-4": {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Anthropic OAuth flow (Claude Pro/Max)
|
* Anthropic OAuth flow (Claude Pro/Max)
|
||||||
*
|
*
|
||||||
* NOTE: This module uses Node.js http.createServer and child_process for the OAuth callback
|
* NOTE: This module uses Node.js http.createServer for the OAuth callback server.
|
||||||
* and token exchange. It is only intended for CLI use, not browser environments.
|
* It is only intended for CLI use, not browser environments.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Server } from "node:http";
|
import type { Server } from "node:http";
|
||||||
@@ -18,7 +18,6 @@ type CallbackServerInfo = {
|
|||||||
|
|
||||||
type NodeApis = {
|
type NodeApis = {
|
||||||
createServer: typeof import("node:http").createServer;
|
createServer: typeof import("node:http").createServer;
|
||||||
execFile: typeof import("node:child_process").execFile;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let nodeApis: NodeApis | null = null;
|
let nodeApis: NodeApis | null = null;
|
||||||
@@ -53,12 +52,9 @@ async function getNodeApis(): Promise<NodeApis> {
|
|||||||
if (typeof process === "undefined" || (!process.versions?.node && !process.versions?.bun)) {
|
if (typeof process === "undefined" || (!process.versions?.node && !process.versions?.bun)) {
|
||||||
throw new Error("Anthropic OAuth is only available in Node.js environments");
|
throw new Error("Anthropic OAuth is only available in Node.js environments");
|
||||||
}
|
}
|
||||||
nodeApisPromise = Promise.all([import("node:http"), import("node:child_process")]).then(
|
nodeApisPromise = import("node:http").then((httpModule) => ({
|
||||||
([httpModule, childProcessModule]) => ({
|
createServer: httpModule.createServer,
|
||||||
createServer: httpModule.createServer,
|
}));
|
||||||
execFile: childProcessModule.execFile,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
nodeApis = await nodeApisPromise;
|
nodeApis = await nodeApisPromise;
|
||||||
return nodeApis;
|
return nodeApis;
|
||||||
@@ -183,59 +179,21 @@ async function startCallbackServer(expectedState: string): Promise<CallbackServe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postJsonWithCurl(url: string, body: Record<string, string | number>): Promise<string> {
|
async function postJson(url: string, body: Record<string, string | number>): Promise<string> {
|
||||||
const { execFile } = await getNodeApis();
|
const response = await fetch(url, {
|
||||||
const payload = JSON.stringify(body);
|
method: "POST",
|
||||||
const marker = "__PI_CURL_HTTP_STATUS__:";
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
const result = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
Accept: "application/json",
|
||||||
execFile(
|
},
|
||||||
"curl",
|
body: JSON.stringify(body),
|
||||||
[
|
signal: AbortSignal.timeout(30_000),
|
||||||
"-sS",
|
|
||||||
"--connect-timeout",
|
|
||||||
"15",
|
|
||||||
"--max-time",
|
|
||||||
"30",
|
|
||||||
"-X",
|
|
||||||
"POST",
|
|
||||||
url,
|
|
||||||
"-H",
|
|
||||||
"Content-Type: application/json",
|
|
||||||
"-H",
|
|
||||||
"Accept: application/json",
|
|
||||||
"--data-binary",
|
|
||||||
payload,
|
|
||||||
"-w",
|
|
||||||
`\n${marker}%{http_code}`,
|
|
||||||
],
|
|
||||||
(error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
reject(
|
|
||||||
new Error(`curl request failed. url=${url}; details=${formatErrorDetails(error)}; stderr=${stderr}`),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve({ stdout, stderr });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const markerIndex = result.stdout.lastIndexOf(`\n${marker}`);
|
const responseBody = await response.text();
|
||||||
if (markerIndex === -1) {
|
|
||||||
throw new Error(
|
|
||||||
`curl response missing status marker. url=${url}; stdout=${result.stdout}; stderr=${result.stderr}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseBody = result.stdout.slice(0, markerIndex);
|
if (!response.ok) {
|
||||||
const statusText = result.stdout.slice(markerIndex + `\n${marker}`.length).trim();
|
throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
|
||||||
const status = Number.parseInt(statusText, 10);
|
|
||||||
if (!Number.isFinite(status)) {
|
|
||||||
throw new Error(`curl returned invalid status. url=${url}; status=${statusText}; body=${responseBody}`);
|
|
||||||
}
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
throw new Error(`HTTP request failed. status=${status}; url=${url}; body=${responseBody}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return responseBody;
|
return responseBody;
|
||||||
@@ -249,7 +207,7 @@ async function exchangeAuthorizationCode(
|
|||||||
): Promise<OAuthCredentials> {
|
): Promise<OAuthCredentials> {
|
||||||
let responseBody: string;
|
let responseBody: string;
|
||||||
try {
|
try {
|
||||||
responseBody = await postJsonWithCurl(TOKEN_URL, {
|
responseBody = await postJson(TOKEN_URL, {
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
client_id: CLIENT_ID,
|
client_id: CLIENT_ID,
|
||||||
code,
|
code,
|
||||||
@@ -406,7 +364,7 @@ export async function loginAnthropic(options: {
|
|||||||
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
|
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
|
||||||
let responseBody: string;
|
let responseBody: string;
|
||||||
try {
|
try {
|
||||||
responseBody = await postJsonWithCurl(TOKEN_URL, {
|
responseBody = await postJson(TOKEN_URL, {
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
client_id: CLIENT_ID,
|
client_id: CLIENT_ID,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
|
|||||||
Reference in New Issue
Block a user