@averyyy/pi-ai 0.99.1-piclient.1 → 1.0.0-piclient.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.
Files changed (50) hide show
  1. package/README.md +4 -3
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +116 -18
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/constrained-sampling.d.ts +8 -2
  6. package/dist/api/constrained-sampling.d.ts.map +1 -1
  7. package/dist/api/constrained-sampling.js +19 -8
  8. package/dist/api/constrained-sampling.js.map +1 -1
  9. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  10. package/dist/api/openai-responses-shared.js +6 -7
  11. package/dist/api/openai-responses-shared.js.map +1 -1
  12. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  13. package/dist/auth/oauth/anthropic.js +51 -1
  14. package/dist/auth/oauth/anthropic.js.map +1 -1
  15. package/dist/compat.d.ts +1 -0
  16. package/dist/compat.d.ts.map +1 -1
  17. package/dist/compat.js +1 -1
  18. package/dist/compat.js.map +1 -1
  19. package/dist/env-api-keys.d.ts +5 -0
  20. package/dist/env-api-keys.d.ts.map +1 -1
  21. package/dist/env-api-keys.js +5 -0
  22. package/dist/env-api-keys.js.map +1 -1
  23. package/dist/models.d.ts +7 -3
  24. package/dist/models.d.ts.map +1 -1
  25. package/dist/models.js +14 -3
  26. package/dist/models.js.map +1 -1
  27. package/dist/providers/anthropic.d.ts.map +1 -1
  28. package/dist/providers/anthropic.js +24 -2
  29. package/dist/providers/anthropic.js.map +1 -1
  30. package/dist/providers/data/.manifest.json +1 -1
  31. package/dist/providers/data/amazon-bedrock.json +1 -1
  32. package/dist/providers/data/baseten.json +1 -1
  33. package/dist/providers/data/cerebras.json +1 -1
  34. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  35. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  36. package/dist/providers/data/fireworks.json +1 -1
  37. package/dist/providers/data/nvidia.json +1 -1
  38. package/dist/providers/data/opencode.json +1 -1
  39. package/dist/providers/data/openrouter.json +1 -1
  40. package/dist/providers/data/radius.json +1 -1
  41. package/dist/providers/data/together.json +1 -1
  42. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  43. package/dist/utils/oauth-page.js +1 -1
  44. package/dist/utils/oauth-page.js.map +1 -1
  45. package/dist/utils/overflow.d.ts.map +1 -1
  46. package/dist/utils/overflow.js +2 -1
  47. package/dist/utils/overflow.js.map +1 -1
  48. package/dist/utils/provider-retry.js +3 -2
  49. package/dist/utils/provider-retry.js.map +1 -1
  50. package/package.json +6 -2
package/README.md CHANGED
@@ -1653,10 +1653,10 @@ Browser compatibility notes:
1653
1653
 
1654
1654
  ## Bundling and Tree Shaking
1655
1655
 
1656
- For small bundles, import only the providers you need:
1656
+ For small bundles and low-overhead unbundled scripts, import the model runtime and only the providers you need:
1657
1657
 
1658
1658
  ```typescript
1659
- import { createModels } from '@earendil-works/pi-ai';
1659
+ import { createModels } from '@earendil-works/pi-ai/models';
1660
1660
  import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
1661
1661
 
1662
1662
  const models = createModels();
@@ -1665,7 +1665,8 @@ models.setProvider(openaiProvider());
1665
1665
 
1666
1666
  Rules:
1667
1667
 
1668
- - `@earendil-works/pi-ai` is the core entrypoint and does not import built-in catalogs, provider factories, or SDK implementations.
1668
+ - `@earendil-works/pi-ai/models` exports the model runtime (`createModels`, `createProvider`, model helpers, and their types) without TypeBox, built-in catalogs, or SDK implementations. Other types can still use `import type` from the root.
1669
+ - `@earendil-works/pi-ai` is the core entrypoint and does not import built-in catalogs, real provider factories, or SDK implementations, but it eagerly imports TypeBox and schema validation. Unbundled Node scripts do not tree-shake its unused exports; prefer `./models`, `./providers/faux`, and specific `./utils/*` subpaths when those are all you need.
1669
1670
  - `@earendil-works/pi-ai/providers/<provider>` imports that provider's catalog and lazy API wrapper only.
1670
1671
  - `@earendil-works/pi-ai/providers/all` imports every built-in provider factory and all catalogs. Use it only when you want the full built-in set.
1671
1672
  - With code splitting, provider SDKs stay in lazy chunks and load on first request.
@@ -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;AAa1C,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAiJrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA2ChE,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;AAqOD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAwUzE,CAAC;AA2BF,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;AAoBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAqJrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA2ChE,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;AAwRD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA0UzE,CAAC;AA2BF,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 { AssistantMessageEventStream } from "../utils/event-stream.js";
@@ -10,7 +11,7 @@ import { retryProviderRequest } from "../utils/provider-retry.js";
10
11
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
11
12
  import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
12
13
  import { getCurrentTools, getDeclaredTools, getInitialSystemMessage, hasToolRedefinitions, resolveTranscript, } from "../utils/transcript.js";
13
- import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
14
+ import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
14
15
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
15
16
  import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
16
17
  import { transformMessages } from "./transform-messages.js";
@@ -169,16 +170,58 @@ function hasHeader(headers, name) {
169
170
  }
170
171
  return false;
171
172
  }
172
- function assertRequestAuth(provider, apiKey, headers) {
173
- if (apiKey)
174
- return;
175
- if (hasHeader(headers, "authorization") ||
173
+ function hasRequestAuth(apiKey, headers) {
174
+ return (!!apiKey ||
175
+ hasHeader(headers, "authorization") ||
176
176
  hasHeader(headers, "x-api-key") ||
177
- hasHeader(headers, "cf-aig-authorization")) {
178
- return;
177
+ hasHeader(headers, "cf-aig-authorization"));
178
+ }
179
+ function assertRequestAuth(provider, apiKey, headers) {
180
+ if (!hasRequestAuth(apiKey, headers))
181
+ throw new Error(`No API key for provider: ${provider}`);
182
+ }
183
+ /**
184
+ * Anthropic SDK client that never runs the SDK's own credential chain
185
+ * (ANTHROPIC_PROFILE config files, federation env vars). Without this, every
186
+ * client built with `apiKey: null, authToken: null` for header-owned auth would
187
+ * also resolve and exchange SDK credentials behind pi's auth resolver.
188
+ */
189
+ class PiAnthropic extends Anthropic {
190
+ _shouldResolveDefaultCredentials() {
191
+ return false;
179
192
  }
180
- throw new Error(`No API key for provider: ${provider}`);
181
193
  }
