@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 CHANGED
@@ -7,33 +7,14 @@
7
7
  [![pnpm](https://img.shields.io/badge/pnpm-%3E%3D9-F69220?style=flat&logo=pnpm&logoColor=white)](https://pnpm.io/)
8
8
  [![Pi Package](https://img.shields.io/badge/Pi-Package-6366F1?style=flat)](https://pi.mariozechner.at/)
9
9
 
10
- Minimal Pi extension package for Anthropic Claude Pro/Max OAuth compatibility.
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
- ## Purpose
12
+ ## What It Does
13
13
 
14
- This package minimally overrides Pi's built-in `anthropic` provider to improve Claude Pro/Max OAuth compatibility while preserving Pi's normal Anthropic behavior wherever possible.
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
- The design goal is to preserve:
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 Notes
31
+ ## Usage
51
32
 
52
- 1. `/login anthropic` should continue using Pi's native Anthropic UX.
53
- 2. API-key Anthropic behavior should remain the baseline behavior.
54
- 3. The extension's compatibility logic is intended to affect only Anthropic OAuth requests.
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
- 1. `pnpm`
61
- 2. a local `pi` installation
62
- 3. Anthropic OAuth credentials configured through Pi
41
+ - `pnpm`
42
+ - a local `pi` installation
43
+ - Anthropic OAuth credentials configured through Pi
63
44
 
64
- ### Install Dependencies
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
- ### Typecheck
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/).
@@ -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.87";
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.87";
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
+ ];
@@ -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
- const shapedPayload = {
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
- * Replaces Pi's verbose default preamble with a minimal neutral prompt while
5
- * preserving any project context that follows. Returns the original string
6
- * unchanged when Pi's default preamble is not detected.
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
- function findProjectContextStart(systemPrompt) {
3
- const marker = "\n\n# Project Context\n\n";
4
- return systemPrompt.indexOf(marker);
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
- * Replaces Pi's verbose default preamble with a minimal neutral prompt while
10
- * preserving any project context that follows. Returns the original string
11
- * unchanged when Pi's default preamble is not detected.
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 projectContextStart = findProjectContextStart(systemPrompt);
18
- if (projectContextStart === -1) {
45
+ const sanitized = sanitizeSystemText(systemPrompt);
46
+ if (!sanitized) {
19
47
  return MINIMAL_ANTHROPIC_OAUTH_PROMPT;
20
48
  }
21
- return `${MINIMAL_ANTHROPIC_OAUTH_PROMPT}${systemPrompt.slice(projectContextStart)}`;
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.2.1",
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": "tsx --test test/**/*.test.ts",
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
- "tsx": "^4.20.6",
60
- "typescript": "^6.0.3"
60
+ "typescript": "^6.0.3",
61
+ "vitest": "^4.1.5"
61
62
  }
62
63
  }