pi-anthropic-oauth 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-anthropic-oauth",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Claude Pro/Max OAuth extension for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/convert.ts CHANGED
@@ -11,8 +11,10 @@ type ToolResultContentBlock =
11
11
  };
12
12
  };
13
13
  import type {
14
+ Api,
14
15
  ImageContent,
15
16
  Message,
17
+ Model,
16
18
  TextContent,
17
19
  ThinkingContent,
18
20
  Tool,
@@ -40,11 +42,28 @@ const claudeCodeTools = [
40
42
  ] as const;
41
43
  const claudeCodeToolLookup = new Map(claudeCodeTools.map((name) => [name.toLowerCase(), name]));
42
44
 
45
+ // Anthropic's OAuth classifier rejects tool definitions with names outside
46
+ // Claude Code's own tool set with a 400 "Third-party apps now draw from your
47
+ // extra usage" error. Claude Code exposes MCP tools as `mcp__<server>__<tool>`,
48
+ // so unknown tools are namespaced under `mcp__pi__`.
49
+ const CLAUDE_CODE_MCP_TOOL_PREFIX = "mcp__pi__";
50
+
51
+ function sanitizeCustomToolName(name: string): string {
52
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
53
+ }
54
+
43
55
  export function toClaudeCodeToolName(name: string): string {
44
- return claudeCodeToolLookup.get(name.toLowerCase()) ?? name;
56
+ return (
57
+ claudeCodeToolLookup.get(name.toLowerCase()) ??
58
+ `${CLAUDE_CODE_MCP_TOOL_PREFIX}${sanitizeCustomToolName(name)}`
59
+ );
45
60
  }
46
61
 
47
62
  export function fromClaudeCodeToolName(name: string, tools?: Tool[]): string {
63
+ if (name.startsWith(CLAUDE_CODE_MCP_TOOL_PREFIX)) {
64
+ const stripped = name.slice(CLAUDE_CODE_MCP_TOOL_PREFIX.length);
65
+ return tools?.find((tool) => sanitizeCustomToolName(tool.name) === stripped)?.name ?? stripped;
66
+ }
48
67
  const lower = name.toLowerCase();
49
68
  return tools?.find((tool) => tool.name.toLowerCase() === lower)?.name ?? name;
50
69
  }
@@ -52,6 +71,7 @@ export function fromClaudeCodeToolName(name: string, tools?: Tool[]): string {
52
71
  export function convertPiMessagesToAnthropic(
53
72
  messages: Message[],
54
73
  isOAuth: boolean,
74
+ model: Pick<Model<Api>, "provider" | "api" | "id">,
55
75
  ): MessageParam[] {
56
76
  const params: MessageParam[] = [];
57
77
  const toolIdMap = new Map<string, string>();
@@ -131,9 +151,34 @@ export function convertPiMessagesToAnthropic(
131
151
 
132
152
  const blocks: ContentBlockParam[] = [];
133
153
  const emittedToolUseIds: string[] = [];
154
+ const isSameModel =
155
+ message.provider === model.provider &&
156
+ message.api === model.api &&
157
+ message.model === model.id;
134
158
  for (const block of message.content) {
135
159
  if (block.type === "text" && block.text.trim()) {
136
160
  blocks.push({ type: "text", text: sanitizeSurrogates(block.text) });
161
+ } else if (block.type === "thinking") {
162
+ const thinkingSignature = block.thinkingSignature?.trim();
163
+ if (block.redacted) {
164
+ if (isSameModel && thinkingSignature) {
165
+ blocks.push({
166
+ type: "redacted_thinking",
167
+ data: block.thinkingSignature!,
168
+ });
169
+ }
170
+ } else if (isSameModel && thinkingSignature) {
171
+ blocks.push({
172
+ type: "thinking",
173
+ thinking: sanitizeSurrogates(block.thinking),
174
+ signature: block.thinkingSignature!,
175
+ });
176
+ } else if (block.thinking.trim()) {
177
+ blocks.push({
178
+ type: "text",
179
+ text: sanitizeSurrogates(block.thinking),
180
+ });
181
+ }
137
182
  } else if (block.type === "toolCall") {
138
183
  const anthropicId = getAnthropicToolId(block.id);
139
184
  blocks.push({
package/src/stream.ts CHANGED
@@ -144,7 +144,7 @@ export function streamAnthropicOAuth(
144
144
 
145
145
  const params: MessageCreateParamsStreaming = {
146
146
  model: model.id,
147
- messages: convertPiMessagesToAnthropic(context.messages, isOAuth),
147
+ messages: convertPiMessagesToAnthropic(context.messages, isOAuth, model),
148
148
  max_tokens: maxTokens,
149
149
  stream: true,
150
150
  };
@@ -248,6 +248,19 @@ export function streamAnthropicOAuth(
248
248
  contentIndex: output.content.length - 1,
249
249
  partial: output,
250
250
  });
251
+ } else if (event.content_block.type === "redacted_thinking") {
252
+ output.content.push({
253
+ type: "thinking",
254
+ thinking: "[Reasoning redacted]",
255
+ thinkingSignature: event.content_block.data,
256
+ redacted: true,
257
+ index: event.index,
258
+ } as IndexedBlock);
259
+ stream.push({
260
+ type: "thinking_start",
261
+ contentIndex: output.content.length - 1,
262
+ partial: output,
263
+ });
251
264
  } else if (event.content_block.type === "tool_use") {
252
265
  output.content.push({
253
266
  type: "toolCall",
@@ -378,6 +391,14 @@ export function streamAnthropicOAuth(
378
391
  output.usage.cacheWrite =
379
392
  (event.usage as { cache_creation_input_tokens?: number })
380
393
  .cache_creation_input_tokens || 0;
394
+ const thinkingTokens = (
395
+ event.usage as {
396
+ output_tokens_details?: { thinking_tokens?: number };
397
+ }
398
+ ).output_tokens_details?.thinking_tokens;
399
+ if (thinkingTokens != null) {
400
+ output.usage.reasoning = thinkingTokens;
401
+ }
381
402
  output.usage.totalTokens =
382
403
  output.usage.input +
383
404
  output.usage.output +