194
+ /**
195
+ * Workload identity federation config from the ANTHROPIC_* variables the
196
+ * Anthropic SDK documents; the SDK performs the token exchange and refresh.
197
+ * Only for the anthropic provider, since the exchange is an Anthropic API
198
+ * endpoint, and only when no key or auth header was resolved.
199
+ */
200
+ function getAnthropicFederation(model, apiKey, headers, env) {
201
+ if (model.provider !== "anthropic" || hasRequestAuth(apiKey, headers))
202
+ return undefined;
203
+ const federationRuleId = getProviderEnvValue(ANTHROPIC_FEDERATION_RULE_ID_ENV, env);
204
+ const organizationId = getProviderEnvValue(ANTHROPIC_ORGANIZATION_ID_ENV, env);
205
+ const identityTokenFile = getProviderEnvValue(ANTHROPIC_IDENTITY_TOKEN_FILE_ENV, env);
206
+ if (!federationRuleId || !organizationId || !identityTokenFile)
207
+ return undefined;
208
+ return {
209
+ organization_id: organizationId,
210
+ workspace_id: getProviderEnvValue(ANTHROPIC_WORKSPACE_ID_ENV, env),
211
+ authentication: {
212
+ type: "oidc_federation",
213
+ federation_rule_id: federationRuleId,
214
+ service_account_id: getProviderEnvValue(ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, env),
215
+ identity_token: { source: "file", path: identityTokenFile },
216
+ },
217
+ };
218
+ }
219
+ /**
220
+ * The SDK caches the federated access token per client, but pi creates a client
221
+ * per request. Keep one client for the current federation config and fetch, and
222
+ * clone it per request with `withOptions()`, which shares the token cache.
223
+ */
224
+ let federationClient;
182
225
  const ANTHROPIC_MESSAGE_EVENTS = new Set([
183
226
  "message_start",
184
227
  "message_delta",
@@ -365,7 +408,9 @@ export const stream = (model, context, options) => {
365
408
  }
366
409
  else {
367
410
  const apiKey = options?.apiKey;
368
- assertRequestAuth(model.provider, apiKey, options?.headers);
411
+ const federation = getAnthropicFederation(model, apiKey, options?.headers, options?.env);
412
+ if (!federation)
413
+ assertRequestAuth(model.provider, apiKey, options?.headers);
369
414
  let copilotDynamicHeaders;
370
415
  if (model.provider === "github-copilot") {
371
416
  const hasImages = hasCopilotVisionInput(normalizedContext.messages);
@@ -376,7 +421,7 @@ export const stream = (model, context, options) => {
376
421
  }
377
422
  const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
378
423
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
379
- const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId);
424
+ const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation);
380
425
  client = created.client;
381
426
  isOAuth = created.isOAuthToken;
382
427
  }
@@ -666,7 +711,9 @@ function mapThinkingLevelToEffort(model, level) {
666
711
  }
667
712
  }
