@gotgenes/pi-anthropic-auth 0.2.1 → 0.4.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 +18 -92
- package/dist/constants.d.ts +26 -1
- package/dist/constants.js +47 -1
- package/dist/request-shaping.js +1 -13
- package/dist/system-prompt-shaping.d.ts +21 -3
- package/dist/system-prompt-shaping.js +38 -10
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -7,33 +7,14 @@
|
|
|
7
7
|
[](https://pnpm.io/)
|
|
8
8
|
[](https://pi.mariozechner.at/)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A [Pi](https://pi.mariozechner.at/) extension that improves compatibility with Anthropic Claude Pro/Max OAuth (i.e., your Claude subscription) while preserving Pi's normal Anthropic behavior.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## What It Does
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Pi works great with Anthropic API keys out of the box.
|
|
15
|
+
This extension fills in the gaps for users who want to use their **Claude Pro or Max subscription** via OAuth instead.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
1. built-in provider name: `anthropic`
|
|
19
|
-
2. built-in model list
|
|
20
|
-
3. normal Anthropic API-key behavior
|
|
21
|
-
4. native `/login anthropic` UX
|
|
22
|
-
|
|
23
|
-
## Current Behavior
|
|
24
|
-
|
|
25
|
-
The extension currently does the following:
|
|
26
|
-
|
|
27
|
-
1. re-registers the built-in `anthropic` provider with only an `oauth` override
|
|
28
|
-
2. reuses Pi's native Anthropic login and refresh helpers from `@mariozechner/pi-ai/oauth`
|
|
29
|
-
3. preserves the old refresh token when refresh responses omit `refresh_token`
|
|
30
|
-
4. shapes OAuth Anthropic request payloads through `before_provider_request`
|
|
31
|
-
5. prepends the Anthropic billing/content-consistency header block to `system[]`
|
|
32
|
-
6. avoids adding `cache_control` to the injected billing block
|
|
33
|
-
7. normalizes assistant `[tool_use..., text]` ordering for Anthropic OAuth payloads
|
|
34
|
-
8. replaces Pi's default prompt body with a minimal neutral prompt for Anthropic OAuth when the default Pi harness prompt is detected, while preserving project context
|
|
35
|
-
|
|
36
|
-
The extension does not currently replace Pi's built-in Anthropic streaming transport.
|
|
17
|
+
It keeps everything you'd expect — the built-in `anthropic` provider, the full model list, API-key behavior, and the native `/login anthropic` flow — and layers on the compatibility fixes needed to make OAuth subscriptions work reliably.
|
|
37
18
|
|
|
38
19
|
## Install
|
|
39
20
|
|
|
@@ -47,90 +28,35 @@ To try it without permanently installing:
|
|
|
47
28
|
pi -e npm:@gotgenes/pi-anthropic-auth
|
|
48
29
|
```
|
|
49
30
|
|
|
50
|
-
## Usage
|
|
31
|
+
## Usage
|
|
51
32
|
|
|
52
|
-
1. `/login anthropic`
|
|
53
|
-
2.
|
|
54
|
-
3.
|
|
33
|
+
1. Run `/login anthropic` as usual — Pi's native Anthropic login flow is preserved.
|
|
34
|
+
2. Select a Claude Pro/Max model and start chatting. The extension handles compatibility transparently.
|
|
35
|
+
3. API-key behavior is unaffected; the extension's changes apply only to OAuth sessions.
|
|
55
36
|
|
|
56
37
|
## Development
|
|
57
38
|
|
|
58
39
|
### Requirements
|
|
59
40
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
- `pnpm`
|
|
42
|
+
- a local `pi` installation
|
|
43
|
+
- Anthropic OAuth credentials configured through Pi
|
|
63
44
|
|
|
64
|
-
###
|
|
45
|
+
### Commands
|
|
65
46
|
|
|
66
47
|
```bash
|
|
67
|
-
pnpm install
|
|
48
|
+
pnpm install # install dependencies
|
|
49
|
+
pnpm run check # typecheck
|
|
50
|
+
pnpm test # run tests
|
|
51
|
+
pnpm run build # compile
|
|
68
52
|
```
|
|
69
53
|
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
pnpm run check
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Run Tests
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
pnpm test
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Build
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
pnpm run build
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Load Local Build In Pi
|
|
54
|
+
### Load a Local Build
|
|
89
55
|
|
|
90
56
|
```bash
|
|
91
57
|
pi -e /absolute/path/to/pi-anthropic-auth/dist/index.js
|
|
92
58
|
```
|
|
93
59
|
|
|
94
|
-
### Fast Repro Loop
|
|
95
|
-
|
|
96
|
-
The most useful non-interactive repro loop is:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
pi \
|
|
100
|
-
--model anthropic/claude-sonnet-4-6 \
|
|
101
|
-
--no-session \
|
|
102
|
-
--tools read,grep,find,ls \
|
|
103
|
-
-e /Users/chris/development/pi-anthropic-auth/dist/index.js \
|
|
104
|
-
-p "How many lines are in @AGENTS.md ?"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
That path was used to validate:
|
|
108
|
-
|
|
109
|
-
1. simple prompts
|
|
110
|
-
2. tool use
|
|
111
|
-
3. multi-turn continuation
|
|
112
|
-
4. structured output
|
|
113
|
-
5. expired-token refresh
|
|
114
|
-
|
|
115
|
-
## Project Skills
|
|
116
|
-
|
|
117
|
-
Project-local skills live in `.agents/skills/`:
|
|
118
|
-
|
|
119
|
-
1. `anthropic`: Anthropic OAuth debugging workflow and lessons learned
|
|
120
|
-
2. `pi-cli-repro`: repeatable `pi -p ... -e ...` repro workflow
|
|
121
|
-
3. `frontmatter`: Pi skill frontmatter template and rules
|
|
122
|
-
|
|
123
|
-
## Key Files
|
|
124
|
-
|
|
125
|
-
1. `src/index.ts`
|
|
126
|
-
2. `src/anthropic-oauth.ts`
|
|
127
|
-
3. `src/request-shaping.ts`
|
|
128
|
-
4. `src/system-prompt-shaping.ts`
|
|
129
|
-
5. `docs/plans/minimal-anthropic-override.md`
|
|
130
|
-
6. `docs/plans/gap-analysis-and-next-steps.md`
|
|
131
|
-
7. `AGENTS.md`
|
|
132
|
-
8. `.agents/skills/`
|
|
133
|
-
|
|
134
60
|
## Acknowledgments
|
|
135
61
|
|
|
136
62
|
This project was inspired by [opencode-anthropic-auth](https://github.com/ex-machina-co/opencode-anthropic-auth/), which solved the same Anthropic OAuth compatibility problem for [OpenCode](https://opencode.ai/).
|
package/dist/constants.d.ts
CHANGED
|
@@ -36,10 +36,35 @@ export declare const CLAUDE_CODE_IDENTITY_PREFIX = "You are Claude Code, Anthrop
|
|
|
36
36
|
* too far from what Anthropic expects, OAuth requests may be rejected or
|
|
37
37
|
* counted incorrectly.
|
|
38
38
|
*/
|
|
39
|
-
export declare const CLAUDE_CODE_VERSION = "2.1.
|
|
39
|
+
export declare const CLAUDE_CODE_VERSION = "2.1.108";
|
|
40
40
|
/** Salt used in the billing header suffix hash. */
|
|
41
41
|
export declare const BILLING_HEADER_SALT = "59cf53e54c78";
|
|
42
42
|
/** Character positions sampled from the first user message for the billing hash. */
|
|
43
43
|
export declare const BILLING_HEADER_POSITIONS: readonly [4, 7, 20];
|
|
44
44
|
/** Entrypoint identifier included in the billing header. */
|
|
45
45
|
export declare const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
46
|
+
/**
|
|
47
|
+
* Strings whose presence in a paragraph marks it as Pi-specific and droppable.
|
|
48
|
+
*
|
|
49
|
+
* Each entry is checked with `paragraph.includes(anchor)`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const PARAGRAPH_REMOVAL_ANCHORS: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Inline text replacements applied after paragraph removal.
|
|
54
|
+
*
|
|
55
|
+
* These handle known Anthropic classifier trigger phrases that may appear
|
|
56
|
+
* in paragraphs we want to keep. Each rule is applied with `replaceAll`.
|
|
57
|
+
*
|
|
58
|
+
* The "Here is some useful information..." phrase was isolated by
|
|
59
|
+
* `opencode-anthropic-auth` via sliding-window bisection of a 10KB failing
|
|
60
|
+
* prompt. When it reaches Anthropic combined with typical agent context,
|
|
61
|
+
* /v1/messages responds with a 400 disguised as "You're out of extra usage."
|
|
62
|
+
* Replacing the word "useful" is enough to unblock the request.
|
|
63
|
+
*
|
|
64
|
+
* We don't currently emit this phrase, but it's included as a documented
|
|
65
|
+
* future risk per Issue #10.
|
|
66
|
+
*/
|
|
67
|
+
export declare const TEXT_REPLACEMENTS: readonly {
|
|
68
|
+
match: string;
|
|
69
|
+
replacement: string;
|
|
70
|
+
}[];
|
package/dist/constants.js
CHANGED
|
@@ -53,10 +53,56 @@ export const CLAUDE_CODE_IDENTITY_PREFIX = "You are Claude Code, Anthropic's off
|
|
|
53
53
|
* too far from what Anthropic expects, OAuth requests may be rejected or
|
|
54
54
|
* counted incorrectly.
|
|
55
55
|
*/
|
|
56
|
-
export const CLAUDE_CODE_VERSION = "2.1.
|
|
56
|
+
export const CLAUDE_CODE_VERSION = "2.1.108";
|
|
57
57
|
/** Salt used in the billing header suffix hash. */
|
|
58
58
|
export const BILLING_HEADER_SALT = "59cf53e54c78";
|
|
59
59
|
/** Character positions sampled from the first user message for the billing hash. */
|
|
60
60
|
export const BILLING_HEADER_POSITIONS = [4, 7, 20];
|
|
61
61
|
/** Entrypoint identifier included in the billing header. */
|
|
62
62
|
export const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Anchor-driven sanitizer constants
|
|
65
|
+
//
|
|
66
|
+
// Used by the system prompt sanitizer to remove Pi-specific paragraphs
|
|
67
|
+
// (identity, documentation references, filler) while preserving extension-
|
|
68
|
+
// contributed content (tool snippets, guidelines, appended content).
|
|
69
|
+
//
|
|
70
|
+
// A paragraph is any text between blank lines. If a paragraph contains any
|
|
71
|
+
// anchor string, it is dropped entirely. This is resilient to upstream
|
|
72
|
+
// rewording — as long as the anchor still appears somewhere in the paragraph,
|
|
73
|
+
// removal works regardless of surrounding text changes.
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Strings whose presence in a paragraph marks it as Pi-specific and droppable.
|
|
77
|
+
*
|
|
78
|
+
* Each entry is checked with `paragraph.includes(anchor)`.
|
|
79
|
+
*/
|
|
80
|
+
export const PARAGRAPH_REMOVAL_ANCHORS = [
|
|
81
|
+
// Pi identity sentence
|
|
82
|
+
"operating inside pi, a coding agent harness",
|
|
83
|
+
// Pi-specific filler about custom tools
|
|
84
|
+
"In addition to the tools above",
|
|
85
|
+
// Pi documentation block — references Pi-specific docs/paths
|
|
86
|
+
"Pi documentation (read only when the user asks about pi itself",
|
|
87
|
+
];
|
|
88
|
+
/**
|
|
89
|
+
* Inline text replacements applied after paragraph removal.
|
|
90
|
+
*
|
|
91
|
+
* These handle known Anthropic classifier trigger phrases that may appear
|
|
92
|
+
* in paragraphs we want to keep. Each rule is applied with `replaceAll`.
|
|
93
|
+
*
|
|
94
|
+
* The "Here is some useful information..." phrase was isolated by
|
|
95
|
+
* `opencode-anthropic-auth` via sliding-window bisection of a 10KB failing
|
|
96
|
+
* prompt. When it reaches Anthropic combined with typical agent context,
|
|
97
|
+
* /v1/messages responds with a 400 disguised as "You're out of extra usage."
|
|
98
|
+
* Replacing the word "useful" is enough to unblock the request.
|
|
99
|
+
*
|
|
100
|
+
* We don't currently emit this phrase, but it's included as a documented
|
|
101
|
+
* future risk per Issue #10.
|
|
102
|
+
*/
|
|
103
|
+
export const TEXT_REPLACEMENTS = [
|
|
104
|
+
{
|
|
105
|
+
match: "Here is some useful information about the environment you are running in:",
|
|
106
|
+
replacement: "Environment context you are running in:",
|
|
107
|
+
},
|
|
108
|
+
];
|
package/dist/request-shaping.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { BILLING_HEADER_POSITIONS, BILLING_HEADER_SALT, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY_PREFIX, CLAUDE_CODE_VERSION, MINIMAL_ANTHROPIC_OAUTH_PROMPT_PREFIX, } from "./constants.js";
|
|
3
3
|
import { shapeSystemBlocks } from "./system-prompt-shaping.js";
|
|
4
|
-
const ANTHROPIC_OAUTH_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
|
|
5
4
|
function isRecord(value) {
|
|
6
5
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
6
|
}
|
|
@@ -90,13 +89,6 @@ function prependBillingHeader(system, messages) {
|
|
|
90
89
|
const billingBlock = { type: "text", text: billingHeader };
|
|
91
90
|
return [billingBlock, ...systemBlocks];
|
|
92
91
|
}
|
|
93
|
-
function mergeAnthropicBetas(betaHeader) {
|
|
94
|
-
const existing = (betaHeader ?? "")
|
|
95
|
-
.split(",")
|
|
96
|
-
.map((value) => value.trim())
|
|
97
|
-
.filter(Boolean);
|
|
98
|
-
return [...new Set([...ANTHROPIC_OAUTH_BETAS, ...existing])].join(",");
|
|
99
|
-
}
|
|
100
92
|
/**
|
|
101
93
|
* Splits assistant messages that interleave text and tool_use blocks.
|
|
102
94
|
*
|
|
@@ -139,13 +131,9 @@ export function shapeAnthropicOAuthPayload(payload) {
|
|
|
139
131
|
const shapedSystem = Array.isArray(payload.system)
|
|
140
132
|
? shapeSystemBlocks(payload.system)
|
|
141
133
|
: payload.system;
|
|
142
|
-
|
|
134
|
+
return {
|
|
143
135
|
...payload,
|
|
144
136
|
messages: normalizedMessages,
|
|
145
137
|
system: prependBillingHeader(shapedSystem, normalizedMessages),
|
|
146
138
|
};
|
|
147
|
-
if (typeof payload["anthropic-beta"] === "string") {
|
|
148
|
-
shapedPayload["anthropic-beta"] = mergeAnthropicBetas(payload["anthropic-beta"]);
|
|
149
|
-
}
|
|
150
|
-
return shapedPayload;
|
|
151
139
|
}
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize system prompt text by removing paragraphs containing known
|
|
3
|
+
* Pi-specific anchor strings and applying inline text replacements for
|
|
4
|
+
* known Anthropic classifier trigger phrases.
|
|
5
|
+
*
|
|
6
|
+
* A paragraph is any text between blank lines (`\n\n`).
|
|
7
|
+
*
|
|
8
|
+
* This approach is resilient to upstream rewording — as long as the anchor
|
|
9
|
+
* string still appears somewhere in the paragraph, removal works regardless
|
|
10
|
+
* of how the surrounding text changes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sanitizeSystemText(text: string): string;
|
|
1
13
|
/**
|
|
2
14
|
* Shape a system prompt string for Anthropic OAuth compatibility.
|
|
3
15
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
16
|
+
* Uses an anchor-driven sanitizer to remove Pi-specific paragraphs
|
|
17
|
+
* (identity, documentation references, filler) while preserving
|
|
18
|
+
* extension-contributed content (tool snippets, guidelines, appended
|
|
19
|
+
* content, project context, skills, and date/cwd footer).
|
|
20
|
+
*
|
|
21
|
+
* Prepends a minimal neutral prompt to replace the removed Pi identity.
|
|
22
|
+
*
|
|
23
|
+
* If the Pi default prompt prefix is not present, the prompt is returned
|
|
24
|
+
* unchanged — this gates shaping so non-Pi prompts pass through.
|
|
7
25
|
*/
|
|
8
26
|
export declare function shapeAnthropicOAuthSystemPrompt(systemPrompt: string): string;
|
|
9
27
|
type TextBlock = {
|
|
@@ -1,24 +1,52 @@
|
|
|
1
|
-
import { MINIMAL_ANTHROPIC_OAUTH_PROMPT, PI_DEFAULT_PROMPT_PREFIX, } from "./constants.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { MINIMAL_ANTHROPIC_OAUTH_PROMPT, PARAGRAPH_REMOVAL_ANCHORS, PI_DEFAULT_PROMPT_PREFIX, TEXT_REPLACEMENTS, } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sanitize system prompt text by removing paragraphs containing known
|
|
4
|
+
* Pi-specific anchor strings and applying inline text replacements for
|
|
5
|
+
* known Anthropic classifier trigger phrases.
|
|
6
|
+
*
|
|
7
|
+
* A paragraph is any text between blank lines (`\n\n`).
|
|
8
|
+
*
|
|
9
|
+
* This approach is resilient to upstream rewording — as long as the anchor
|
|
10
|
+
* string still appears somewhere in the paragraph, removal works regardless
|
|
11
|
+
* of how the surrounding text changes.
|
|
12
|
+
*/
|
|
13
|
+
export function sanitizeSystemText(text) {
|
|
14
|
+
const paragraphs = text.split(/\n\n+/);
|
|
15
|
+
const filtered = paragraphs.filter((paragraph) => {
|
|
16
|
+
for (const anchor of PARAGRAPH_REMOVAL_ANCHORS) {
|
|
17
|
+
if (paragraph.includes(anchor))
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
let result = filtered.join("\n\n");
|
|
23
|
+
for (const rule of TEXT_REPLACEMENTS) {
|
|
24
|
+
result = result.replaceAll(rule.match, rule.replacement);
|
|
25
|
+
}
|
|
26
|
+
return result.trim();
|
|
5
27
|
}
|
|
6
28
|
/**
|
|
7
29
|
* Shape a system prompt string for Anthropic OAuth compatibility.
|
|
8
30
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
31
|
+
* Uses an anchor-driven sanitizer to remove Pi-specific paragraphs
|
|
32
|
+
* (identity, documentation references, filler) while preserving
|
|
33
|
+
* extension-contributed content (tool snippets, guidelines, appended
|
|
34
|
+
* content, project context, skills, and date/cwd footer).
|
|
35
|
+
*
|
|
36
|
+
* Prepends a minimal neutral prompt to replace the removed Pi identity.
|
|
37
|
+
*
|
|
38
|
+
* If the Pi default prompt prefix is not present, the prompt is returned
|
|
39
|
+
* unchanged — this gates shaping so non-Pi prompts pass through.
|
|
12
40
|
*/
|
|
13
41
|
export function shapeAnthropicOAuthSystemPrompt(systemPrompt) {
|
|
14
42
|
if (!systemPrompt.includes(PI_DEFAULT_PROMPT_PREFIX)) {
|
|
15
43
|
return systemPrompt;
|
|
16
44
|
}
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
45
|
+
const sanitized = sanitizeSystemText(systemPrompt);
|
|
46
|
+
if (!sanitized) {
|
|
19
47
|
return MINIMAL_ANTHROPIC_OAUTH_PROMPT;
|
|
20
48
|
}
|
|
21
|
-
return `${MINIMAL_ANTHROPIC_OAUTH_PROMPT}${
|
|
49
|
+
return `${MINIMAL_ANTHROPIC_OAUTH_PROMPT}\n\n${sanitized}`;
|
|
22
50
|
}
|
|
23
51
|
/**
|
|
24
52
|
* Apply system prompt shaping to an array of Anthropic system text blocks.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-anthropic-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Pi extension package for Anthropic OAuth compatibility",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Chris Lasher"
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"lint:md": "markdownlint-cli2 '*.md'",
|
|
39
39
|
"lint:all": "pnpm run lint && pnpm run lint:md",
|
|
40
40
|
"format": "biome format --write .",
|
|
41
|
-
"test": "
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
42
43
|
"prepublishOnly": "pnpm run check && pnpm run test && pnpm run build"
|
|
43
44
|
},
|
|
44
45
|
"pi": {
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"@mariozechner/pi-ai": "^0.68.0",
|
|
57
58
|
"@mariozechner/pi-coding-agent": "^0.68.0",
|
|
58
59
|
"markdownlint-cli2": "^0.22.0",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
60
|
+
"typescript": "^6.0.3",
|
|
61
|
+
"vitest": "^4.1.5"
|
|
61
62
|
}
|
|
62
63
|
}
|