@cortexkit/pi-anthropic-auth 1.9.2 → 1.9.4

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/dist/commands.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildClaudeQuotaSummary, buildFallbackQuotaSummaries, CLAUDE_CACHE_KEEP_COMMAND_NAME, CLAUDE_ROUTING_COMMAND_NAME, executeCache1hCommand, executeCacheKeepCommand, executeDumpCommand, executeFastModeCommand, executeRoutingCommand, getCache1hPersistentMode, getCacheKeepWindow, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, loadAccounts, parseCache1hCommandAction, parseCacheKeepCommandAction, parseDumpCommandAction, parseFastModeCommandAction, parseRoutingCommandAction, setCache1hPersistentEnabled, setCache1hPersistentMode, setCacheKeepPersistentEnabled, setCacheKeepPersistentWindow, setDumpPersistentEnabled, setFastModePersistentEnabled, setRoutingMode, } from '@cortexkit/anthropic-auth-core';
1
+ import { buildClaudeQuotaSummary, buildFallbackQuotaSummaries, CLAUDE_CACHE_KEEP_COMMAND_NAME, CLAUDE_ROUTING_COMMAND_NAME, executeCache1hCommand, executeCacheKeepCommand, executeDumpCommand, executeFastModeCommand, executeRoutingCommand, getCache1hPersistentMode, getCacheKeepWindow, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, loadAccounts, parseCache1hCommandAction, parseCacheKeepCommandAction, parseDumpCommandAction, parseFastModeCommandAction, parseRoutingCommandAction, setCache1hPersistentEnabled, setCache1hPersistentMode, setCacheKeepPersistentEnabled, setCacheKeepPersistentWindow, setDumpEnabled, setDumpPersistentEnabled, setFastModePersistentEnabled, setRoutingMode, } from '@cortexkit/anthropic-auth-core';
2
2
  import { getPiAccountStoragePath } from "./paths.js";
