@gotgenes/pi-anthropic-auth 0.3.0 → 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.
@@ -43,3 +43,28 @@ export declare const BILLING_HEADER_SALT = "59cf53e54c78";
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
@@ -60,3 +60,49 @@ export const BILLING_HEADER_SALT = "59cf53e54c78";
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
- shapedPayload["anthropic-beta"] = mergeAnthropicBetas(typeof payload["anthropic-beta"] === "string"
148
- ? payload["anthropic-beta"]
149
- : undefined);
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.3.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": "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
  }