@bastani/pi-ai 0.9.41 → 0.9.42-alpha.1

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 (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +2 -0
  3. package/dist/api/anthropic-messages.d.ts.map +1 -1
  4. package/dist/api/anthropic-messages.js +37 -3
  5. package/dist/api/anthropic-messages.js.map +1 -1
  6. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  7. package/dist/api/azure-openai-responses.js +12 -4
  8. package/dist/api/azure-openai-responses.js.map +1 -1
  9. package/dist/api/mistral-conversations.d.ts.map +1 -1
  10. package/dist/api/mistral-conversations.js +3 -1
  11. package/dist/api/mistral-conversations.js.map +1 -1
  12. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  13. package/dist/api/openai-codex-responses.js +3 -1
  14. package/dist/api/openai-codex-responses.js.map +1 -1
  15. package/dist/api/openai-completions.d.ts.map +1 -1
  16. package/dist/api/openai-completions.js +12 -4
  17. package/dist/api/openai-completions.js.map +1 -1
  18. package/dist/api/openai-responses.d.ts.map +1 -1
  19. package/dist/api/openai-responses.js +4 -3
  20. package/dist/api/openai-responses.js.map +1 -1
  21. package/dist/auth/types.d.ts +4 -0
  22. package/dist/auth/types.d.ts.map +1 -1
  23. package/dist/auth/types.js.map +1 -1
  24. package/dist/compat/extension-oauth-types.d.ts +4 -0
  25. package/dist/compat/extension-oauth-types.d.ts.map +1 -1
  26. package/dist/compat/extension-oauth-types.js.map +1 -1
  27. package/dist/providers/data/.manifest.json +1 -1
  28. package/dist/providers/data/fireworks.json +1 -1
  29. package/dist/providers/data/huggingface.json +1 -1
  30. package/dist/providers/data/mistral.json +1 -1
  31. package/dist/providers/data/nvidia.json +1 -1
  32. package/dist/providers/data/opencode-go.json +1 -1
  33. package/dist/providers/data/opencode.json +1 -1
  34. package/dist/providers/data/openrouter.json +1 -1
  35. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  36. package/dist/types.d.ts +5 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js.map +1 -1
  39. package/dist/utils/openrouter-cost.d.ts +24 -0
  40. package/dist/utils/openrouter-cost.d.ts.map +1 -0
  41. package/dist/utils/openrouter-cost.js +34 -0
  42. package/dist/utils/openrouter-cost.js.map +1 -0
  43. package/dist/utils/request-admission.d.ts +12 -0
  44. package/dist/utils/request-admission.d.ts.map +1 -0
  45. package/dist/utils/request-admission.js +37 -0
  46. package/dist/utils/request-admission.js.map +1 -0
  47. package/dist/utils/version.d.ts +1 -1
  48. package/dist/utils/version.d.ts.map +1 -1
  49. package/dist/utils/version.js +1 -1
  50. package/dist/utils/version.js.map +1 -1
  51. package/package.json +2 -2
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.42-alpha.1] - 2026-10-23
8
+
9
+ ### Added
10
+
11
+ - Added `openBrowserOnConfirm` and `copyCodeOnConfirm` to `OAuthDeviceCodeInfo` and the `device_code` `AuthEvent`, so a provider can tell clients whether to offer opening the verification URL and copying the code when the user confirms. Both default to `true` ([pi #9282](https://github.com/earendil-works/pi/issues/9282))
12
+
13
+ ### Fixed
14
+
15
+ - OpenRouter requests through Chat Completions and Anthropic Messages now set `usage.cost.total` to the cost OpenRouter reports, including the upstream inference cost for BYOK requests, and mark it `totalFromProvider: true`. When the response has no valid accounting, the catalog estimate stays. The `input`, `output`, `cacheRead` and `cacheWrite` components remain catalog estimates and may not add up to the total ([pi #9980](https://github.com/earendil-works/pi/issues/9980))
16
+ - Claude models that accept mid-conversation system messages no longer reject a request with a 400 (`role 'system' must precede an 'assistant' message`) when a system update is followed by an assistant reply with nothing to send to Anthropic, such as an empty reply or another model's reasoning-only reply ([pi #11091](https://github.com/earendil-works/pi/issues/11091))
17
+ - Cancelling an Anthropic request while it waits for federation authentication now stops that request promptly without cancelling the token exchange shared by other requests ([#3864](https://github.com/bastani-inc/atomic/issues/3864)).
18
+ - Provider request-budget waits no longer consume the Mistral or explicit Codex SSE response-header timeout; the timer now starts once the request is admitted, and cancellation still interrupts the wait ([#3864](https://github.com/bastani-inc/atomic/issues/3864)).
19
+
7
20
  ## [0.9.41] - 2026-10-22
8
21
 
9
22
  ### Breaking Changes
package/README.md CHANGED
@@ -240,6 +240,8 @@ for (const block of response.content) {
240
240
 
241
241
  Snippets in the rest of this README assume a `models` collection set up like this (with the relevant providers registered).
242
242
 
243
+ `usage.cost.total` is the best available total cost. For OpenRouter chat responses, it uses the cost reported by OpenRouter, including both the OpenRouter charge and upstream inference cost for BYOK requests. When provider accounting is unavailable or invalid, it falls back to the model's catalog rates. The `input`, `output`, `cacheRead`, and `cacheWrite` cost components remain catalog estimates and may not sum to a provider-reported total.
244
+
243
245
  ## Providers and Models
244
246
 
245
247
  A **provider** is the runtime unit: it owns its model catalog, its auth (API key resolution, OAuth flows), and its stream behavior. A `Models` collection holds providers and routes every request to the provider that owns the model.
@@ -1 +1 @@
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;AAwKrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAqPhE,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;AA4RD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAyZzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAkD,MAAM,mBAAmB,CAAC;AAiBnF,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AA8KrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAqPhE,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;AAsTD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAiazE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
@@ -1,14 +1,17 @@
1
1
  import Anthropic, {} from "@anthropic-ai/sdk";
2
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";
3
3
  import { calculateCost } from "../models.js";
4
+ import { raceWithAbortSignal } from "../utils/abort.js";
4
5
  import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
5
6
  import { assertSupportedDocumentMimeType } from "../utils/document-input.js";
6
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
7
8
  import { headersToRecord } from "../utils/headers.js";
8
9
  import { getIdentityHeaders } from "../utils/identity.js";
9
10
  import { parseJsonWithRepair, parseStreamingJson, parseToolCallArguments } from "../utils/json-parse.js";
11
+ import { applyOpenRouterReportedCost, isOpenRouterModel, } from "../utils/openrouter-cost.js";
10
12
  import { getProviderEnvValue } from "../utils/provider-env.js";
11
13
  import { retryProviderRequest } from "../utils/provider-retry.js";
14
+ import { getFetchAdmission } from "../utils/request-admission.js";
12
15
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
13
16
  import { createStreamDeadline, withStreamDeadline } from "../utils/stream-deadline.js";
14
17
  import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
@@ -359,6 +362,30 @@ function assertRequestAuth(provider, apiKey, headers) {
359
362
  * also resolve and exchange SDK credentials behind Atomic's auth resolver.
360
363
  */
361
364
  class AtomicAnthropic extends Anthropic {
365
+ admission;
366
+ constructor(options) {
367
+ const admission = getFetchAdmission(options.fetch);
368
+ const tokenAdmission = async (request, next, context) => {
369
+ if (!context.options)
370
+ await admission?.beforeRequest(request.signal ?? undefined);
371
+ return next(request);
372
+ };
373
+ super({
374
+ ...options,
375
+ fetch: admission?.fetch ?? options.fetch,
376
+ middleware: admission ? [tokenAdmission, ...(options.middleware ?? [])] : options.middleware,
377
+ });
378
+ this.admission = admission;
379
+ }
380
+ authHeaders(...args) {
381
+ const result = super.authHeaders(...args);
382
+ const signal = args[0].signal;
383
+ return signal ? raceWithAbortSignal(result, signal) : result;
384
+ }
385
+ async prepareRequest(...args) {
386
+ await super.prepareRequest(...args);
387
+ await this.admission?.beforeRequest(args[0].signal ?? undefined);
388
+ }
362
389
  _shouldResolveDefaultCredentials() {
363
390
  return false;
364
391
  }
@@ -895,6 +922,11 @@ export const stream = (model, context, options) => {
895
922
  inputTransformations = servingTransformations;
896
923
  }
897
924
  addEarlierAttemptCosts(output, model, usageModel, event);
925
+ if (isOpenRouterModel(model) && event.usage) {
926
+ // OpenRouter adds billing fields to streamed message_delta usage at runtime,
927
+ // although its OpenAPI schema and the Anthropic SDK do not type those fields.
928
+ applyOpenRouterReportedCost(output.usage, event.usage);
929
+ }
898
930
  }
899
931
  }
900
932
  if (inputTransformations)
@@ -1075,7 +1107,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1075
1107
  });
1076
1108
  federationClient = { key, fetch, client };
1077
1109
  }
1078
- return { client: federationClient.client.withOptions({ defaultHeaders }), isOAuthToken: false };
1110
+ return { client: federationClient.client.withOptions({ defaultHeaders, fetch }), isOAuthToken: false };
1079
1111
  }
1080
1112
  const client = new AtomicAnthropic({
1081
1113
  apiKey: apiKey ?? null,
@@ -1324,7 +1356,9 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1324
1356
  const params = [];
1325
1357
  const assistantLevels = new Map();
1326
1358
  // Later system messages are held back and emitted directly before the next assistant
1327
- // message (or at the end of the transcript). Anthropic requires `tool_result` blocks to
1359
+ // message that is sent (or at the end of the transcript). Assistant messages that convert to
1360
+ // no blocks are dropped, so holding past them keeps an update from landing before a user
1361
+ // message (pi #11091). Anthropic requires `tool_result` blocks to
1328
1362
  // immediately follow their `tool_use`, so a system message between them is rejected; this
1329
1363
  // also mirrors where the managed-effort system messages are inserted. As a result an
1330
1364
  // update placed before a user message in the transcript lands after it on the wire.
@@ -1417,7 +1451,6 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1417
1451
  }
1418
1452
  }
1419
1453
  else if (msg.role === "assistant") {
1420
- flushPendingSystemMessages();
1421
1454
  const blocks = [];
1422
1455
  for (const block of msg.content) {
1423
1456
  if (block.type === "text") {
@@ -1491,6 +1524,7 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1491
1524
  }
1492
1525
  if (blocks.length === 0)
1493
1526
  continue;
1527
+ flushPendingSystemMessages();
1494
1528
  const messageIndex = params.length;
1495
1529
  params.push({
1496
1530
  role: "assistant",