@bastani/pi-ai 0.9.25-alpha.2 → 0.9.25-alpha.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/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ This package is a Bastani fork of `@earendil-works/pi-ai`. Upstream history at t
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.25-alpha.3] - 2026-09-30
8
+
9
+ ### Added
10
+
11
+ - Added Anthropic workload identity federation from the Anthropic SDK environment variables `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, and `ANTHROPIC_IDENTITY_TOKEN_FILE` (plus optional `ANTHROPIC_SERVICE_ACCOUNT_ID` and `ANTHROPIC_WORKSPACE_ID`). API keys and `ANTHROPIC_AUTH_TOKEN` take precedence ([#10177](https://github.com/earendil-works/pi/issues/10177), [#10242](https://github.com/earendil-works/pi/pull/10242) by [@philfreo](https://github.com/philfreo)).
12
+ - Anthropic subscription login now offers a copy-code method for remote or headless machines, alongside the default browser callback method ([#10194](https://github.com/earendil-works/pi/pull/10194) by [@lucasmeijer](https://github.com/lucasmeijer)).
13
+
14
+ ### Fixed
15
+
16
+ - Fixed context overflow detection for Z.AI CN endpoint `Prompt exceeds max length` errors ([#10208](https://github.com/earendil-works/pi/issues/10208)).
17
+ - Anthropic `strict: "prefer"` tools now fall back to non-strict mode for unsupported string formats and additional array/object constraints instead of rejecting the request. Supported string length and pattern constraints remain strict ([#9953](https://github.com/earendil-works/pi/issues/9953)).
18
+ - Provider retries now use exponential backoff when `Retry-After` or `retry-after-ms` cannot be parsed as a finite delay ([#9571](https://github.com/earendil-works/pi/issues/9571)).
19
+
7
20
  ## [0.9.25-alpha.1] - 2026-09-29
8
21
 
9
22
  ### Added
package/NOTICE.md CHANGED
@@ -6,7 +6,7 @@ monorepo at `packages/ai` and publishes at the same version as `@bastani/atomic`
6
6
 
7
7
  - Upstream package: [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai)
8
8
  - Original fork point: `v0.84.2` (`914cf1472e715297caa30db4b9535d534a9eb718`)
9
- - Applicable Pi AI fixes and generated catalogs synced through audited upstream `main`: `earendil-works/pi@1b347794e2a630e4359f2584f4eea388145d0ddf`. Atomic retains its adaptations and does not enable upstream virtual models.
9
+ - Applicable Pi AI fixes synced through audited upstream `main`: `earendil-works/pi@7a11fe1c723b942fa093edb8b00f051ddb9b6627`, with remaining registry Pi packages at `0.99.2`. Atomic retains its adaptations and does not enable upstream virtual models.
10
10
  - Catalog JSON under `src/providers/data/` is generated at build time from models.dev, matching upstream. It is not committed.
11
11
 
12
12
  Original work is Copyright (c) 2025 Mario Zechner and is licensed under the MIT License.
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAU1C,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAsKrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAoPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AA6OD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA2XzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA8ClF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAiBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAsKrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAoPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAgSD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA6XzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
@@ -1,4 +1,5 @@
1
- import Anthropic from "@anthropic-ai/sdk";
1
+ import Anthropic, {} from "@anthropic-ai/sdk";
2
+ import { ANTHROPIC_FEDERATION_RULE_ID_ENV, ANTHROPIC_IDENTITY_TOKEN_FILE_ENV, ANTHROPIC_ORGANIZATION_ID_ENV, ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, ANTHROPIC_WORKSPACE_ID_ENV, } from "../env-api-keys.js";
2
3
  import { calculateCost } from "../models.js";
3
4
  import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
4
5
  import { assertSupportedDocumentMimeType } from "../utils/document-input.js";
@@ -340,16 +341,58 @@ function hasHeader(headers, name) {
340
341
  }
341
342
  return false;
342
343
  }
343
- function assertRequestAuth(provider, apiKey, headers) {
344
- if (apiKey)
345
- return;
346
- if (hasHeader(headers, "authorization") ||
344
+ function hasRequestAuth(apiKey, headers) {
345
+ return (!!apiKey ||
346
+ hasHeader(headers, "authorization") ||
347
347
  hasHeader(headers, "x-api-key") ||
348
- hasHeader(headers, "cf-aig-authorization")) {
349
- return;
348
+ hasHeader(headers, "cf-aig-authorization"));
349
+ }
350
+ function assertRequestAuth(provider, apiKey, headers) {
351
+ if (!hasRequestAuth(apiKey, headers))
352
+ throw new Error(`No API key for provider: ${provider}`);
353
+ }
354
+ /**
355
+ * Anthropic SDK client that never runs the SDK's own credential chain
356
+ * (ANTHROPIC_PROFILE config files, federation env vars). Without this, every
357
+ * client built with `apiKey: null, authToken: null` for header-owned auth would
358
+ * also resolve and exchange SDK credentials behind Atomic's auth resolver.
359
+ */
360
+ class AtomicAnthropic extends Anthropic {
361
+ _shouldResolveDefaultCredentials() {
362
+ return false;
350
363
  }
351
- throw new Error(`No API key for provider: ${provider}`);
352
364
  }
365
+ /**
366
+ * Workload identity federation config from the ANTHROPIC_* variables the
367
+ * Anthropic SDK documents; the SDK performs the token exchange and refresh.
368
+ * Only for the anthropic provider, since the exchange is an Anthropic API
369
+ * endpoint, and only when no key or auth header was resolved.
370
+ */
371
+ function getAnthropicFederation(model, apiKey, headers, env) {
372
+ if (model.provider !== "anthropic" || hasRequestAuth(apiKey, headers))
373
+ return undefined;
374
+ const federationRuleId = getProviderEnvValue(ANTHROPIC_FEDERATION_RULE_ID_ENV, env);
375
+ const organizationId = getProviderEnvValue(ANTHROPIC_ORGANIZATION_ID_ENV, env);
376
+ const identityTokenFile = getProviderEnvValue(ANTHROPIC_IDENTITY_TOKEN_FILE_ENV, env);
377
+ if (!federationRuleId || !organizationId || !identityTokenFile)
378
+ return undefined;
379
+ return {
380
+ organization_id: organizationId,
381
+ workspace_id: getProviderEnvValue(ANTHROPIC_WORKSPACE_ID_ENV, env),
382
+ authentication: {
383
+ type: "oidc_federation",
384
+ federation_rule_id: federationRuleId,
385
+ service_account_id: getProviderEnvValue(ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, env),
386
+ identity_token: { source: "file", path: identityTokenFile },
387
+ },
388
+ };
389
+ }
390
+ /**
391
+ * The SDK caches the federated access token per client, but a client is created
392
+ * per request. Keep one client for the current federation config and fetch, and
393
+ * clone it per request with `withOptions()`, which shares the token cache.
394
+ */
395
+ let federationClient;
353
396
  const ANTHROPIC_MESSAGE_EVENTS = new Set([
354
397
  "message_start",
355
398
  "message_delta",
@@ -547,7 +590,9 @@ export const stream = (model, context, options) => {
547
590
  }
548
591
  else {
549
592
  const apiKey = options?.apiKey;
550
- assertRequestAuth(model.provider, apiKey, options?.headers);
593
+ const federation = getAnthropicFederation(model, apiKey, options?.headers, options?.env);
594
+ if (!federation)
595
+ assertRequestAuth(model.provider, apiKey, options?.headers);
551
596
  let copilotDynamicHeaders;
552
597
  if (model.provider === "github-copilot") {
553
598
  const hasImages = hasCopilotVisionInput(normalizedContext.messages);
@@ -562,7 +607,7 @@ export const stream = (model, context, options) => {
562
607
  const created = createClient(model, apiKey, model.reasoning && options?.thinkingEnabled === true && (options.interleavedThinking ?? true), shouldUseFineGrainedToolStreamingBeta(model, context), shouldUseServerSideFallbackBeta(model), options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, getAnthropicCompat(model).supportsMidConvoToolChanges &&
563
608
  getAnthropicCompat(model).supportsMidConvoSystemMessages &&
564
609
  (getInitialSystemMessage(normalizedContext.messages)?.toolsAdded?.length ?? 0) > 0 &&
565
- !hasToolRedefinitions(normalizedContext.messages));
610
+ !hasToolRedefinitions(normalizedContext.messages), federation);
566
611
  client = created.client;
567
612
  isOAuth = created.isOAuthToken;
568
613
  }
@@ -881,7 +926,9 @@ function mapThinkingLevelToEffort(model, level) {
881
926
  }
882
927
  }
883
928
  export const streamSimple = (model, context, options) => {
884
- assertRequestAuth(model.provider, options?.apiKey, options?.headers);
929
+ if (!getAnthropicFederation(model, options?.apiKey, options?.headers, options?.env)) {
930
+ assertRequestAuth(model.provider, options?.apiKey, options?.headers);
931
+ }
885
932
  const base = {
886
933
  ...buildBaseOptions(model, context, options, options?.apiKey),
887
934
  toolChoice: options?.toolChoice,
@@ -916,7 +963,7 @@ export const streamSimple = (model, context, options) => {
916
963
  function isOAuthToken(apiKey) {
917
964
  return apiKey.includes("sk-ant-oat");
918
965
  }
919
- function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId, useNativeToolChanges = false) {
966
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId, useNativeToolChanges = false, federation) {
920
967
  // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
921
968
  const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
922
969
  const betaFeatures = [];
@@ -942,7 +989,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
942
989
  const uniqueBetaFeatures = [...new Set(betaFeatures)];
943
990
  // Copilot: Bearer auth, selective betas.
944
991
  if (model.provider === "github-copilot") {
945
- const client = new Anthropic({
992
+ const client = new AtomicAnthropic({
946
993
  apiKey: null,
947
994
  authToken: apiKey ?? null,
948
995
  baseURL: model.baseUrl,
@@ -958,7 +1005,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
958
1005
  }
959
1006
  // OAuth: Bearer auth, Claude Code identity headers
960
1007
  if (apiKey && isOAuthToken(apiKey)) {
961
- const client = new Anthropic({
1008
+ const client = new AtomicAnthropic({
962
1009
  apiKey: null,
963
1010
  authToken: apiKey,
964
1011
  baseURL: model.baseUrl,
@@ -974,7 +1021,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
974
1021
  });
975
1022
  return { client, isOAuthToken: true };
976
1023
  }
977
- // API key or header-owned auth.
1024
+ // API key, header-owned auth, or workload identity federation.
978
1025
  const compat = getAnthropicCompat(model);
979
1026
  const sessionAffinityHeaders = {};
980
1027
  if (sessionId && compat.sendSessionAffinityHeaders) {
@@ -986,7 +1033,22 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
986
1033
  "anthropic-dangerous-direct-browser-access": "true",
987
1034
  ...(uniqueBetaFeatures.length > 0 ? { "anthropic-beta": uniqueBetaFeatures.join(",") } : {}),
988
1035
  }, sessionAffinityHeaders, model.headers, optionsHeaders);
989
- const client = new Anthropic({
1036
+ if (federation) {
1037
+ const key = JSON.stringify([model.baseUrl, federation]);
1038
+ if (federationClient?.key !== key || federationClient.fetch !== fetch) {
1039
+ const client = new AtomicAnthropic({
1040
+ apiKey: null,
1041
+ authToken: null,
1042
+ config: federation,
1043
+ baseURL: model.baseUrl,
1044
+ dangerouslyAllowBrowser: true,
1045
+ fetch,
1046
+ });
1047
+ federationClient = { key, fetch, client };
1048
+ }
1049
+ return { client: federationClient.client.withOptions({ defaultHeaders }), isOAuthToken: false };
1050
+ }
1051
+ const client = new AtomicAnthropic({
990
1052
  apiKey: apiKey ?? null,
991
1053
  authToken: null,
992
1054
  baseURL: model.baseUrl,