@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.
- package/README.md +4 -3
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +116 -18
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/constrained-sampling.d.ts +8 -2
- package/dist/api/constrained-sampling.d.ts.map +1 -1
- package/dist/api/constrained-sampling.js +19 -8
- package/dist/api/constrained-sampling.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +6 -7
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +51 -1
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/compat.d.ts +1 -0
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/compat.js.map +1 -1
- package/dist/env-api-keys.d.ts +5 -0
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +5 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/models.d.ts +7 -3
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +14 -3
- package/dist/models.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +24 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/cerebras.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/utils/oauth-page.js +1 -1
- package/dist/utils/oauth-page.js.map +1 -1
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +2 -1
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/provider-retry.js +3 -2
- package/dist/utils/provider-retry.js.map +1 -1
- 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`
|
|
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,
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = {
|