@cortexkit/pi-anthropic-auth 2.4.10 → 2.4.12

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/native.d.ts CHANGED
@@ -27,6 +27,9 @@ export interface PiVaultPollOptions {
27
27
  clearInterval?: typeof globalThis.clearInterval;
28
28
  }
29
29
  export declare class PiNativeRuntime {
30
+ private readonly pendingReports;
31
+ /** Join started failure reports, never model sends waiting for headers. */
32
+ drainReports(): Promise<void>;
30
33
  private readonly paths;
31
34
  private readonly holderRegistration;
32
35
  private readonly initialization;
package/dist/native.js CHANGED
@@ -71,6 +71,12 @@ export function isNativeMigrationRequired(error) {
71
71
  /** How often Pi asks the vault for account changes, matching OpenCode's default. Model requests no longer rediscover a healthy roster, so this poll is how Pi notices a re-login or a newly added account. */
72
72
  export const PI_VAULT_POLL_INTERVAL_MS = 2000;
73
73
  export class PiNativeRuntime {
74
+ pendingReports = new Set();
75
+ /** Join started failure reports, never model sends waiting for headers. */
76
+ async drainReports() {
77
+ while (this.pendingReports.size)
78
+ await Promise.allSettled([...this.pendingReports]);
79
+ }
74
80
  paths;
75
81
  holderRegistration;
76
82
  initialization;
@@ -328,8 +334,18 @@ export class PiNativeRuntime {
328
334
  };
329
335
  }
330
336
  async report(served, status, source) {
331
- if (served.source === 'vault' && status === 401)
332
- await (await this.service()).vault.reportFailure(served.receipt, status, source);
337
+ if (served.source === 'vault' && status === 401) {
338
+ // An already-served receipt can be reported while shutdown drains it.
339
+ // Do not re-enter service admission after close has blocked new sends.
340
+ const pending = this.initialization.then((runtime) => runtime.vault.reportFailure(served.receipt, status, source));
341
+ this.pendingReports.add(pending);
342
+ try {
343
+ await pending;
344
+ }
345
+ finally {
346
+ this.pendingReports.delete(pending);
347
+ }
348
+ }
333
349
  }
334
350
  async markUsed(served, servingRuntime) {
335
351
  const runtime = servingRuntime ?? (await this.service());
@@ -350,6 +366,7 @@ export class PiNativeRuntime {
350
366
  await this.initialization.then(async (runtime) => {
351
367
  await runtime.drainQuotaPublications();
352
368
  await runtime.drainRoutingPublications();
369
+ await this.drainReports();
353
370
  await runtime.close();
354
371
  }, () => { });
355
372
  })();
package/dist/stream.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type ApiKeyAccount, CacheKeepManager, type MidConversationEffortTransition, type QuotaManagerOptions } from '@cortexkit/anthropic-auth-core';
2
- import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions } from '@earendil-works/pi-ai';
1
+ import { type ApiKeyAccount, CacheKeepManager, type MidConversationEffortTransition, type NativeAccountView, type QuotaManagerOptions } from '@cortexkit/anthropic-auth-core';
2
+ import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions, type Tool } from '@earendil-works/pi-ai';
3
3
  /** Set or clear the test replacement for the wait between quota usage polls. */
4
4
  export declare function __setPiQuotaCallPauseForTest(pause: QuotaManagerOptions['pauseBetweenQuotaCalls']): void;
5
5
  export declare function clearPiStickyRoutingSession(storagePath: string, sessionId: string): Promise<void>;
