@bastani/pi-ai 0.9.41-alpha.3 → 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.
- package/CHANGELOG.md +41 -0
- package/README.md +2 -0
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +37 -3
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/dist/api/azure-openai-responses.js +12 -4
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +3 -1
- package/dist/api/mistral-conversations.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +3 -1
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +12 -4
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +4 -3
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/auth/types.d.ts +4 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/compat/extension-oauth-types.d.ts +4 -0
- package/dist/compat/extension-oauth-types.d.ts.map +1 -1
- package/dist/compat/extension-oauth-types.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/openrouter-cost.d.ts +24 -0
- package/dist/utils/openrouter-cost.d.ts.map +1 -0
- package/dist/utils/openrouter-cost.js +34 -0
- package/dist/utils/openrouter-cost.js.map +1 -0
- package/dist/utils/request-admission.d.ts +12 -0
- package/dist/utils/request-admission.d.ts.map +1 -0
- package/dist/utils/request-admission.js +37 -0
- package/dist/utils/request-admission.js.map +1 -0
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.js +1 -1
- package/dist/utils/version.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ 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
|
+
|
|
20
|
+
## [0.9.41] - 2026-10-22
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
|
|
24
|
+
- The default `User-Agent` is now `pi/<version> (<platform>; <runtime>; <arch>)` instead of `pi (<platform> <release>; <arch>)`, and `@bastani/pi-ai/utils/pi-user-agent` (`getPiUserAgent()`) is removed; use `formatUserAgent()`.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added `Usage.cost.totalFromProvider`. It is `true` when `cost.total` is the billed cost reported by the provider and `false` when the total is estimated from the model's catalog rates, as `calculateCost()` and the image generators now record. A missing value, as in sessions recorded before this field existed, means `false`.
|
|
29
|
+
- Added image input to more System One classifiers: `ClassifierContext.images` now also works with Jev and Clef on `cloudflare-workers-ai` and with OpenRouter decision models whose catalog lists image input, next to Jev on `typesafe`. Each service gets its own wire form: TypeSafe `assets` (PNG and JPEG) with the `TypeSafe-Danger-Zone-Images` opt-in header, Clef `images` data URLs (PNG, JPEG, and WebP, at most four), or OpenRouter `image_url` parts in the state. A text-only model's error now names the model: `System One API does not support image input for <model>`.
|
|
30
|
+
- Added `compat` to System One classifier models (`SystemOneCompat`, with `imageInput` and `sendImageOptInHeader`). Built-in catalog entries set it explicitly; models without it detect the form from the provider, `baseUrl`, and model ID.
|
|
31
|
+
- Added the `@cf/cloudflare/clef-omni` and `typesafe/jev-1.14.0` classifier models to `cloudflare-workers-ai`.
|
|
32
|
+
- Added client identity configuration: an `identity` request option and `createModels({ identity })` default choose the `User-Agent` (`"detailed"` with a version, `"short"`, or custom) and provider attribution headers for OpenRouter, NVIDIA NIM, and OpenCode from `attribution: { name, url, categories? }`. `withClientIdentity()` applies an identity to a `Models` or `MutableModels` a library receives from its host. `formatUserAgent()`, `getIdentityHeaders()`, `getOpenCodeSessionHeaders()`, `mergeClientIdentity()`, `PI_ATTRIBUTION`, and `PI_AI_VERSION` are exported. Attribution matches OpenRouter by its exact `openrouter.ai` host, so a custom endpoint that merely contains that name is not attributed.
|
|
33
|
+
- Bedrock, OpenRouter image generation, pi-messages, and classifier requests now send pi's `User-Agent` too.
|
|
34
|
+
- `Models` requests with a `sessionId` to custom providers pointing at OpenCode (`opencode.ai`) now send the `x-opencode-session` header too, like the built-in OpenCode providers.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Fixed Cloudflare Workers AI classifier catalog data: Clef Flash now costs $0.038 per million input tokens with a 24,576-token context window, and Jev costs $0.042 per million input tokens instead of being listed as free.
|
|
39
|
+
- `refreshStoredOAuthCredential()` no longer aborts a token exchange when its caller stops waiting. A token rotation that completes after the 60-second refresh timeout is now saved instead of leaving the replaced refresh token stored. The refresh keeps the credential lock until the exchange settles, for up to `OAUTH_REFRESH_LOCK_HOLD_MS`, so no other refresh presents the token it may already have rotated. When the provider rejects a refresh, it returns a newer credential that another process has saved. A refresh rejected with `invalid_grant` reports only the provider's error code and description, never request URLs, bodies, or tokens, followed by an instruction to run `/login <provider>`. `OAUTH_REFRESH_TIMEOUT_MS` and `OAUTH_REFRESH_LOCK_HOLD_MS` are exported.
|
|
40
|
+
- Anthropic OAuth no longer cuts off a token refresh request after 30 seconds, because the provider may already have rotated the refresh token. The 30-second limit still applies to the login code exchange.
|
|
41
|
+
- Kimi Code OAuth no longer cuts off a token refresh request after 30 seconds. It also no longer sends the same refresh token again after an attempt that may have reached the server (a timeout, an abort, a connection lost mid-request, or an HTTP 429 or 5xx response), because the server may already have rotated that token. A refresh is retried only when the connection was never made. The 30-second limit still applies to device login.
|
|
42
|
+
- A refresh rejected because the stored login is revoked or expired now tells you to run `/login <provider>` for more cases than a literal `invalid_grant`. This includes OpenAI's `refresh_token_expired`, `refresh_token_invalidated` and `refresh_token_reused` codes (also in a nested `error.code`), any HTTP 401 from a token endpoint, Kimi Code's 401 and 403 responses, Meta's expired session, and a revoked GitHub Copilot login. Built-in refreshers throw the new exported `OAuthRefreshRejectedError`, which carries only the HTTP status and the provider's error code and description. Custom refreshers can throw it too, and `refreshStoredOAuthCredential()` maps it to the `/login <provider>` message. The description is dropped when it contains the presented token or a URL. Kimi Code's own refresh errors no longer include the provider's raw error description. `isOAuthRefreshRejectedError`, `isPermanentRefreshRejection`, `parseOAuthErrorBody` and the `OAuthRefreshRejection` and `OAuthErrorDetail` types are exported alongside it.
|
|
43
|
+
- A token rotation that completes after its caller stopped waiting is no longer lost when the process exits. The new `drainPendingOAuthRotations(timeoutMs = OAUTH_SHUTDOWN_DRAIN_MS)` waits, for at most `OAUTH_SHUTDOWN_DRAIN_MS` (3 seconds) by default, until every token exchange that has started settles and its result is saved. It resolves `true` when none is left and `false` when the bound passes first, and never rejects. Hosts call it before a graceful exit. A process killed or crashing mid-exchange still cannot save a rotation it never received; `/login <provider>` signs in again. If a late rotation succeeds but saving it fails, the next refresh failure for that provider adds that an earlier refresh succeeded but its new login could not be saved, with only the save error's code or name (for example `ELOCKED`).
|
|
44
|
+
- Sign in with ChatGPT: a ChatGPT Business workspace usage-limit error (`subscription_sharing_workplace_individual_usage_limit_exceeded`) is no longer classified as a transient error to retry, and its message now tells you to ask your workspace admin to increase the limit.
|
|
45
|
+
- Fixed automatic retry not triggering for `stream disconnected before completion` errors relayed by proxies that wrap the Codex CLI ([#11057](https://github.com/earendil-works/pi/issues/11057))
|
|
46
|
+
- Fixed automatic retry treating digits inside request ids or other numbers (for example `req_011CU503xyz` or `5242880 bytes`) as retryable HTTP status codes. Status codes 429, 500, 502-504 and 520-524 now match only as standalone numbers ([#11057](https://github.com/earendil-works/pi/issues/11057))
|
|
47
|
+
|
|
7
48
|
## [0.9.41-alpha.3] - 2026-10-22
|
|
8
49
|
|
|
9
50
|
### Added
|
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,
|
|
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).
|
|
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",
|