pi-anthropic-oauth 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/auth.ts +40 -11
- package/src/convert.ts +22 -12
- package/src/index.ts +5 -3
- package/src/stream.ts +9 -4
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -17,6 +17,8 @@ const SCOPES = [
|
|
|
17
17
|
"user:file_upload",
|
|
18
18
|
].join(" ");
|
|
19
19
|
const USER_AGENT = "claude-code/2.1.97";
|
|
20
|
+
const CALLBACK_PORT = 53692;
|
|
21
|
+
const CALLBACK_HOST = "127.0.0.1";
|
|
20
22
|
const LOCAL_CALLBACK_TIMEOUT = 5 * 60 * 1000;
|
|
21
23
|
const MAX_TOKEN_RETRIES = 2;
|
|
22
24
|
const INITIAL_RETRY_DELAY_MS = 5000;
|
|
@@ -27,6 +29,7 @@ type ParsedAuthInput = { code: string; state: string };
|
|
|
27
29
|
type LocalAuthorization = {
|
|
28
30
|
redirectUri: string;
|
|
29
31
|
waitForCallback: () => Promise<string | null>;
|
|
32
|
+
cancel: () => void;
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
export function isClaudeOAuthAccessToken(apiKey: string): boolean {
|
|
@@ -49,12 +52,43 @@ export async function loginAnthropic(
|
|
|
49
52
|
callbacks.onAuth({
|
|
50
53
|
url: makeAuthorizeUrl(challenge, state, redirectUri),
|
|
51
54
|
instructions:
|
|
52
|
-
"Complete
|
|
55
|
+
"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.",
|
|
53
56
|
});
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
if (callbacks.onManualCodeInput) {
|
|
59
|
+
let manualInput: string | undefined;
|
|
60
|
+
let manualError: Error | undefined;
|
|
61
|
+
const manualPromise = callbacks
|
|
62
|
+
.onManualCodeInput()
|
|
63
|
+
.then((input) => {
|
|
64
|
+
manualInput = input;
|
|
65
|
+
localAuthorization.cancel();
|
|
66
|
+
})
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
manualError =
|
|
69
|
+
err instanceof Error ? err : new Error(String(err));
|
|
70
|
+
localAuthorization.cancel();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const callbackResult = await localAuthorization.waitForCallback();
|
|
74
|
+
|
|
75
|
+
if (manualError) throw manualError;
|
|
76
|
+
|
|
77
|
+
if (callbackResult) {
|
|
78
|
+
authInput = callbackResult;
|
|
79
|
+
} else if (manualInput) {
|
|
80
|
+
authInput = manualInput;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!authInput) {
|
|
84
|
+
await manualPromise;
|
|
85
|
+
if (manualError) throw manualError;
|
|
86
|
+
if (manualInput) authInput = manualInput;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
authInput = await localAuthorization.waitForCallback();
|
|
90
|
+
}
|
|
56
91
|
} catch {
|
|
57
|
-
// Fall back to manual code entry when localhost callback setup fails.
|
|
58
92
|
}
|
|
59
93
|
|
|
60
94
|
if (!authInput) {
|
|
@@ -270,17 +304,12 @@ async function createLocalAuthorization(
|
|
|
270
304
|
|
|
271
305
|
server.once("error", reject);
|
|
272
306
|
|
|
273
|
-
server.listen(
|
|
274
|
-
const address = server.address();
|
|
275
|
-
if (!address || typeof address === "string") {
|
|
276
|
-
reject(new Error("Failed to allocate localhost callback port"));
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
307
|
+
server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
|
|
280
308
|
timer = setTimeout(() => finish(null), LOCAL_CALLBACK_TIMEOUT);
|
|
281
309
|
resolve({
|
|
282
|
-
redirectUri: `http://localhost:${
|
|
310
|
+
redirectUri: `http://localhost:${CALLBACK_PORT}/callback`,
|
|
283
311
|
waitForCallback: () => wait,
|
|
312
|
+
cancel: () => finish(null),
|
|
284
313
|
});
|
|
285
314
|
});
|
|
286
315
|
});
|
package/src/convert.ts
CHANGED
|
@@ -54,6 +54,24 @@ export function convertPiMessagesToAnthropic(
|
|
|
54
54
|
isOAuth: boolean,
|
|
55
55
|
): MessageParam[] {
|
|
56
56
|
const params: MessageParam[] = [];
|
|
57
|
+
const toolIdMap = new Map<string, string>();
|
|
58
|
+
const usedToolIds = new Set<string>();
|
|
59
|
+
|
|
60
|
+
const getAnthropicToolId = (id: string): string => {
|
|
61
|
+
const existing = toolIdMap.get(id);
|
|
62
|
+
if (existing) return existing;
|
|
63
|
+
|
|
64
|
+
let base = sanitizeSurrogates(id).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
65
|
+
if (!base) base = "tool";
|
|
66
|
+
let candidate = base;
|
|
67
|
+
let suffix = 1;
|
|
68
|
+
while (usedToolIds.has(candidate)) {
|
|
69
|
+
candidate = `${base}_${suffix++}`;
|
|
70
|
+
}
|
|
71
|
+
usedToolIds.add(candidate);
|
|
72
|
+
toolIdMap.set(id, candidate);
|
|
73
|
+
return candidate;
|
|
74
|
+
};
|
|
57
75
|
|
|
58
76
|
for (let i = 0; i < messages.length; i++) {
|
|
59
77
|
const message = messages[i];
|
|
@@ -81,19 +99,11 @@ export function convertPiMessagesToAnthropic(
|
|
|
81
99
|
if (block.type === "text" && block.text.trim()) {
|
|
82
100
|
blocks.push({ type: "text", text: sanitizeSurrogates(block.text) });
|
|
83
101
|
} else if (block.type === "thinking" && block.thinking.trim()) {
|
|
84
|
-
|
|
85
|
-
blocks.push({
|
|
86
|
-
type: "thinking" as never,
|
|
87
|
-
thinking: sanitizeSurrogates(block.thinking),
|
|
88
|
-
signature: block.thinkingSignature,
|
|
89
|
-
});
|
|
90
|
-
} else {
|
|
91
|
-
blocks.push({ type: "text", text: sanitizeSurrogates(block.thinking) });
|
|
92
|
-
}
|
|
102
|
+
blocks.push({ type: "text", text: sanitizeSurrogates(block.thinking) });
|
|
93
103
|
} else if (block.type === "toolCall") {
|
|
94
104
|
blocks.push({
|
|
95
105
|
type: "tool_use",
|
|
96
|
-
id: block.id,
|
|
106
|
+
id: getAnthropicToolId(block.id),
|
|
97
107
|
name: isOAuth ? toClaudeCodeToolName(block.name) : block.name,
|
|
98
108
|
input: block.arguments,
|
|
99
109
|
});
|
|
@@ -107,7 +117,7 @@ export function convertPiMessagesToAnthropic(
|
|
|
107
117
|
const toolResults = [
|
|
108
118
|
{
|
|
109
119
|
type: "tool_result" as const,
|
|
110
|
-
tool_use_id: message.toolCallId,
|
|
120
|
+
tool_use_id: getAnthropicToolId(message.toolCallId),
|
|
111
121
|
content: convertToolResultContentToAnthropic(message.content),
|
|
112
122
|
is_error: message.isError,
|
|
113
123
|
},
|
|
@@ -118,7 +128,7 @@ export function convertPiMessagesToAnthropic(
|
|
|
118
128
|
const nextMessage = messages[j] as ToolResultMessage;
|
|
119
129
|
toolResults.push({
|
|
120
130
|
type: "tool_result" as const,
|
|
121
|
-
tool_use_id: nextMessage.toolCallId,
|
|
131
|
+
tool_use_id: getAnthropicToolId(nextMessage.toolCallId),
|
|
122
132
|
content: convertToolResultContentToAnthropic(nextMessage.content),
|
|
123
133
|
is_error: nextMessage.isError,
|
|
124
134
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, symlinkSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import type { ExtensionAPI, ProviderConfig } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
|
5
6
|
import { loginAnthropic, refreshAnthropicToken } from "./auth.js";
|
|
6
7
|
import { streamAnthropicOAuth } from "./stream.js";
|
|
7
8
|
|
|
@@ -54,10 +55,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
54
55
|
models: [...MODELS],
|
|
55
56
|
oauth: {
|
|
56
57
|
name: "Claude Pro/Max",
|
|
58
|
+
usesCallbackServer: true,
|
|
57
59
|
login: loginAnthropic,
|
|
58
60
|
refreshToken: refreshAnthropicToken,
|
|
59
|
-
getApiKey: (credentials) => credentials.access,
|
|
60
|
-
},
|
|
61
|
+
getApiKey: (credentials: OAuthCredentials) => credentials.access,
|
|
62
|
+
} as unknown as ProviderConfig["oauth"],
|
|
61
63
|
streamSimple: streamAnthropicOAuth,
|
|
62
64
|
});
|
|
63
65
|
}
|
package/src/stream.ts
CHANGED
|
@@ -111,10 +111,13 @@ export function streamAnthropicOAuth(
|
|
|
111
111
|
dangerouslyAllowBrowser: true,
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
const maxTokens =
|
|
115
|
+
options?.maxTokens || Math.floor(model.maxTokens / 3);
|
|
116
|
+
|
|
114
117
|
const params: MessageCreateParamsStreaming = {
|
|
115
118
|
model: model.id,
|
|
116
119
|
messages: convertPiMessagesToAnthropic(context.messages, isOAuth),
|
|
117
|
-
max_tokens:
|
|
120
|
+
max_tokens: maxTokens,
|
|
118
121
|
stream: true,
|
|
119
122
|
};
|
|
120
123
|
|
|
@@ -123,7 +126,7 @@ export function streamAnthropicOAuth(
|
|
|
123
126
|
if (context.tools?.length)
|
|
124
127
|
params.tools = convertPiToolsToAnthropic(context.tools, isOAuth);
|
|
125
128
|
|
|
126
|
-
if (options?.reasoning && model.reasoning) {
|
|
129
|
+
if (options?.reasoning && model.reasoning && maxTokens > 1) {
|
|
127
130
|
const defaultBudgets: Record<string, number> = {
|
|
128
131
|
minimal: 1024,
|
|
129
132
|
low: 4096,
|
|
@@ -135,10 +138,12 @@ export function streamAnthropicOAuth(
|
|
|
135
138
|
options.thinkingBudgets?.[
|
|
136
139
|
options.reasoning as keyof typeof options.thinkingBudgets
|
|
137
140
|
];
|
|
141
|
+
const requestedBudget =
|
|
142
|
+
customBudget ?? defaultBudgets[options.reasoning] ?? 10240;
|
|
143
|
+
|
|
138
144
|
params.thinking = {
|
|
139
145
|
type: "enabled",
|
|
140
|
-
budget_tokens:
|
|
141
|
-
customBudget ?? defaultBudgets[options.reasoning] ?? 10240,
|
|
146
|
+
budget_tokens: Math.min(requestedBudget, maxTokens - 1),
|
|
142
147
|
};
|
|
143
148
|
}
|
|
144
149
|
|