3
3
  function notify(ctx, message, kind = 'info') {
4
4
  ctx.ui.notify(message, kind);
@@ -68,9 +68,14 @@ export function registerCommands(pi) {
68
68
  : enabled;
69
69
  if (action.type === 'enable') {
70
70
  await setDumpPersistentEnabled(true, path);
71
+ setDumpEnabled(true);
71
72
  }
72
73
  else if (action.type === 'disable') {
73
74
  await setDumpPersistentEnabled(false, path);
75
+ setDumpEnabled(false);
76
+ }
77
+ else {
78
+ setDumpEnabled(enabled);
74
79
  }
75
80
  notify(ctx, executeDumpCommand({ argumentsText: args ?? '', enabled: nextEnabled }), action.type === 'usage' ? 'warning' : 'info');
76
81
  },
package/dist/convert.js CHANGED
@@ -1,4 +1,4 @@
1
- import { applyClaudeCodeMetadata, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING, isClaudeFableOrMythos5Model, isFastModeSupportedModel, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeMetadata, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING, isClaudeFableOrMythos5Model, isFastModeSupportedModel, isOpenAIReasoningSignature, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
2
2
  const CLAUDE_CODE_TOOLS = new Map([
3
3
  'Read',
4
4
  'Write',
@@ -41,6 +41,37 @@ export function fromClaudeCodeToolName(name, tools) {
41
41
  const lower = name.toLowerCase();
42
42
  return tools?.find((tool) => tool.name.toLowerCase() === lower)?.name ?? name;
43
43
  }
44
+ function getAssistantToolCallIds(message) {
45
+ const ids = new Set();
46
+ if (message?.role !== 'assistant' || !Array.isArray(message.content)) {
47
+ return ids;
48
+ }
49
+ for (const block of message.content) {
50
+ if (block.type === 'toolCall')
51
+ ids.add(sanitizeToolId(block.id));
52
+ }
53
+ return ids;
54
+ }
55
+ function getImmediateToolResultIds(messages, assistantIndex) {
56
+ const ids = new Set();
57
+ let index = assistantIndex + 1;
58
+ while (messages[index]?.role === 'toolResult') {
59
+ ids.add(sanitizeToolId(messages[index].toolCallId));
60
+ index += 1;
61
+ }
62
+ return ids;
63
+ }
64
+ function assistantToolCallsHaveImmediateResults(messages, assistantIndex) {
65
+ const toolCallIds = getAssistantToolCallIds(messages[assistantIndex]);
66
+ if (toolCallIds.size === 0)
67
+ return true;
68
+ const resultIds = getImmediateToolResultIds(messages, assistantIndex);
69
+ for (const id of toolCallIds) {
70
+ if (!resultIds.has(id))
71
+ return false;
72
+ }
73
+ return true;
74
+ }
44
75
  function convertTextAndImages(content) {
45
76
  if (!content.some((item) => item.type === 'image')) {
46
77
  return content
@@ -91,6 +122,15 @@ function convertMessages(messages) {
91
122
  continue;
92
123
  }
93
124
  if (message.role === 'assistant') {
125
+ const toolCallIds = getAssistantToolCallIds(message);
126
+ if (toolCallIds.size > 0 &&
127
+ !assistantToolCallsHaveImmediateResults(messages, index)) {
128
+ // Anthropic requires every tool_use block to be followed immediately by
129
+ // a matching tool_result message. Interrupted Pi runs can leave an
130
+ // aborted assistant message with a partial toolCall and no result; drop
131
+ // that incomplete assistant turn from the replayed history.
132
+ continue;
133
+ }
94
134
  const blocks = [];
95
135
  for (const block of message.content) {
96
136
  if (block.type === 'text' && block.text.trim()) {
@@ -98,6 +138,12 @@ function convertMessages(messages) {
98
138
  }
99
139
  else if (block.type === 'thinking' && block.thinking.trim()) {
100
140
  const thinking = block;
141
+ if (isOpenAIReasoningSignature(thinking.thinkingSignature)) {
142
+ // OpenAI reasoningEncryptedContent is also opaque encrypted state,
143
+ // but it is not an Anthropic thinking signature. Sending it as
144
+ // `thinking.signature` makes Anthropic reject the request.
145
+ continue;
146
+ }
101
147
  if (thinking.thinkingSignature &&
102
148
  !hasLoneSurrogate(thinking.thinking)) {
103
149
  // Signed thinking blocks must be sent back verbatim — the signature
@@ -132,41 +178,43 @@ function convertMessages(messages) {
132
178
  continue;
133
179
  }
134
180
  if (message.role === 'toolResult') {
135
- const toolResult = message;
136
- const toolResults = [];
137
- let content = convertTextAndImages(toolResult.content);
138
- // Anthropic rejects tool_result with is_error=true but empty content
139
- if (toolResult.isError &&
140
- (!content || (Array.isArray(content) && content.length === 0))) {
141
- content = [{ type: 'text', text: 'Error' }];
181
+ const previous = messages[index - 1];
182
+ if (previous?.role !== 'assistant' ||
183
+ !assistantToolCallsHaveImmediateResults(messages, index - 1)) {
184
+ while (messages[index + 1]?.role === 'toolResult')
185
+ index += 1;
186
+ continue;
142
187
  }
143
- toolResults.push({
144
- type: 'tool_result',
145
- tool_use_id: sanitizeToolId(toolResult.toolCallId),
146
- content: content,
147
- is_error: toolResult.isError,
148
- });
149
- let nextIndex = index + 1;
150
- while (nextIndex < messages.length &&
151
- messages[nextIndex]?.role === 'toolResult') {
152
- const next = messages[nextIndex];
153
- let nextContent = convertTextAndImages(next.content);
188
+ const expectedToolCallIds = getAssistantToolCallIds(previous);
189
+ const appendToolResult = (toolResult) => {
190
+ const toolUseId = sanitizeToolId(toolResult.toolCallId);
191
+ if (!expectedToolCallIds.delete(toolUseId))
192
+ return;
193
+ let content = convertTextAndImages(toolResult.content);
154
194
  // Anthropic rejects tool_result with is_error=true but empty content
155
- if (next.isError &&
156
- (!nextContent ||
157
- (Array.isArray(nextContent) && nextContent.length === 0))) {
158
- nextContent = [{ type: 'text', text: 'Error' }];
195
+ if (toolResult.isError &&
196
+ (!content || (Array.isArray(content) && content.length === 0))) {
197
+ content = [{ type: 'text', text: 'Error' }];
159
198
  }
160
199
  toolResults.push({
161
200
  type: 'tool_result',
162
- tool_use_id: sanitizeToolId(next.toolCallId),
163
- content: nextContent,
164
- is_error: next.isError,
201
+ tool_use_id: toolUseId,
202
+ content: content,
203
+ is_error: toolResult.isError,
165
204
  });
205
+ };
206
+ const toolResult = message;
207
+ const toolResults = [];
208
+ appendToolResult(toolResult);
209
+ let nextIndex = index + 1;
210
+ while (nextIndex < messages.length &&
211
+ messages[nextIndex]?.role === 'toolResult') {
212
+ appendToolResult(messages[nextIndex]);
166
213
  nextIndex += 1;
167
214
  }
168
215
  index = nextIndex - 1;
169
- result.push({ role: 'user', content: toolResults });
216
+ if (toolResults.length)
217
+ result.push({ role: 'user', content: toolResults });
170
218
  }
171
219
  }
172
220
  return result;
package/dist/stream.d.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import { type ApiKeyAccount } from '@cortexkit/anthropic-auth-core';
2
2
  import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions } from '@earendil-works/pi-ai';
3
+ type AnthropicEvent = {
4
+ type?: string;
5
+ index?: number;
6
+ content_block?: Record<string, unknown>;
7
+ delta?: Record<string, unknown>;
8
+ message?: {
9
+ usage?: Record<string, number>;
10
+ };
11
+ usage?: Record<string, number>;
12
+ };
3
13
  export declare function buildExplicitBaseMessagesUrl(baseURL: string): URL;
4
14
  export declare function configureApiRouteHeaders(account: ApiKeyAccount, fastMode: boolean): Headers;
15
+ export declare function parseSse(response: Response): AsyncGenerator<AnthropicEvent>;
5
16
  export declare function primaryResponseAllowsApiFallback(preflight: Response | string): boolean;
6
17
  export declare function streamCortexKitAnthropic(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
18
+ export {};
package/dist/stream.js CHANGED
@@ -1,7 +1,10 @@
1
- import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getRelayConfig, getRoutingMode, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isFastModePersistentlyEnabled, isOAuthAccount, isValidApiBaseURL, loadAccounts, mergeAnthropicBetas, QuotaManager, resolveClaudeCodeIdentity, sendViaRelay, shouldFallbackStatus, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, dumpDirectRequest, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getRelayConfig, getRoutingMode, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isOAuthAccount, isValidApiBaseURL, loadAccounts, mergeAnthropicBetas, QuotaManager, resolveClaudeCodeIdentity, sendViaRelay, setDumpEnabled, shouldFallbackStatus, } from '@cortexkit/anthropic-auth-core';
2
2
  import { calculateCost, createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
3
3
  import { buildAnthropicRequest, fromClaudeCodeToolName } from "./convert.js";
4
4
  import { getPiAccountStoragePath } from "./paths.js";
5
+ function errorText(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
5
8
  const cacheKeepManager = new CacheKeepManager({
6
9
  loadStorage: () => loadAccounts(getPiAccountStoragePath()),
7
10
  prepareHeaders: async (headers, target) => {
@@ -100,20 +103,17 @@ export function configureApiRouteHeaders(account, fastMode) {
100
103
  }
101
104
  return headers;
102
105
  }
103
- async function* parseSse(response) {
106
+ export async function* parseSse(response) {
104
107
  if (!response.body)
105
108
  return;
106
109
  const reader = response.body.getReader();
107
110
  const decoder = new TextDecoder();
108
111
  let buffer = '';
109
- let completed = false;
110
112
  try {
111
113
  while (true) {
112
114
  const { done, value } = await reader.read();
113
- if (done) {
114
- completed = true;
115
+ if (done)
115
116
  break;
116
- }
117
117
  buffer += decoder.decode(value, { stream: true });
118
118
  let boundary = buffer.indexOf('\n\n');
119
119
  while (boundary !== -1) {
@@ -132,13 +132,18 @@ async function* parseSse(response) {
132
132
  }
133
133
  }
134
134
  finally {
135
- if (!completed)
136
- await reader.cancel().catch(() => { });
135
+ // Do not cancel the reader on early abandon. `firstStreamingError()` peeks
136
+ // the first SSE event from a `response.clone()` and then abandons this
137
+ // generator; cancelling the cloned (tee'd) reader tears down the shared
138
+ // underlying body, so the real `parseSse(response)` that streams the reply
139
+ // reads zero events and the assistant message comes back empty. Releasing
140
+ // the lock is enough — the abandoned clone branch is garbage-collected.
137
141
  reader.releaseLock();
138
142
  }
139
143
  }
140
144
  async function sendAnthropicRequest(options) {
141
145
  const storage = await loadAccounts(options.storagePath);
146
+ setDumpEnabled(isDumpPersistentlyEnabled(storage));
142
147
  const identity = options.accessToken
143
148
  ? await resolveClaudeCodeIdentity(options.accessToken, options.model.id)
144
149
  : undefined;
@@ -174,15 +179,42 @@ async function sendAnthropicRequest(options) {
174
179
  storage,
175
180
  cacheMode: isCacheKeepHybridActive(storage) ? 'hybrid' : 'disabled',
176
181
  });
182
+ const directFetch = async () => {
183
+ try {
184
+ const response = await fetch(input, init);
185
+ await dumpDirectRequest({
186
+ affinity: relayAffinity,
187
+ route: options.apiAccount ? `api:${options.apiAccount.id}` : 'oauth',
188
+ status: response.status,
189
+ bodyText,
190
+ url: input.toString(),
191
+ method: init.method,
192
+ headers,
193
+ });
194
+ return response;
195
+ }
196
+ catch (error) {
197
+ await dumpDirectRequest({
198
+ affinity: relayAffinity,
199
+ route: options.apiAccount ? `api:${options.apiAccount.id}` : 'oauth',
200
+ error: errorText(error),
201
+ bodyText,
202
+ url: input.toString(),
203
+ method: init.method,
204
+ headers,
205
+ });
206
+ throw error;
207
+ }
208
+ };
177
209
  if (options.apiAccount)
178
- return fetch(input, init);
210
+ return directFetch();
179
211
  return sendViaRelay({
180
212
  config: getRelayConfig(storage),
181
213
  input,
182
214
  init,
183
215
  headers,
184
216
  body: bodyText,
185
- fallback: () => fetch(input, init),
217
+ fallback: directFetch,
186
218
  affinity: relayAffinity,
187
219
  });
188
220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-anthropic-auth",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  "prepublishOnly": "bun run build"
37
37
  },
38
38
  "dependencies": {
39
- "@cortexkit/anthropic-auth-core": "1.9.2"
39
+ "@cortexkit/anthropic-auth-core": "1.9.4"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@earendil-works/pi-ai": "*",