pi-anthropic-oauth 0.2.3 → 0.3.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/README.md +9 -1
- package/package.json +5 -5
- package/src/auth.ts +2 -1
- package/src/prompt.ts +28 -7
- package/src/stream.ts +54 -19
- package/src/version.ts +24 -0
package/README.md
CHANGED
|
@@ -47,9 +47,17 @@ pi update npm:pi-anthropic-oauth
|
|
|
47
47
|
> [!NOTE]
|
|
48
48
|
> Anthropic auth changes are closely monitored for quick compatibility updates.
|
|
49
49
|
|
|
50
|
+
## Claude Code version override
|
|
51
|
+
|
|
52
|
+
Anthropic gates new models on the Claude Code client version (error `claude_code_version_too_old`). The version is sent in both the OAuth user agent and the billing header of the system prompt. Override it without reinstalling:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
PI_ANTHROPIC_OAUTH_CLAUDE_CODE_VERSION=2.1.280
|
|
56
|
+
```
|
|
57
|
+
|
|
50
58
|
## System prompt rewriting
|
|
51
59
|
|
|
52
|
-
When using Claude Pro/Max OAuth, the extension prepends Claude Code identity
|
|
60
|
+
When using Claude Pro/Max OAuth, the extension prepends the Claude Code billing-header and identity system blocks and rewrites standalone `Pi` / `pi` references in Pi's system prompt to `Claude Code`. The rewrite mode defaults to `aggressive`:
|
|
53
61
|
|
|
54
62
|
```bash
|
|
55
63
|
PI_ANTHROPIC_OAUTH_REWRITE_MODE=aggressive
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-anthropic-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Claude Pro/Max OAuth extension for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": ">=0.
|
|
42
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
41
|
+
"@earendil-works/pi-ai": ">=0.86.0",
|
|
42
|
+
"@earendil-works/pi-coding-agent": ">=0.86.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@earendil-works/pi-ai": "^0.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
45
|
+
"@earendil-works/pi-ai": "^0.86.1",
|
|
46
|
+
"@earendil-works/pi-coding-agent": "^0.86.1",
|
|
47
47
|
"typescript": "^5.9.3"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/auth.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
OAuthCredentials,
|
|
4
4
|
OAuthLoginCallbacks,
|
|
5
5
|
} from "@earendil-works/pi-ai";
|
|
6
|
+
import { makeClaudeCodeUserAgent } from "./version.js";
|
|
6
7
|
|
|
7
8
|
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
8
9
|
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
@@ -16,7 +17,7 @@ const SCOPES = [
|
|
|
16
17
|
"user:mcp_servers",
|
|
17
18
|
"user:file_upload",
|
|
18
19
|
].join(" ");
|
|
19
|
-
const USER_AGENT =
|
|
20
|
+
const USER_AGENT = makeClaudeCodeUserAgent();
|
|
20
21
|
const CALLBACK_PORT = 53692;
|
|
21
22
|
const CALLBACK_HOST = "127.0.0.1";
|
|
22
23
|
const LOCAL_CALLBACK_TIMEOUT = 5 * 60 * 1000;
|
package/src/prompt.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getClaudeCodeVersion } from "./version.js";
|
|
3
|
+
|
|
1
4
|
const CLAUDE_CODE_IDENTITY =
|
|
2
|
-
"You are Claude
|
|
5
|
+
"You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Anthropic's OAuth classifier rejects premium-model requests that do not
|
|
9
|
+
* open with a Claude Code billing-header system block. The `cch` and
|
|
10
|
+
* `cc_prompt_id` keys must be present, but their values are not validated,
|
|
11
|
+
* and the block must not carry `cache_control`.
|
|
12
|
+
*/
|
|
13
|
+
export function buildClaudeCodeBillingBlock(): MessageContentBlock {
|
|
14
|
+
return {
|
|
15
|
+
type: "text",
|
|
16
|
+
text: `x-anthropic-billing-header: cc_version=${getClaudeCodeVersion()}; cc_entrypoint=cli; cch=00000; cc_prompt_id=${randomUUID()}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
3
19
|
const PI_REMOVAL_ANCHORS = [
|
|
4
20
|
"pi-coding-agent",
|
|
5
21
|
"@earendil-works/pi-coding-agent",
|
|
@@ -32,7 +48,15 @@ type MessageContentBlock = {
|
|
|
32
48
|
};
|
|
33
49
|
|
|
34
50
|
export function sanitizeSurrogates(text: string): string {
|
|
35
|
-
|
|
51
|
+
// Replace only *unpaired* surrogates: a high surrogate not followed by a low
|
|
52
|
+
// surrogate, or a low surrogate not preceded by a high surrogate. Valid
|
|
53
|
+
// surrogate pairs (non-BMP characters such as emoji) must pass through
|
|
54
|
+
// untouched. Equivalent to String.prototype.toWellFormed(), which requires
|
|
55
|
+
// Node 20+ while this package supports Node >=18.
|
|
56
|
+
return text.replace(
|
|
57
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,
|
|
58
|
+
"\uFFFD",
|
|
59
|
+
);
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
export function buildAnthropicSystemPrompt(
|
|
@@ -42,11 +66,8 @@ export function buildAnthropicSystemPrompt(
|
|
|
42
66
|
const blocks: MessageContentBlock[] = [];
|
|
43
67
|
|
|
44
68
|
if (isOAuth) {
|
|
45
|
-
blocks.push(
|
|
46
|
-
|
|
47
|
-
text: CLAUDE_CODE_IDENTITY,
|
|
48
|
-
cache_control: { type: "ephemeral" },
|
|
49
|
-
});
|
|
69
|
+
blocks.push(buildClaudeCodeBillingBlock());
|
|
70
|
+
blocks.push({ type: "text", text: CLAUDE_CODE_IDENTITY });
|
|
50
71
|
}
|
|
51
72
|
|
|
52
73
|
const sanitized = systemPrompt ? sanitizeSystemText(systemPrompt) : "";
|
package/src/stream.ts
CHANGED
|
@@ -5,11 +5,15 @@ import {
|
|
|
5
5
|
type AssistantMessage,
|
|
6
6
|
type AssistantMessageEventStream,
|
|
7
7
|
calculateCost,
|
|
8
|
-
|
|
8
|
+
collapseSystemMessages,
|
|
9
9
|
createAssistantMessageEventStream,
|
|
10
|
+
getCurrentSystemPrompt,
|
|
11
|
+
getCurrentTools,
|
|
12
|
+
type JsonObject,
|
|
10
13
|
type Model,
|
|
11
14
|
type SimpleStreamOptions,
|
|
12
15
|
type StopReason,
|
|
16
|
+
type TranscriptContext,
|
|
13
17
|
} from "@earendil-works/pi-ai";
|
|
14
18
|
import { isClaudeOAuthAccessToken, USER_AGENT } from "./auth.js";
|
|
15
19
|
import {
|
|
@@ -94,11 +98,19 @@ function makeDefaultHeaders(
|
|
|
94
98
|
|
|
95
99
|
export function streamAnthropicOAuth(
|
|
96
100
|
model: Model<Api>,
|
|
97
|
-
context:
|
|
101
|
+
context: TranscriptContext,
|
|
98
102
|
options?: SimpleStreamOptions,
|
|
99
103
|
): AssistantMessageEventStream {
|
|
100
104
|
const stream = createAssistantMessageEventStream();
|
|
101
105
|
|
|
106
|
+
// Anthropic has no mid-conversation system messages: fold system-message
|
|
107
|
+
// patches into the leading system message, then read the current prompt and
|
|
108
|
+
// tool declarations from the transcript (pi >= 0.86 no longer provides
|
|
109
|
+
// context.systemPrompt / context.tools).
|
|
110
|
+
const transcript = collapseSystemMessages(context);
|
|
111
|
+
const tools = getCurrentTools(transcript.messages);
|
|
112
|
+
const systemPrompt = getCurrentSystemPrompt(transcript.messages);
|
|
113
|
+
|
|
102
114
|
void (async () => {
|
|
103
115
|
const output: AssistantMessage = {
|
|
104
116
|
role: "assistant",
|
|
@@ -144,15 +156,15 @@ export function streamAnthropicOAuth(
|
|
|
144
156
|
|
|
145
157
|
const params: MessageCreateParamsStreaming = {
|
|
146
158
|
model: model.id,
|
|
147
|
-
messages: convertPiMessagesToAnthropic(
|
|
159
|
+
messages: convertPiMessagesToAnthropic(transcript.messages, isOAuth, model),
|
|
148
160
|
max_tokens: maxTokens,
|
|
149
161
|
stream: true,
|
|
150
162
|
};
|
|
151
163
|
|
|
152
|
-
const system = buildAnthropicSystemPrompt(
|
|
164
|
+
const system = buildAnthropicSystemPrompt(systemPrompt, isOAuth);
|
|
153
165
|
if (system) params.system = system as never;
|
|
154
|
-
if (
|
|
155
|
-
params.tools = convertPiToolsToAnthropic(
|
|
166
|
+
if (tools.length)
|
|
167
|
+
params.tools = convertPiToolsToAnthropic(tools, isOAuth);
|
|
156
168
|
|
|
157
169
|
if (options?.reasoning && model.reasoning && maxTokens > 1) {
|
|
158
170
|
const defaultBudgets: Record<string, number> = {
|
|
@@ -168,11 +180,40 @@ export function streamAnthropicOAuth(
|
|
|
168
180
|
];
|
|
169
181
|
const requestedBudget =
|
|
170
182
|
customBudget ?? defaultBudgets[options.reasoning] ?? 10240;
|
|
183
|
+
const display = "summarized";
|
|
184
|
+
const forceAdaptive = (
|
|
185
|
+
model.compat as { forceAdaptiveThinking?: boolean } | undefined
|
|
186
|
+
)?.forceAdaptiveThinking;
|
|
187
|
+
const id = model.id.toLowerCase().replace(/\./g, "-");
|
|
188
|
+
const adaptive =
|
|
189
|
+
forceAdaptive === true ||
|
|
190
|
+
(forceAdaptive !== false &&
|
|
191
|
+
(/claude-(?:opus|sonnet|haiku|fable|mythos)-5(?:-|$)/.test(id) ||
|
|
192
|
+
/claude-(?:opus|sonnet|haiku|fable|mythos)-4-(?:[6-9]|\d{2,})(?:-|$)/.test(
|
|
193
|
+
id,
|
|
194
|
+
)));
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
if (adaptive) {
|
|
197
|
+
const mapped = model.thinkingLevelMap?.[options.reasoning];
|
|
198
|
+
const effort =
|
|
199
|
+
typeof mapped === "string"
|
|
200
|
+
? mapped
|
|
201
|
+
: options.reasoning === "minimal" || options.reasoning === "low"
|
|
202
|
+
? "low"
|
|
203
|
+
: options.reasoning === "medium"
|
|
204
|
+
? "medium"
|
|
205
|
+
: options.reasoning === "high"
|
|
206
|
+
? "high"
|
|
207
|
+
: "high";
|
|
208
|
+
params.thinking = { type: "adaptive", display } as never;
|
|
209
|
+
Object.assign(params, { output_config: { effort } });
|
|
210
|
+
} else {
|
|
211
|
+
params.thinking = {
|
|
212
|
+
type: "enabled",
|
|
213
|
+
budget_tokens: Math.min(requestedBudget, maxTokens - 1),
|
|
214
|
+
display,
|
|
215
|
+
} as never;
|
|
216
|
+
}
|
|
176
217
|
}
|
|
177
218
|
|
|
178
219
|
// Raw stream instead of the MessageStream helper: MessageStream
|
|
@@ -268,7 +309,7 @@ export function streamAnthropicOAuth(
|
|
|
268
309
|
name: isOAuth
|
|
269
310
|
? fromClaudeCodeToolName(
|
|
270
311
|
event.content_block.name,
|
|
271
|
-
|
|
312
|
+
tools,
|
|
272
313
|
)
|
|
273
314
|
: event.content_block.name,
|
|
274
315
|
arguments: {},
|
|
@@ -322,10 +363,7 @@ export function streamAnthropicOAuth(
|
|
|
322
363
|
) {
|
|
323
364
|
block.partialJson += event.delta.partial_json;
|
|
324
365
|
try {
|
|
325
|
-
block.arguments = JSON.parse(block.partialJson) as
|
|
326
|
-
string,
|
|
327
|
-
unknown
|
|
328
|
-
>;
|
|
366
|
+
block.arguments = JSON.parse(block.partialJson) as JsonObject;
|
|
329
367
|
} catch {}
|
|
330
368
|
stream.push({
|
|
331
369
|
type: "toolcall_delta",
|
|
@@ -361,10 +399,7 @@ export function streamAnthropicOAuth(
|
|
|
361
399
|
});
|
|
362
400
|
} else if (block.type === "toolCall") {
|
|
363
401
|
try {
|
|
364
|
-
block.arguments = JSON.parse(block.partialJson) as
|
|
365
|
-
string,
|
|
366
|
-
unknown
|
|
367
|
-
>;
|
|
402
|
+
block.arguments = JSON.parse(block.partialJson) as JsonObject;
|
|
368
403
|
} catch {}
|
|
369
404
|
delete (block as { partialJson?: string }).partialJson;
|
|
370
405
|
stream.push({
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const CLAUDE_CODE_VERSION_ENV = "PI_ANTHROPIC_OAUTH_CLAUDE_CODE_VERSION";
|
|
2
|
+
const DEFAULT_CLAUDE_CODE_VERSION = "2.1.280";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code version advertised to the API (user agent and billing header).
|
|
6
|
+
*
|
|
7
|
+
* Anthropic gates new models on the client version; override with
|
|
8
|
+
* PI_ANTHROPIC_OAUTH_CLAUDE_CODE_VERSION when a newer one is required.
|
|
9
|
+
*/
|
|
10
|
+
export function getClaudeCodeVersion(
|
|
11
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
12
|
+
): string {
|
|
13
|
+
return env[CLAUDE_CODE_VERSION_ENV]?.trim() || DEFAULT_CLAUDE_CODE_VERSION;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* User agent matching the current Claude Code CLI client. The classifier
|
|
18
|
+
* rejects the older `claude-code/<ver>` form for premium models.
|
|
19
|
+
*/
|
|
20
|
+
export function makeClaudeCodeUserAgent(
|
|
21
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
22
|
+
): string {
|
|
23
|
+
return `claude-cli/${getClaudeCodeVersion(env)} (external, cli)`;
|
|
24
|
+
}
|