@@ -20,6 +20,22 @@ type AnthropicEvent = {
20
20
  export declare function buildExplicitBaseMessagesUrl(baseURL: string): URL;
21
21
  export declare function configureApiRouteHeaders(account: ApiKeyAccount, fastMode: boolean): Headers;
22
22
  export declare function parseSse(response: Response): AsyncGenerator<AnthropicEvent>;
23
+ declare function sendAnthropicRequest(options: {
24
+ model: Model<Api>;
25
+ context: Context;
26
+ streamOptions?: SimpleStreamOptions;
27
+ apiAccount?: NativeAccountView;
28
+ storagePath: string;
29
+ oauthAccountId?: string;
30
+ route?: string;
31
+ effortTransitions?: readonly MidConversationEffortTransition[];
32
+ onResolvedTools?: (tools: Tool[]) => void;
33
+ }, deps?: {
34
+ fetch?: typeof fetch;
35
+ cacheKeep?: CacheKeepManager;
36
+ }): Promise<Response>;
37
+ /** Exercise one native send with isolated transport and scheduling in tests. */
38
+ export declare const __sendPiAnthropicRequestForTest: typeof sendAnthropicRequest;
23
39
  export declare function primaryResponseAllowsApiFallback(preflight: Response | string): boolean;
24
40
  /**
25
41
  * Classify a successful stream by its first event without a tee: the bytes
package/dist/stream.js CHANGED
@@ -1,4 +1,4 @@
1
- import { applyClaudeCodeHeaders, applyCustomHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, createNativeVaultNoRouteResponse, createProviderErrorResponseScope, createSseFramer, createStickyNoRouteResponse, decideStickyQuotaFailure, decodeSseFrame, dumpDirectRequest, FAST_MODE_BETA, getCache1hPersistentMode, getClaudeCodeIdentityForVerifiedAccount, getDefaultCacheKeepRegistryDirectory, getFallbackReauthLabels, getQuotaCheckIntervalMs, getRequestGzipLatch, getRequestGzipLatchPath, getRoutingMode, getScopedQuotaWindowForModel, getThinkingPrefixMismatchBehavior, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isNativeTransientAccountError, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, logger, mergeAnthropicBetas, NativeAccountRefusal, nativeAccountCanServe, parseSseFrame, QuotaManager, quotaBackoffActive, quotaExhaustionFromHeaders, quotaSnapshotHasStandardWindows, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, sendWithRequestGzip, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeHeaders, applyCustomHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, createNativeVaultNoRouteResponse, createProviderErrorResponseScope, createSseFramer, createStickyNoRouteResponse, decideStickyQuotaFailure, decodeSseFrame, dumpDirectRequest, FAST_MODE_BETA, getCache1hPersistentMode, getClaudeCodeIdentityForVerifiedAccount, getDefaultCacheKeepRegistryDirectory, getFallbackReauthLabels, getQuotaCheckIntervalMs, getRequestGzipLatch, getRequestGzipLatchPath, getRoutingMode, getScopedQuotaWindowForModel, getThinkingPrefixMismatchBehavior, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isNativeTransientAccountError, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, logger, mergeAnthropicBetas, NativeAccountRefusal, nativeAccountCanServe, parseSseFrame, QuotaManager, quotaBackoffActive, quotaExhaustionFromHeaders, quotaSnapshotHasStandardWindows, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, runWithRequestSignal, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, sendWithRequestGzip, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, } 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 { getPiNativeRuntime, piAttemptAccessToken, } from './native.js';
@@ -335,7 +335,9 @@ export async function* parseSse(response) {
335
335
  reader.releaseLock();
336
336
  }
337
337
  }
338
- async function sendAnthropicRequest(options) {
338
+ async function sendAnthropicRequest(options, deps = {}) {
339
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
340
+ const keep = deps.cacheKeep ?? cacheKeepManager;
339
341
  const native = getPiNativeRuntime(options.storagePath);
340
342
  // Capture the serving service while authorization is open. Shutdown rejects
341
343
  // new serving work immediately but drains writes for already-served attempts.
@@ -385,8 +387,10 @@ async function sendAnthropicRequest(options) {
385
387
  signal: options.streamOptions?.signal,
386
388
  ...(!options.apiAccount && { redirect: 'error' }),
387
389
  };
388
- if (!options.apiAccount)
389
- await cacheKeepManager.track({
390
+ const trackAcceptedRequest = () => {
391
+ if (options.apiAccount || init.signal?.aborted)
392
+ return;
393
+ keep.track({
390
394
  sessionId: relayAffinity,
391
395
  url: input.toString(),
392
396
  headers,
@@ -397,6 +401,7 @@ async function sendAnthropicRequest(options) {
397
401
  oauthAccountIdentity: verifiedUuid,
398
402
  accountStoragePath: options.storagePath,
399
403
  });
404
+ };
400
405
  const reported = new Set();
401
406
  let retried401 = false;
402
407
  let lastTransport;
@@ -455,13 +460,13 @@ async function sendAnthropicRequest(options) {
455
460
  // OAuth bodies go gzipped as Claude Code sends them; `bodyText` stays
456
461
  // the signed identity text for dumps and CacheKeep.
457
462
  if (options.apiAccount || typeof init.body !== 'string')
458
- return scope.redactResponse(await fetch(input, init));
463
+ return scope.redactResponse(await fetchImpl(input, init));
459
464
  return scope.redactResponse(await sendWithRequestGzip({
460
465
  url: input,
461
466
  body: init.body,
462
467
  headers,
463
468
  latch: getRequestGzipLatch(getRequestGzipLatchPath(options.storagePath)),
464
- send: (wireBody, wireHeaders) => fetch(input, { ...init, body: wireBody, headers: wireHeaders }),
469
+ send: (wireBody, wireHeaders) => fetchImpl(input, { ...init, body: wireBody, headers: wireHeaders }),
465
470
  }));
466
471
  };
467
472
  let response;
@@ -537,8 +542,10 @@ async function sendAnthropicRequest(options) {
537
542
  const subject = apiSubject;
538
543
  native.usagePublications.publish(() => servingRuntime.publishApi(subject, { lastUsed: Date.now() }));
539
544
  }
540
- if (attempt && response.ok)
545
+ if (attempt && response.ok) {
546
+ trackAcceptedRequest();
541
547
  markServed(attempt);
548
+ }
542
549
  return response;
543
550
  };
544
551
  if (options.apiAccount)
@@ -601,10 +608,14 @@ async function sendAnthropicRequest(options) {
601
608
  // If the request was cancelled since, end it as cancelled here.
602
609
  if (relay401Attempt && response.status === 401)
603
610
  await endIfCancelled(response, init.signal);
604
- if (response.ok && lastTransport === 'relay' && relayAttempt)
611
+ if (response.ok && lastTransport === 'relay' && relayAttempt) {
612
+ trackAcceptedRequest();
605
613
  markServed(relayAttempt);
614
+ }
606
615
  return response;
607
616
  }
617
+ /** Exercise one native send with isolated transport and scheduling in tests. */
618
+ export const __sendPiAnthropicRequestForTest = sendAnthropicRequest;
608
619
  /**
609
620
  * If the request's signal is aborted, cancels the response body and throws the
610
621
  * signal's abort error; otherwise does nothing. Call it before a rejected
@@ -1242,7 +1253,7 @@ export function streamCortexKitAnthropic(model, context, options, effortTransiti
1242
1253
  buildPiOAuthMessagesUrl(model.baseUrl);
1243
1254
  const storagePath = getPiAccountStoragePath();
1244
1255
  let hostTools = [];
1245
- const response = await executeWithFallback({
1256
+ const response = await runWithRequestSignal(options?.signal, () => executeWithFallback({
1246
1257
  model,
1247
1258
  context,
1248
1259
  streamOptions: options,
@@ -1251,7 +1262,7 @@ export function streamCortexKitAnthropic(model, context, options, effortTransiti
1251
1262
  onResolvedTools: (tools) => {
1252
1263
  hostTools = tools;
1253
1264
  },
1254
- });
1265
+ }));
1255
1266
  if (!response.ok) {
1256
1267
  throw new Error(`Anthropic request failed: HTTP ${response.status} ${await response.text()}`);
1257
1268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-anthropic-auth",
3
- "version": "2.4.10",
3
+ "version": "2.4.12",
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": "2.4.10"
39
+ "@cortexkit/anthropic-auth-core": "2.4.12"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@earendil-works/pi-ai": ">=0.86.1 <2",