668
713
  export const streamSimple = (model, context, options) => {
669
- assertRequestAuth(model.provider, options?.apiKey, options?.headers);
714
+ if (!getAnthropicFederation(model, options?.apiKey, options?.headers, options?.env)) {
715
+ assertRequestAuth(model.provider, options?.apiKey, options?.headers);
716
+ }
670
717
  const base = {
671
718
  ...buildBaseOptions(model, context, options, options?.apiKey),
672
719
  toolChoice: options?.toolChoice,
@@ -701,10 +748,10 @@ export const streamSimple = (model, context, options) => {
701
748
  function isOAuthToken(apiKey) {
702
749
  return apiKey.includes("sk-ant-oat");
703
750
  }
704
- function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId) {
751
+ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId, federation) {
705
752
  // Copilot: Bearer auth.
706
753
  if (model.provider === "github-copilot") {
707
- const client = new Anthropic({
754
+ const client = new PiAnthropic({
708
755
  apiKey: null,
709
756
  authToken: apiKey ?? null,
710
757
  baseURL: model.baseUrl,
@@ -719,7 +766,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
719
766
  }
720
767
  // OAuth: Bearer auth, Claude Code identity headers
721
768
  if (apiKey && isOAuthToken(apiKey)) {
722
- const client = new Anthropic({
769
+ const client = new PiAnthropic({
723
770
  apiKey: null,
724
771
  authToken: apiKey,
725
772
  baseURL: model.baseUrl,
@@ -734,7 +781,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
734
781
  });
735
782
  return { client, isOAuthToken: true };
736
783
  }
737
- // API key or header-owned auth.
784
+ // API key, header-owned auth, or workload identity federation.
738
785
  const compat = getAnthropicCompat(model);
739
786
  const sessionAffinityHeaders = {};
740
787
  if (sessionId && compat.sendSessionAffinityHeaders) {
@@ -745,7 +792,22 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
745
792
  accept: "application/json",
746
793
  "anthropic-dangerous-direct-browser-access": "true",
747
794
  }, sessionAffinityHeaders, model.headers, optionsHeaders);
748
- const client = new Anthropic({
795
+ if (federation) {
796
+ const key = JSON.stringify([model.baseUrl, federation]);
797
+ if (federationClient?.key !== key || federationClient.fetch !== fetch) {
798
+ const client = new PiAnthropic({
799
+ apiKey: null,
800
+ authToken: null,
801
+ config: federation,
802
+ baseURL: model.baseUrl,
803
+ dangerouslyAllowBrowser: true,
804
+ fetch,
805
+ });
806
+ federationClient = { key, fetch, client };
807
+ }
808
+ return { client: federationClient.client.withOptions({ defaultHeaders }), isOAuthToken: false };
809
+ }
810
+ const client = new PiAnthropic({
749
811
  apiKey: apiKey ?? null,
750
812
  authToken: null,
751
813
  baseURL: model.baseUrl,
@@ -1156,11 +1218,47 @@ function insertThinkingLevelMessages(converted, activeEffort) {
1156
1218
  function shouldUseFineGrainedToolStreamingBeta(model, context) {
1157
1219
  return getCurrentTools(context.messages).length > 0 && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
1158
1220
  }
1221
+ // Keywords Anthropic strict tool use rejects with a 400 for the whole request.
1222
+ // https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations
1223
+ const ANTHROPIC_STRICT_UNSUPPORTED_KEYWORDS = new Set([
1224
+ "minimum",
1225
+ "maximum",
1226
+ "exclusiveMinimum",
1227
+ "exclusiveMaximum",
1228
+ "multipleOf",
1229
+ "maxItems",
1230
+ "uniqueItems",
1231
+ "minContains",
1232
+ "maxContains",
1233
+ "minProperties",
1234
+ "maxProperties",
1235
+ ]);
1236
+ const ANTHROPIC_STRICT_STRING_FORMATS = new Set([
1237
+ "date-time",
1238
+ "time",
1239
+ "date",
1240
+ "duration",
1241
+ "email",
1242
+ "hostname",
1243
+ "uri",
1244
+ "ipv4",
1245
+ "ipv6",
1246
+ "uuid",
1247
+ ]);
1248
+ const isAnthropicStrictUnsupportedKeyword = (key, value) => {
1249
+ if (ANTHROPIC_STRICT_UNSUPPORTED_KEYWORDS.has(key))
1250
+ return true;
1251
+ if (key === "minItems")
1252
+ return value !== 0 && value !== 1;
1253
+ if (key === "format")
1254
+ return typeof value !== "string" || !ANTHROPIC_STRICT_STRING_FORMATS.has(value);
1255
+ return false;
1256
+ };
1159
1257
  function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl) {
1160
1258
  if (!tools)
1161
1259
  return [];
1162
1260
  return tools.map((tool, index) => {
1163
- const strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools);
1261
+ const strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
1164
1262
  const parameters = getJsonSchemaToolParameters(tool, strict);
1165
1263
  const schema = parameters;
1166
1264
  const legacyInputSchema = {