@fractaal/pi-ai 0.86.8 → 1.4.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/README.md +105 -45
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +180 -141
- 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 +8 -10
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +130 -96
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/classifier-shared.d.ts +21 -0
- package/dist/api/classifier-shared.d.ts.map +1 -0
- package/dist/api/classifier-shared.js +99 -0
- package/dist/api/classifier-shared.js.map +1 -0
- package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
- package/dist/api/cloudflare-workers-ai-system-one.js +10 -1
- package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
- package/dist/api/constrained-sampling.d.ts +11 -1
- package/dist/api/constrained-sampling.d.ts.map +1 -1
- package/dist/api/constrained-sampling.js +33 -18
- package/dist/api/constrained-sampling.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +4 -10
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +2 -1
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +60 -3
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +9 -15
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/lazy.d.ts +7 -2
- package/dist/api/lazy.d.ts.map +1 -1
- package/dist/api/lazy.js +28 -8
- package/dist/api/lazy.js.map +1 -1
- package/dist/api/llama-cpp-classify.d.ts.map +1 -1
- package/dist/api/llama-cpp-classify.js +4 -1
- package/dist/api/llama-cpp-classify.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +50 -60
- 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 +23 -14
- 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 +83 -87
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-decisions.d.ts +4 -0
- package/dist/api/openai-decisions.d.ts.map +1 -0
- package/dist/api/openai-decisions.js +162 -0
- package/dist/api/openai-decisions.js.map +1 -0
- package/dist/api/openai-decisions.lazy.d.ts +3 -0
- package/dist/api/openai-decisions.lazy.d.ts.map +1 -0
- package/dist/api/openai-decisions.lazy.js +4 -0
- package/dist/api/openai-decisions.lazy.js.map +1 -0
- package/dist/api/openai-responses-shared.d.ts +26 -0
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +132 -66
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +38 -19
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/openrouter-images.d.ts.map +1 -1
- package/dist/api/openrouter-images.js +5 -3
- package/dist/api/openrouter-images.js.map +1 -1
- package/dist/api/pi-messages.d.ts +2 -0
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +18 -12
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +2 -1
- package/dist/api/simple-options.js.map +1 -1
- package/dist/api/system-one-shared.d.ts +29 -3
- package/dist/api/system-one-shared.d.ts.map +1 -1
- package/dist/api/system-one-shared.js +54 -86
- package/dist/api/system-one-shared.js.map +1 -1
- package/dist/api/transform-messages.d.ts +16 -1
- package/dist/api/transform-messages.d.ts.map +1 -1
- package/dist/api/transform-messages.js +28 -15
- package/dist/api/transform-messages.js.map +1 -1
- package/dist/api/typesafe-system-one.d.ts.map +1 -1
- package/dist/api/typesafe-system-one.js +12 -1
- package/dist/api/typesafe-system-one.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +14 -6
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/auth/oauth/openai-chatgpt.js +1 -1
- package/dist/auth/oauth/openai-chatgpt.js.map +1 -1
- package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
- package/dist/auth/oauth/openai-codex.js +4 -4
- package/dist/auth/oauth/openai-codex.js.map +1 -1
- package/dist/auth/types.d.ts +9 -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/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +9 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +23 -7
- package/dist/models.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +509 -0
- package/dist/providers/compat-schema.d.ts.map +1 -0
- package/dist/providers/compat-schema.js +396 -0
- package/dist/providers/compat-schema.js.map +1 -0
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/azure.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/github-copilot.json +1 -1
- package/dist/providers/data/google-vertex.json +1 -1
- package/dist/providers/data/google.json +1 -1
- package/dist/providers/data/groq.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/kimi-coding.json +1 -1
- package/dist/providers/data/meta.json +1 -1
- package/dist/providers/data/minimax-cn.json +1 -1
- package/dist/providers/data/minimax.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/moonshotai-cn.json +1 -1
- package/dist/providers/data/moonshotai.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/openai-codex.json +1 -1
- package/dist/providers/data/openai.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/qwen-token-plan-cn.json +1 -1
- package/dist/providers/data/qwen-token-plan.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/typesafe.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/providers/data/xiaomi.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +31 -9
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/kimi-coding.d.ts +1 -1
- package/dist/providers/kimi-coding.js +3 -3
- package/dist/providers/kimi-coding.js.map +1 -1
- package/dist/providers/model-schema.d.ts +91 -0
- package/dist/providers/model-schema.d.ts.map +1 -0
- package/dist/providers/model-schema.js +57 -0
- package/dist/providers/model-schema.js.map +1 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +12 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/opencode-go.d.ts +3 -1
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +2 -0
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/openrouter-api.d.ts +40 -0
- package/dist/providers/openrouter-api.d.ts.map +1 -0
- package/dist/providers/openrouter-api.js +41 -0
- package/dist/providers/openrouter-api.js.map +1 -0
- package/dist/providers/openrouter.d.ts +11 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +52 -1
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/radius-config.d.ts.map +1 -1
- package/dist/providers/radius-config.js +2 -1
- package/dist/providers/radius-config.js.map +1 -1
- package/dist/providers/radius.d.ts.map +1 -1
- package/dist/providers/radius.js +4 -12
- package/dist/providers/radius.js.map +1 -1
- package/dist/telemetry.d.ts +288 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +192 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +44 -307
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +2 -1
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/estimate.js +1 -1
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/event-stream.d.ts +10 -1
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +25 -0
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/identity.d.ts +65 -0
- package/dist/utils/identity.d.ts.map +1 -0
- package/dist/utils/identity.js +141 -0
- package/dist/utils/identity.js.map +1 -0
- package/dist/utils/json-parse.d.ts +10 -0
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +26 -1
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/model-operations.d.ts +3 -1
- package/dist/utils/model-operations.d.ts.map +1 -1
- package/dist/utils/model-operations.js +6 -0
- package/dist/utils/model-operations.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/overflow.d.ts +1 -0
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +6 -1
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +2 -0
- package/dist/utils/provider-retry.d.ts.map +1 -1
- package/dist/utils/provider-retry.js +2 -0
- package/dist/utils/provider-retry.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +26 -10
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/streaming-json.d.ts +84 -0
- package/dist/utils/streaming-json.d.ts.map +1 -0
- package/dist/utils/streaming-json.js +513 -0
- package/dist/utils/streaming-json.js.map +1 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.d.ts.map +1 -1
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/transcript.d.ts +22 -8
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +29 -18
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +3 -0
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +3 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +3 -3
- package/dist/utils/assistant-message-frame.d.ts +0 -77
- package/dist/utils/assistant-message-frame.d.ts.map +0 -1
- package/dist/utils/assistant-message-frame.js +0 -425
- package/dist/utils/assistant-message-frame.js.map +0 -1
- package/dist/utils/pi-user-agent.d.ts +0 -2
- package/dist/utils/pi-user-agent.d.ts.map +0 -1
- package/dist/utils/pi-user-agent.js +0 -12
- package/dist/utils/pi-user-agent.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and
|
|
|
18
18
|
- [Auth](#auth)
|
|
19
19
|
- [How Auth Resolves](#how-auth-resolves)
|
|
20
20
|
- [Transforming Request Headers](#transforming-request-headers)
|
|
21
|
+
- [Client Identity](#client-identity)
|
|
21
22
|
- [Credential Store](#credential-store)
|
|
22
23
|
- [Environment Variables](#environment-variables)
|
|
23
24
|
- [Tools](#tools)
|
|
@@ -26,7 +27,6 @@ Unified LLM API with provider collections, automatic auth resolution, token and
|
|
|
26
27
|
- [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
|
|
27
28
|
- [Validating Tool Arguments](#validating-tool-arguments)
|
|
28
29
|
- [Complete Event Reference](#complete-event-reference)
|
|
29
|
-
- [Compact Assistant Message Frames](#compact-assistant-message-frames)
|
|
30
30
|
- [Image Input](#image-input)
|
|
31
31
|
- [Image Generation](#image-generation)
|
|
32
32
|
- [Classification](#classification)
|
|
@@ -35,6 +35,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and
|
|
|
35
35
|
- [Provider-Specific Options](#provider-specific-options-streamcomplete)
|
|
36
36
|
- [Streaming Thinking Content](#streaming-thinking-content)
|
|
37
37
|
- [Stop Reasons](#stop-reasons)
|
|
38
|
+
- [Telemetry](#telemetry)
|
|
38
39
|
- [Error Handling](#error-handling)
|
|
39
40
|
- [Aborting Requests](#aborting-requests)
|
|
40
41
|
- [Continuing After Abort](#continuing-after-abort)
|
|
@@ -60,7 +61,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and
|
|
|
60
61
|
|
|
61
62
|
## Supported Providers
|
|
62
63
|
|
|
63
|
-
- **OpenAI**
|
|
64
|
+
- **OpenAI** (including the Decisions classifier API)
|
|
64
65
|
- **Ant Ling**
|
|
65
66
|
- **Azure OpenAI (Responses)**
|
|
66
67
|
- **OpenAI Codex (legacy)** (ChatGPT Plus/Pro subscription, requires OAuth, see below)
|
|
@@ -232,8 +233,12 @@ for (const block of response.content) {
|
|
|
232
233
|
}
|
|
233
234
|
```
|
|
234
235
|
|
|
236
|
+
`usage.cost.totalFromProvider` is `true` when `usage.cost.total` is the billed cost reported by the provider and `false` when it is estimated from the model's catalog rates. A missing value, as in sessions recorded before this field existed, also means `false`.
|
|
237
|
+
|
|
235
238
|
Snippets in the rest of this README assume a `models` collection set up like this (with the relevant providers registered).
|
|
236
239
|
|
|
240
|
+
`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.
|
|
241
|
+
|
|
237
242
|
## Providers and Models
|
|
238
243
|
|
|
239
244
|
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.
|
|
@@ -425,6 +430,33 @@ Header names are merged case-insensitively. Explicit headers override auth/model
|
|
|
425
430
|
|
|
426
431
|
`transformHeaders` belongs to `Models`, not `Provider`. A `Models` implementation must consume it and remove it before calling `Provider.stream*()`. Provider implementations continue receiving ordinary `ApiStreamOptions` or `SimpleStreamOptions` and never handle the transform themselves. Use this option instead of calling `getAuth(model)` before `stream*()`, which would resolve request auth twice.
|
|
427
432
|
|
|
433
|
+
### Client Identity
|
|
434
|
+
|
|
435
|
+
`identity` controls how requests identify the calling app to providers. Set it on `createModels()` as a default, or per request; a request's fields override the `Models` default field by field:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
const models = createModels({
|
|
439
|
+
identity: {
|
|
440
|
+
userAgent: 'detailed',
|
|
441
|
+
version: '2.1.0',
|
|
442
|
+
attribution: { name: 'my-app', url: 'https://my-app.example', categories: ['cloud-agent'] },
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await models.completeSimple(model, context, { identity: { attribution: null } });
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- `userAgent`: `"detailed"` (default) sends `pi/<version> (<platform>; <runtime>; <arch>)`, `"short"` sends `pi`, and `{ custom: "..." }` sends the given value. On Bedrock the value is appended to the AWS SDK user agent.
|
|
450
|
+
- `version`: the version in the detailed `User-Agent`. Default: the pi-ai version.
|
|
451
|
+
- `attribution`: provider attribution headers. Absent (default) or `null` sends none; `null` also turns off attribution from a merged identity. `{ name, url, categories? }` maps to each provider's headers:
|
|
452
|
+
- OpenRouter: `HTTP-Referer: <url>` (the app's identity in OpenRouter's rankings, grouped by origin), `X-OpenRouter-Title: <name>`, and `X-OpenRouter-Categories` (comma-separated).
|
|
453
|
+
- NVIDIA NIM: `X-BILLING-INVOKE-ORIGIN: <name>` with its first letter upper-cased.
|
|
454
|
+
- OpenCode: `x-opencode-client: <name>`.
|
|
455
|
+
|
|
456
|
+
`PI_ATTRIBUTION` is pi's: `{ name: "pi", url: "https://pi.dev", categories: ["cli-agent"] }`.
|
|
457
|
+
|
|
458
|
+
Identity headers are defaults below provider, model, and request headers, so configured headers win and a `null` request header removes one. Providers that send a fixed client identity, such as GitHub Copilot and Anthropic OAuth, keep it. `withClientIdentity(models, identity)` returns a wrapper of a `Models` (or `MutableModels`) whose requests use `identity` over the one the `Models` was created with, for libraries that receive a `Models` from their host. `identity` is plain JSON, so it can be persisted with other request options.
|
|
459
|
+
|
|
428
460
|
### Credential Store
|
|
429
461
|
|
|
430
462
|
Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. pi-ai ships an in-memory default; apps inject persistent storage:
|
|
@@ -723,34 +755,6 @@ All streaming events emitted during assistant message generation:
|
|
|
723
755
|
|
|
724
756
|
Streaming events for different content blocks are not guaranteed to be contiguous. Providers may emit deltas for text, thinking, and tool calls in the same upstream chunk, and pi may surface corresponding events interleaved, for example `text_start`, `text_delta`, `toolcall_start`, `text_delta`, `toolcall_delta`. Consumers must use `contentIndex` to associate each delta/end event with its block and must not assume that a block's `*_start`/`*_delta`/`*_end` sequence is uninterrupted by events for other blocks.
|
|
725
757
|
|
|
726
|
-
### Compact Assistant Message Frames
|
|
727
|
-
|
|
728
|
-
`AssistantMessageFrameEncoder` converts one stream into compact, persistable `AssistantMessageFrame` values. Create one encoder per stream and feed it every event in order. The encoder understands that `partial` is live: a block-start event consumed after the provider has already queued later deltas snapshots the current block once, and covered queued text/thinking deltas produce no duplicate frame. It retains only per-open-block counters plus, temporarily, the raw prefix needed to synchronize an already-advanced tool call. It never clones the growing full partial per token.
|
|
729
|
-
|
|
730
|
-
The start frame contains message metadata with empty content. Text and thinking frames store each generated character at most once before the authoritative end frame. Tool calls that were already advanced when their start event was consumed use one compact JSON checkpoint before ordinary deltas resume. Terminal `done` and `error` events produce no frame because final message settlement is separate. A pre-generation `error` therefore produces no frames.
|
|
731
|
-
|
|
732
|
-
`reduceAssistantMessageFrames()` is the canonical pure reducer. It reconstructs text, thinking, and tool-call arguments, including interleaved blocks identified by `contentIndex`, and rejects malformed sequences. It performs a single pass over the iterable and returns `undefined` when there is no start frame. End frames replace blocks with the provider's authoritative completed content and metadata. The reducer does not validate tool arguments against a TypeBox schema; call `validateToolCall` before execution.
|
|
733
|
-
|
|
734
|
-
```typescript
|
|
735
|
-
import {
|
|
736
|
-
AssistantMessageFrameEncoder,
|
|
737
|
-
reduceAssistantMessageFrames,
|
|
738
|
-
type AssistantMessageFrame,
|
|
739
|
-
} from '@earendil-works/pi-ai';
|
|
740
|
-
|
|
741
|
-
const encoder = new AssistantMessageFrameEncoder();
|
|
742
|
-
const frames: AssistantMessageFrame[] = [];
|
|
743
|
-
for await (const event of s) {
|
|
744
|
-
const frame = encoder.encode(event);
|
|
745
|
-
if (frame) frames.push(frame);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const reconstructedPartial = reduceAssistantMessageFrames(frames);
|
|
749
|
-
const finalMessage = await s.result(); // Persist terminal settlement separately.
|
|
750
|
-
```
|
|
751
|
-
|
|
752
|
-
An encoder rejects duplicate starts, updates before start, `done` before start, events after a terminal event, duplicate block starts, and block-kind mismatches. An `error` before start is valid and returns no frame.
|
|
753
|
-
|
|
754
758
|
## Image Input
|
|
755
759
|
|
|
756
760
|
Models with vision capabilities can process images. You can check if a model supports images via the `input` property. If you pass images to a non-vision model, they are silently ignored.
|
|
@@ -890,15 +894,16 @@ console.log(model.output); // ['image'] or ['image', 'text']
|
|
|
890
894
|
|
|
891
895
|
## Classification
|
|
892
896
|
|
|
893
|
-
Classifier models consume structured JSON state and answer one or more typed questions. They do not use chat or image-generation APIs. TypeSafe's Jev model
|
|
897
|
+
Classifier models consume structured JSON state, and optionally images, and answer one or more typed questions. They do not use chat or image-generation APIs. TypeSafe's Jev model, Cloudflare's Clef models, and OpenAI's GPT-6 Luna are available from these built-in providers:
|
|
894
898
|
|
|
895
899
|
| Provider | Model IDs | Auth |
|
|
896
900
|
| --- | --- | --- |
|
|
897
901
|
| `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
|
|
898
|
-
| `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or OpenRouter OAuth |
|
|
899
|
-
| `cloudflare-workers-ai` | `typesafe/jev`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
|
|
902
|
+
| `openrouter` | `typesafe/jev-1.13`, `typesafe/jev-1.14`, `~typesafe/jev-latest`, `cloudflare/clef`, `cloudflare/clef-flash`, `cloudflare/clef-omni` | `OPENROUTER_API_KEY` or OpenRouter OAuth |
|
|
903
|
+
| `cloudflare-workers-ai` | `typesafe/jev`, `typesafe/jev-1.14.0`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash`, `@cf/cloudflare/clef-omni` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
|
|
900
904
|
| `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
|
|
901
905
|
| `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
|
|
906
|
+
| `openai` | `gpt-6-luna` | `OPENAI_API_KEY` |
|
|
902
907
|
|
|
903
908
|
```typescript
|
|
904
909
|
import { builtinModels } from '@earendil-works/pi-ai/providers/all';
|
|
@@ -936,10 +941,32 @@ The public contract uses `bool` questions and `{ type: "bool", probability }` an
|
|
|
936
941
|
|
|
937
942
|
When the service reports token counts, `result.usage` carries them with their cost at the model's catalog price, the same `Usage` shape as chat messages. All System One services report token counts; a request that was answered with malformed answers keeps its usage. Local classifiers such as `llama-cpp-classify` report no usage.
|
|
938
943
|
|
|
939
|
-
`ClassifierOptions.temperature` divides the answer logits by the given value before they are normalized; values above 1 soften the distribution. APIs that cannot apply it, such as System One, ignore it.
|
|
944
|
+
`ClassifierOptions.temperature` divides the answer logits by the given value before they are normalized; values above 1 soften the distribution. APIs that cannot apply it, such as System One and OpenAI Decisions, ignore it.
|
|
945
|
+
|
|
946
|
+
`ClassifierContext.images` holds image blocks (`{ type: "image", data, mimeType }`) judged together with the state. Only models whose `input` includes `"image"` accept them; `classify()` returns an error result for other models.
|
|
947
|
+
|
|
948
|
+
### System One images
|
|
949
|
+
|
|
950
|
+
System One services take images in three forms. The model's `compat.imageInput` chooses the form (`"assets"`, `"images"`, or `"state-image-urls"`), and `compat.sendImageOptInHeader` controls TypeSafe's opt-in header:
|
|
951
|
+
|
|
952
|
+
- **TypeSafe** (`typesafe`, and Jev on Workers AI) takes PNG and JPEG images as `assets`, `{ "<image-1>": { type: "image", format: "png", data_base64 } }`. Every asset must be referenced from the request, so the state is sent as `["<image-1>", ..., state]`. Images are experimental on TypeSafe's own API and need the `TypeSafe-Danger-Zone-Images: true` header, which the API sends with requests that carry images.
|
|
953
|
+
- **Clef on Workers AI** takes up to four PNG, JPEG, or WebP images as an `images` list of data URLs, next to the unchanged state.
|
|
954
|
+
- **OpenRouter** takes images as `{ type: "image_url", image_url: { url } }` parts before the state: `[part, ..., state]`. It answers requests with `assets` but drops the images, without an error.
|
|
955
|
+
|
|
956
|
+
Built-in catalog entries set `compat` explicitly, so they keep their form when `baseUrl` points at a proxy. For models without it, the APIs detect the form: OpenRouter by provider `openrouter` or a `baseUrl` containing `openrouter.ai`, Clef by a model ID starting with `@cf/cloudflare/clef`, and TypeSafe's form with the opt-in header otherwise. A custom provider that reaches OpenRouter through a proxy matches neither check and must set `compat.imageInput: "state-image-urls"` and `compat.sendImageOptInHeader: false`; otherwise OpenRouter drops its images.
|
|
957
|
+
|
|
958
|
+
Vercel AI Gateway and OpenCode list their Jev models with text input only.
|
|
959
|
+
|
|
960
|
+
### OpenAI Decisions
|
|
961
|
+
|
|
962
|
+
The `openai-decisions` API calls OpenAI's [Decisions API](https://developers.openai.com/api/docs/guides/decisions) (`POST /v1/decisions`). GPT-6 Luna is its only model. The state is sent as JSON text; with images, the input is one user message with the state followed by the images as data URLs (at most 128). `choice` and `score` questions map to the Decisions types of the same name. `bool` questions become `predicate` questions; predicates have no criteria field, so the meanings of true and false are appended to the instructions. If OpenAI refuses to answer a question, the whole result is an error. Usage reports input tokens only, priced at the catalog input rate with OpenAI's long-context multiplier.
|
|
963
|
+
|
|
964
|
+
The endpoint needs an OpenAI API key; Sign in with ChatGPT credentials are rejected, so the `openai` provider hides `gpt-6-luna` from `getAvailableOfType('classifier')` while it uses OAuth. The API rejects inputs above 922K tokens, but requests that run longer than about five seconds, currently above roughly 600K input tokens, fail with a gateway timeout (504).
|
|
940
965
|
|
|
941
966
|
### Chat models on llama.cpp
|
|
942
967
|
|
|
968
|
+
llama.cpp also serves decision models such as Julia-1 or Kev natively through `/v1/systemone`. Since llama.cpp 0.6.0, `GET /models` lists `decisions` in a model's `architecture.output_modalities` for these models. They use the `typesafe-system-one` API with the server's `/v1` URL as `baseUrl` (for example `http://127.0.0.1:8080/v1`); it needs an `apiKey`, which llama.cpp ignores unless it was started with `--api-key`. The `llama-cpp-classify` API is the fallback for chat models.
|
|
969
|
+
|
|
943
970
|
The `llama-cpp-classify` API turns a chat model served by llama.cpp's `llama-server` into a classifier. Each question becomes one chat prompt: the state, every question of the request, the state again, and the question with its answers under single-token labels (letters for a choice, `Yes`/`No` for a bool, digits for a score). The prompt up to the final question is shared by all questions of a request, so the server's prompt cache evaluates the state once per request. The server returns the log-probabilities of the next token, and the answer is the softmax over the label tokens. Choices support up to 62 options and scores up to 10 levels. The model's `baseUrl` is the server URL; a trailing `/v1` is ignored. In router mode, the model ID selects the model.
|
|
944
971
|
|
|
945
972
|
```typescript
|
|
@@ -1088,6 +1115,40 @@ Every `AssistantMessage` includes a `stopReason` field that indicates how the ge
|
|
|
1088
1115
|
|
|
1089
1116
|
`AssistantMessage` may also include `responseId`, a provider-specific upstream response or message identifier when the underlying API exposes one. Do not assume it is always present across providers.
|
|
1090
1117
|
|
|
1118
|
+
## Telemetry
|
|
1119
|
+
|
|
1120
|
+
Pass an explicit parent span (`TelemetrySpan`, not an adapter) through `telemetry`; omitting it uses the no-op span:
|
|
1121
|
+
|
|
1122
|
+
```typescript
|
|
1123
|
+
import { InMemoryTelemetryContext } from '@earendil-works/pi-telemetry';
|
|
1124
|
+
|
|
1125
|
+
const telemetry = new InMemoryTelemetryContext();
|
|
1126
|
+
await telemetry.startSpan({ name: 'application.turn' }, async (span) => {
|
|
1127
|
+
await models.completeSimple(model, context, { telemetry: span });
|
|
1128
|
+
});
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
`Models.stream()` and `streamSimple()` create `pi.ai.request`; `streamDeferred()` creates one `pi.ai.poll` per poll. Their completion helpers use the same spans. These spans include auth/setup, end at the terminal result, and do not require the caller to consume events. Direct provider/API calls are not instrumented.
|
|
1132
|
+
|
|
1133
|
+
`AI_TELEMETRY_SCHEMA`, exported from the root and `@earendil-works/pi-ai/models`, defines the typed Pi-owned vocabulary:
|
|
1134
|
+
|
|
1135
|
+
- Start attributes: `provider`, `api`, `model`, and `reasoning` only when `options.reasoning` is a string. Provider-native reasoning objects are ignored.
|
|
1136
|
+
- End attributes: `stop`, `usage.input`, `usage.output`, `usage.cache_read`, `usage.cache_write`, `cost.total`, and optional `response.id`. `stop: "error"` sets error status with the fixed category `provider_error` and `AssistantMessage.errorMessage` truncated to 500 characters (empty when absent). Cancellation keeps status `ok` with `stop: "aborted"`.
|
|
1137
|
+
- `pi.ai.first_chunk` marks the first normalized event, including setup errors. Each completed text, thinking, or tool-call block emits `pi.ai.block` with `type`, `index`, and `chars`; only tool calls include `tool`. Character counts use JavaScript string length; tool counts sum argument delta lengths without serializing arguments.
|
|
1138
|
+
|
|
1139
|
+
When `options.telemetry.metrics` is available, the same requests record metrics:
|
|
1140
|
+
|
|
1141
|
+
| Metric | Unit | Attributes |
|
|
1142
|
+
| --- | --- | --- |
|
|
1143
|
+
| `pi.ai.request.duration` | `ms` | `provider`, `model`, `stop` |
|
|
1144
|
+
| `pi.ai.first_chunk.duration` | `ms` | `provider`, `model` |
|
|
1145
|
+
| `pi.ai.tokens` | `token` | `provider`, `model`, `kind` (`input`, `output`, `cache_read`, `cache_write`) |
|
|
1146
|
+
| `pi.ai.cost` | `USD` | `provider`, `model` |
|
|
1147
|
+
|
|
1148
|
+
Durations use `performance.now()`. Pending/deferred polls do not add tokens or cost; final completed/error polls do. A parent span without metric capabilities remains valid and simply records no metrics. Adapter-created spans carry the same optional metric and propagation capabilities as their root.
|
|
1149
|
+
|
|
1150
|
+
Telemetry does not record prompts, generated text, reasoning content, or arguments in attributes/events. Error status includes up to 500 characters of provider error text, which may contain sensitive data; apply your export policy accordingly. Telemetry observes the existing forwarding path without another stream consumer or per-delta events. Response identifiers and error messages never become metric attributes.
|
|
1151
|
+
|
|
1091
1152
|
## Error Handling
|
|
1092
1153
|
|
|
1093
1154
|
Request failures after a stream is returned never throw: when a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event and the final message carries the details. Setup failures may emit `error` without `start`; failures after generation begins emit `start`, any observed updates, then `error`. Direct API `streamSimple()` calls throw synchronously when request auth is missing:
|
|
@@ -1516,13 +1577,13 @@ Notes:
|
|
|
1516
1577
|
|
|
1517
1578
|
## Cross-Provider Handoffs
|
|
1518
1579
|
|
|
1519
|
-
The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including
|
|
1580
|
+
The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including text, tool calls, and tool results.
|
|
1520
1581
|
|
|
1521
1582
|
When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
|
|
1522
1583
|
|
|
1523
1584
|
- **User and tool result messages** are passed through unchanged
|
|
1524
|
-
- **Assistant messages from the same provider
|
|
1525
|
-
- **Assistant messages from different
|
|
1585
|
+
- **Assistant messages from the same provider, API, and model** are preserved as-is, including thinking blocks
|
|
1586
|
+
- **Assistant messages from a different model** have their thinking blocks dropped. Signatures and encrypted reasoning only work with the model that produced them, and readable thinking sent as plain text makes the new model treat it as part of the reply
|
|
1526
1587
|
- **Tool calls and regular text** are preserved unchanged
|
|
1527
1588
|
|
|
1528
1589
|
```typescript
|
|
@@ -1543,7 +1604,7 @@ const claude = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
|
1543
1604
|
context.messages.push({ role: 'user', content: 'What is 25 * 18?', timestamp: Date.now() });
|
|
1544
1605
|
context.messages.push(await models.completeSimple(claude, context, { reasoning: 'medium' }));
|
|
1545
1606
|
|
|
1546
|
-
// Switch to GPT-5 - it
|
|
1607
|
+
// Switch to GPT-5 - it sees Claude's answer, but not Claude's thinking
|
|
1547
1608
|
const gpt5 = models.getModel('openai', 'gpt-5-mini')!;
|
|
1548
1609
|
context.messages.push({ role: 'user', content: 'Is that calculation correct?', timestamp: Date.now() });
|
|
1549
1610
|
context.messages.push(await models.complete(gpt5, context));
|
|
@@ -1554,7 +1615,7 @@ context.messages.push({ role: 'user', content: 'What was the original question?'
|
|
|
1554
1615
|
const geminiResponse = await models.complete(gemini, context);
|
|
1555
1616
|
```
|
|
1556
1617
|
|
|
1557
|
-
All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (
|
|
1618
|
+
All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (dropped when the model changes), and aborted messages with partial content. This enables flexible workflows: start with a fast model, switch to a more capable one for complex reasoning, or maintain continuity across provider outages.
|
|
1558
1619
|
|
|
1559
1620
|
## System Messages
|
|
1560
1621
|
|
|
@@ -1587,7 +1648,7 @@ getCurrentTools(messages); // []
|
|
|
1587
1648
|
|
|
1588
1649
|
A custom `Provider` or `ProviderStreams` implementation reads the prompt and tools the same way from `context.messages`; `context.systemPrompt` and `context.tools` do not exist at that layer.
|
|
1589
1650
|
|
|
1590
|
-
Models that accept system messages mid-conversation (`supportsMidConvoSystemMessages` in the model's compat settings, set by the generated catalog for verified models) receive each later system message in place, so the cached prefix stays intact; section changes are framed by name for the model. Every other model receives `collapseSystemMessages(transcript)`: the replayed prompt and current tools as the leading system message, with later system messages dropped. Anthropic models that also set `supportsMidConvoToolChanges` send tool changes as native blocks (`inline-tools-2026-09-15` beta): the top-level tool list holds the initial tools plus a stable deferred placeholder and never changes, later tools are defined by value in `tool_addition` blocks (a new definition under an existing name replaces the old one), and removals are `tool_removal` references, so tool changes do not invalidate the prompt cache. That needs at least one initial tool; otherwise the current tool list is sent at the top level with the system text only. OpenAI Responses models with `supportsAdditionalTools` or `supportsToolSearch` anchor
|
|
1651
|
+
Models that accept system messages mid-conversation (`supportsMidConvoSystemMessages` in the model's compat settings, set by the generated catalog for verified models) receive each later system message in place, so the cached prefix stays intact; section changes are framed by name for the model. Every other model receives `collapseSystemMessages(transcript)`: the replayed prompt and current tools as the leading system message, with later system messages dropped. Anthropic models that also set `supportsMidConvoToolChanges` send tool changes as native blocks (`inline-tools-2026-09-15` beta): the top-level tool list holds the initial tools plus a stable deferred placeholder and never changes, later tools are defined by value in `tool_addition` blocks (a new definition under an existing name replaces the old one), and removals are `tool_removal` references, so tool changes do not invalidate the prompt cache. That needs at least one initial tool; otherwise the current tool list is sent at the top level with the system text only. OpenAI Responses models with `supportsAdditionalTools` declare every tool in `additional_tools` items, like Codex: the initial tools in one at the head of the input and later additions and redefinitions at their message, with no top-level tool list. A later item replaces same-name definitions from earlier items but cannot replace a top-level tool or remove any tool, so removals are announced as text, later updates restate the tools that are still unavailable, and calls to removed tools fail at execution. OpenAI Responses models with only `supportsToolSearch`, and Kimi models with `supportsMidConvoToolAdditions`, anchor new tools at their message; a removal or a redeclared name moves the tool state up to that point into the top-level list once. Everything else sends the current tool list at the top level.
|
|
1591
1652
|
|
|
1592
1653
|
## Context Serialization
|
|
1593
1654
|
|
|
@@ -1879,7 +1940,7 @@ Add a lazy wrapper `src/api/<api-id>.lazy.ts` (`<name>Api()` via `lazyApi()`) so
|
|
|
1879
1940
|
- `createProvider()` wiring catalog + auth + the lazy API wrapper
|
|
1880
1941
|
- Auth: `envApiKeyAuth` for standard key providers, a custom `ApiKeyAuth` for ambient auth (AWS profiles, ADC), `lazyOAuth` where an OAuth flow exists
|
|
1881
1942
|
- Register the factory in `src/providers/all.ts`
|
|
1882
|
-
- If it is a new API: register it in the builtin list in `src/compat.ts
|
|
1943
|
+
- If it is a new API: register it in the builtin list in `src/compat.ts`; the `./api/*` subpath export in `package.json` already covers the new module
|
|
1883
1944
|
|
|
1884
1945
|
#### 5. Tests (`test/`)
|
|
1885
1946
|
|
|
@@ -1890,7 +1951,6 @@ Create or update test files to cover the new provider:
|
|
|
1890
1951
|
- `abort.test.ts` - Request cancellation
|
|
1891
1952
|
- `empty.test.ts` - Empty message handling
|
|
1892
1953
|
- `context-overflow.test.ts` - Context limit errors
|
|
1893
|
-
- `image-limits.test.ts` - Image support (if applicable)
|
|
1894
1954
|
- `unicode-surrogate.test.ts` - Unicode handling
|
|
1895
1955
|
- `tool-call-without-result.test.ts` - Orphaned tool calls
|
|
1896
1956
|
- `image-tool-result.test.ts` - Images in tool results
|
|
@@ -1906,15 +1966,15 @@ For providers with non-standard auth (AWS, Google Vertex), create a utility like
|
|
|
1906
1966
|
|
|
1907
1967
|
Update `src/core/model-resolver.ts`:
|
|
1908
1968
|
|
|
1909
|
-
- Add a default model ID for the provider in `
|
|
1969
|
+
- Add a default model ID for the provider in `defaultModelPerProvider`
|
|
1910
1970
|
|
|
1911
1971
|
Update `src/cli/args.ts`:
|
|
1912
1972
|
|
|
1913
1973
|
- Add environment variable documentation in the help text
|
|
1914
1974
|
|
|
1915
|
-
Update `
|
|
1975
|
+
Update `docs/providers.md`:
|
|
1916
1976
|
|
|
1917
|
-
- Add
|
|
1977
|
+
- Add setup instructions, the environment variable, and the `auth.json` key
|
|
1918
1978
|
|
|
1919
1979
|
#### 7. Documentation
|
|
1920
1980
|
|
|
@@ -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;AAoBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,
|
|
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,EAIb,MAAM,aAAa,CAAC;AA6JrB,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;AAoRD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAkWzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
|