pi-anthropic-oauth 0.1.7 → 0.1.9

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
@@ -1,12 +1,11 @@
1
1
  # Pi Anthropic OAuth
2
2
 
3
- [![npm](https://img.shields.io/npm/v/pi-anthropic-oauth?style=flat-square&logo=npm&logoColor=white&label=npm&color=7c3aed)](https://www.npmjs.com/package/pi-anthropic-oauth) [![downloads](https://img.shields.io/npm/dm/pi-anthropic-oauth?style=flat-square&logo=npm&logoColor=white&label=downloads&color=7c3aed)](https://www.npmjs.com/package/pi-anthropic-oauth) [![node](https://img.shields.io/badge/node-%3E%3D18-7c3aed?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org)
3
+ [![npm](https://img.shields.io/npm/v/pi-anthropic-oauth?style=flat-square&logo=npm&logoColor=white&label=npm&color=7c3aed)](https://www.npmjs.com/package/pi-anthropic-oauth) [![node](https://img.shields.io/badge/node-%3E%3D18-7c3aed?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org)
4
4
 
5
5
  Claude Pro/Max OAuth extension for Pi.
6
6
 
7
7
  > [!WARNING]
8
8
  > Use this at your own risk. This may go against Anthropic's terms.
9
- > I do not recommend using this with gsd2 or any extensions that will cause heavy token usage.
10
9
 
11
10
  ## Install
12
11
 
@@ -28,10 +27,36 @@ Claude Pro/Max
28
27
 
29
28
  ## Notes
30
29
 
30
+ - uses Pi's built-in Anthropic model list
31
31
  - sends Claude Code-like OAuth headers
32
32
  - rewrites Pi system identity where needed
33
33
  - auto-creates `~/.Claude Code` → `~/.pi` symlink
34
34
 
35
+ ## Custom models
36
+
37
+ To add another Anthropic model, create `~/.pi/agent/models.json`:
38
+
39
+ ```json
40
+ {
41
+ "providers": {
42
+ "anthropic": {
43
+ "baseUrl": "https://api.anthropic.com",
44
+ "apiKey": "REQUIRED_BY_PI_FOR_CUSTOM_MODELS",
45
+ "api": "anthropic-messages",
46
+ "models": [
47
+ {
48
+ "id": "your-model-id",
49
+ "name": "Your Model Name"
50
+ }
51
+ ]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ > [!NOTE]
58
+ > Pi requires `baseUrl`, `apiKey`, and `api` when defining custom models in `models.json`. With this extension, requests normally authenticate through Claude Pro/Max OAuth after `/login anthropic`, so `apiKey` is present to satisfy Pi's config requirements and does not need to be a valid Anthropic API key.
59
+
35
60
  ## License
36
61
 
37
62
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-anthropic-oauth",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Claude Pro/Max OAuth extension for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/auth.ts CHANGED
@@ -152,7 +152,6 @@ export async function refreshAnthropicToken(
152
152
  grant_type: "refresh_token",
153
153
  client_id: CLIENT_ID,
154
154
  refresh_token: credentials.refresh,
155
- scope: SCOPES,
156
155
  }),
157
156
  },
158
157
  "Token refresh",
package/src/convert.ts CHANGED
@@ -98,8 +98,6 @@ export function convertPiMessagesToAnthropic(
98
98
  for (const block of message.content) {
99
99
  if (block.type === "text" && block.text.trim()) {
100
100
  blocks.push({ type: "text", text: sanitizeSurrogates(block.text) });
101
- } else if (block.type === "thinking" && block.thinking.trim()) {
102
- blocks.push({ type: "text", text: sanitizeSurrogates(block.thinking) });
103
101
  } else if (block.type === "toolCall") {
104
102
  blocks.push({
105
103
  type: "tool_use",
package/src/index.ts CHANGED
@@ -1,40 +1,27 @@
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, ProviderConfig } from "@mariozechner/pi-coding-agent";
4
+ import {
5
+ AuthStorage,
6
+ ModelRegistry,
7
+ type ExtensionAPI,
8
+ type ProviderConfig,
9
+ } from "@mariozechner/pi-coding-agent";
5
10
  import type { OAuthCredentials } from "@mariozechner/pi-ai";
6
11
  import { loginAnthropic, refreshAnthropicToken } from "./auth.js";
7
12
  import { streamAnthropicOAuth } from "./stream.js";
8
13
 
9
- const MODELS = [
10
- {
11
- id: "claude-opus-4-6",
12
- name: "Claude Opus 4.6",
13
- reasoning: true,
14
- input: ["text", "image"] as ("text" | "image")[],
15
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
16
- contextWindow: 1000000,
17
- maxTokens: 128000,
18
- },
19
- {
20
- id: "claude-sonnet-4-6",
21
- name: "Claude Sonnet 4.6",
22
- reasoning: true,
23
- input: ["text", "image"] as ("text" | "image")[],
24
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
25
- contextWindow: 1000000,
26
- maxTokens: 64000,
27
- },
28
- {
29
- id: "claude-haiku-4-5",
30
- name: "Claude Haiku 4.5",
31
- reasoning: true,
32
- input: ["text", "image"] as ("text" | "image")[],
33
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
34
- contextWindow: 200000,
35
- maxTokens: 64000,
36
- },
37
- ];
14
+ const DEFAULT_OPUS_4_7: NonNullable<ProviderConfig["models"]>[number] = {
15
+ id: "claude-opus-4-7",
16
+ name: "Claude Opus 4.7",
17
+ api: "anthropic-messages",
18
+ reasoning: true,
19
+ input: ["text", "image"],
20
+ cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
21
+ contextWindow: 1000000,
22
+ maxTokens: 128000,
23
+ compat: undefined,
24
+ };
38
25
 
39
26
  function ensureClaudeCodeSymlink() {
40
27
  const target = join(homedir(), ".pi");
@@ -42,17 +29,42 @@ function ensureClaudeCodeSymlink() {
42
29
  if (existsSync(target) && !existsSync(link)) {
43
30
  try {
44
31
  symlinkSync(target, link);
45
- } catch {}
32
+ } catch { }
46
33
  }
47
34
  }
48
35
 
36
+ function getAnthropicModels(): NonNullable<ProviderConfig["models"]> {
37
+ const modelRegistry = ModelRegistry.create(AuthStorage.inMemory());
38
+ const models: NonNullable<ProviderConfig["models"]> = modelRegistry
39
+ .getAll()
40
+ .filter((model) => model.provider === "anthropic")
41
+ .map((model) => ({
42
+ id: model.id,
43
+ name: model.name,
44
+ api: model.api ?? "anthropic-messages",
45
+ reasoning: model.reasoning,
46
+ input: model.input,
47
+ cost: model.cost,
48
+ contextWindow: model.contextWindow,
49
+ maxTokens: model.maxTokens,
50
+ compat: model.compat,
51
+ }));
52
+
53
+ if (!models.some((model) => model.id === DEFAULT_OPUS_4_7.id)) {
54
+ models.push(DEFAULT_OPUS_4_7);
55
+ }
56
+
57
+ return models;
58
+ }
59
+
49
60
  export default function (pi: ExtensionAPI) {
50
61
  ensureClaudeCodeSymlink();
62
+ const models = getAnthropicModels();
63
+
51
64
  pi.registerProvider("anthropic", {
52
65
  baseUrl: "https://api.anthropic.com",
53
- apiKey: "ANTHROPIC_MAX_API_KEY",
54
- api: "anthropic-max-api",
55
- models: [...MODELS],
66
+ api: "anthropic-messages",
67
+ models,
56
68
  oauth: {
57
69
  name: "Claude Pro/Max",
58
70
  usesCallbackServer: true,
package/src/stream.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  type SimpleStreamOptions,
12
12
  type StopReason,
13
13
  } from "@mariozechner/pi-ai";
14
- import { USER_AGENT } from "./auth.js";
14
+ import { isClaudeOAuthAccessToken, USER_AGENT } from "./auth.js";
15
15
  import {
16
16
  convertPiMessagesToAnthropic,
17
17
  convertPiToolsToAnthropic,
@@ -63,8 +63,16 @@ function makeDefaultHeaders(
63
63
  }
64
64
 
65
65
  if (options?.headers) {
66
- for (const [key, value] of Object.entries(options.headers))
66
+ for (const [key, value] of Object.entries(options.headers)) {
67
+ const normalizedKey = key.toLowerCase();
68
+ if (
69
+ isOAuth &&
70
+ (normalizedKey === "x-api-key" || normalizedKey === "authorization")
71
+ ) {
72
+ continue;
73
+ }
67
74
  headers[key] = value;
75
+ }
68
76
  }
69
77
 
70
78
  return headers;
@@ -97,8 +105,12 @@ export function streamAnthropicOAuth(
97
105
  };
98
106
 
99
107
  try {
100
- const apiKey = options?.apiKey ?? "";
101
- const isOAuth = true;
108
+ const apiKey = options?.apiKey;
109
+ if (!apiKey) {
110
+ throw new Error("No Anthropic auth available. Run /login anthropic.");
111
+ }
112
+
113
+ const isOAuth = isClaudeOAuthAccessToken(apiKey);
102
114
  const defaultHeaders = makeDefaultHeaders(isOAuth, options);
103
115
 
104
116
  if (isOAuth) defaultHeaders.authorization = `Bearer ${apiKey}`;