@earendil-works/pi-ai 1.2.0 → 1.4.0
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 +83 -37
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +66 -64
- 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 -4
- 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 +28 -18
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/classifier-shared.d.ts.map +1 -1
- package/dist/api/classifier-shared.js +4 -3
- package/dist/api/classifier-shared.js.map +1 -1
- package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
- package/dist/api/cloudflare-workers-ai-system-one.js +8 -0
- package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
- package/dist/api/google-generative-ai.js +4 -4
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +3 -1
- 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 -9
- 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 +24 -5
- 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 +2 -1
- package/dist/api/llama-cpp-classify.js.map +1 -1
- package/dist/api/mistral-conversations.js +4 -4
- 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 +17 -11
- 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 +43 -35
- package/dist/api/openai-completions.js.map +1 -1
- 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 +76 -11
- 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 -13
- 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 +4 -3
- package/dist/api/openrouter-images.js.map +1 -1
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +3 -3
- 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 -2
- package/dist/api/system-one-shared.d.ts.map +1 -1
- package/dist/api/system-one-shared.js +55 -3
- 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 +10 -0
- package/dist/api/typesafe-system-one.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +4 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +19 -6
- package/dist/models.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +171 -0
- package/dist/providers/compat-schema.d.ts.map +1 -1
- package/dist/providers/compat-schema.js +23 -2
- package/dist/providers/compat-schema.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/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/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/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.map +1 -1
- package/dist/providers/model-schema.js +4 -2
- package/dist/providers/model-schema.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/telemetry.d.ts +288 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +191 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +18 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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/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/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +21 -8
- package/dist/utils/retry.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 +2 -2
- 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)
|
|
@@ -425,6 +426,33 @@ Header names are merged case-insensitively. Explicit headers override auth/model
|
|
|
425
426
|
|
|
426
427
|
`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
428
|
|
|
429
|
+
### Client Identity
|
|
430
|
+
|
|
431
|
+
`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:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
const models = createModels({
|
|
435
|
+
identity: {
|
|
436
|
+
userAgent: 'detailed',
|
|
437
|
+
version: '2.1.0',
|
|
438
|
+
attribution: { name: 'my-app', url: 'https://my-app.example', categories: ['cloud-agent'] },
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await models.completeSimple(model, context, { identity: { attribution: null } });
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
- `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.
|
|
446
|
+
- `version`: the version in the detailed `User-Agent`. Default: the pi-ai version.
|
|
447
|
+
- `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:
|
|
448
|
+
- 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).
|
|
449
|
+
- NVIDIA NIM: `X-BILLING-INVOKE-ORIGIN: <name>` with its first letter upper-cased.
|
|
450
|
+
- OpenCode: `x-opencode-client: <name>`.
|
|
451
|
+
|
|
452
|
+
`PI_ATTRIBUTION` is pi's: `{ name: "pi", url: "https://pi.dev", categories: ["cli-agent"] }`.
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
|
|
428
456
|
### Credential Store
|
|
429
457
|
|
|
430
458
|
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 +751,6 @@ All streaming events emitted during assistant message generation:
|
|
|
723
751
|
|
|
724
752
|
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
753
|
|
|
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
754
|
## Image Input
|
|
755
755
|
|
|
756
756
|
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.
|
|
@@ -895,8 +895,8 @@ Classifier models consume structured JSON state, and optionally images, and answ
|
|
|
895
895
|
| Provider | Model IDs | Auth |
|
|
896
896
|
| --- | --- | --- |
|
|
897
897
|
| `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` |
|
|
898
|
+
| `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 |
|
|
899
|
+
| `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
900
|
| `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
|
|
901
901
|
| `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
|
|
902
902
|
| `openai` | `gpt-6-luna` | `OPENAI_API_KEY` |
|
|
@@ -941,6 +941,18 @@ When the service reports token counts, `result.usage` carries them with their co
|
|
|
941
941
|
|
|
942
942
|
`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.
|
|
943
943
|
|
|
944
|
+
### System One images
|
|
945
|
+
|
|
946
|
+
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:
|
|
947
|
+
|
|
948
|
+
- **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.
|
|
949
|
+
- **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.
|
|
950
|
+
- **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.
|
|
951
|
+
|
|
952
|
+
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.
|
|
953
|
+
|
|
954
|
+
Vercel AI Gateway and OpenCode list their Jev models with text input only.
|
|
955
|
+
|
|
944
956
|
### OpenAI Decisions
|
|
945
957
|
|
|
946
958
|
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.
|
|
@@ -1099,6 +1111,40 @@ Every `AssistantMessage` includes a `stopReason` field that indicates how the ge
|
|
|
1099
1111
|
|
|
1100
1112
|
`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.
|
|
1101
1113
|
|
|
1114
|
+
## Telemetry
|
|
1115
|
+
|
|
1116
|
+
Pass an explicit parent span (`TelemetrySpan`, not an adapter) through `telemetry`; omitting it uses the no-op span:
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
import { InMemoryTelemetryContext } from '@earendil-works/pi-telemetry';
|
|
1120
|
+
|
|
1121
|
+
const telemetry = new InMemoryTelemetryContext();
|
|
1122
|
+
await telemetry.startSpan({ name: 'application.turn' }, async (span) => {
|
|
1123
|
+
await models.completeSimple(model, context, { telemetry: span });
|
|
1124
|
+
});
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
`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.
|
|
1128
|
+
|
|
1129
|
+
`AI_TELEMETRY_SCHEMA`, exported from the root and `@earendil-works/pi-ai/models`, defines the typed Pi-owned vocabulary:
|
|
1130
|
+
|
|
1131
|
+
- Start attributes: `provider`, `api`, `model`, and `reasoning` only when `options.reasoning` is a string. Provider-native reasoning objects are ignored.
|
|
1132
|
+
- 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"`.
|
|
1133
|
+
- `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.
|
|
1134
|
+
|
|
1135
|
+
When `options.telemetry.metrics` is available, the same requests record metrics:
|
|
1136
|
+
|
|
1137
|
+
| Metric | Unit | Attributes |
|
|
1138
|
+
| --- | --- | --- |
|
|
1139
|
+
| `pi.ai.request.duration` | `ms` | `provider`, `model`, `stop` |
|
|
1140
|
+
| `pi.ai.first_chunk.duration` | `ms` | `provider`, `model` |
|
|
1141
|
+
| `pi.ai.tokens` | `token` | `provider`, `model`, `kind` (`input`, `output`, `cache_read`, `cache_write`) |
|
|
1142
|
+
| `pi.ai.cost` | `USD` | `provider`, `model` |
|
|
1143
|
+
|
|
1144
|
+
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.
|
|
1145
|
+
|
|
1146
|
+
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.
|
|
1147
|
+
|
|
1102
1148
|
## Error Handling
|
|
1103
1149
|
|
|
1104
1150
|
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:
|
|
@@ -1527,13 +1573,13 @@ Notes:
|
|
|
1527
1573
|
|
|
1528
1574
|
## Cross-Provider Handoffs
|
|
1529
1575
|
|
|
1530
|
-
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
|
|
1576
|
+
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.
|
|
1531
1577
|
|
|
1532
1578
|
When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
|
|
1533
1579
|
|
|
1534
1580
|
- **User and tool result messages** are passed through unchanged
|
|
1535
|
-
- **Assistant messages from the same provider
|
|
1536
|
-
- **Assistant messages from different
|
|
1581
|
+
- **Assistant messages from the same provider, API, and model** are preserved as-is, including thinking blocks
|
|
1582
|
+
- **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
|
|
1537
1583
|
- **Tool calls and regular text** are preserved unchanged
|
|
1538
1584
|
|
|
1539
1585
|
```typescript
|
|
@@ -1554,7 +1600,7 @@ const claude = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
|
1554
1600
|
context.messages.push({ role: 'user', content: 'What is 25 * 18?', timestamp: Date.now() });
|
|
1555
1601
|
context.messages.push(await models.completeSimple(claude, context, { reasoning: 'medium' }));
|
|
1556
1602
|
|
|
1557
|
-
// Switch to GPT-5 - it
|
|
1603
|
+
// Switch to GPT-5 - it sees Claude's answer, but not Claude's thinking
|
|
1558
1604
|
const gpt5 = models.getModel('openai', 'gpt-5-mini')!;
|
|
1559
1605
|
context.messages.push({ role: 'user', content: 'Is that calculation correct?', timestamp: Date.now() });
|
|
1560
1606
|
context.messages.push(await models.complete(gpt5, context));
|
|
@@ -1565,7 +1611,7 @@ context.messages.push({ role: 'user', content: 'What was the original question?'
|
|
|
1565
1611
|
const geminiResponse = await models.complete(gemini, context);
|
|
1566
1612
|
```
|
|
1567
1613
|
|
|
1568
|
-
All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (
|
|
1614
|
+
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.
|
|
1569
1615
|
|
|
1570
1616
|
## System Messages
|
|
1571
1617
|
|
|
@@ -1598,7 +1644,7 @@ getCurrentTools(messages); // []
|
|
|
1598
1644
|
|
|
1599
1645
|
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.
|
|
1600
1646
|
|
|
1601
|
-
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
|
|
1647
|
+
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.
|
|
1602
1648
|
|
|
1603
1649
|
## Context Serialization
|
|
1604
1650
|
|
|
@@ -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,EAMb,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAoBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAuJrB,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,CA2WzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
|
|
@@ -4,8 +4,8 @@ import { calculateCost } from "../models.js";
|
|
|
4
4
|
import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
|
|
5
5
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
6
6
|
import { headersToRecord } from "../utils/headers.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { getIdentityHeaders } from "../utils/identity.js";
|
|
8
|
+
import { parseJsonWithRepair, parseStreamingJson, parseToolCallArguments } from "../utils/json-parse.js";
|
|
9
9
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
10
10
|
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
11
11
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
@@ -14,7 +14,7 @@ import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "..
|
|
|
14
14
|
import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
15
15
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
16
16
|
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
|
|
17
|
-
import { transformMessages } from "./transform-messages.js";
|
|
17
|
+
import { splitErrorToolResultImages, transformMessages } from "./transform-messages.js";
|
|
18
18
|
/**
|
|
19
19
|
* Resolve cache retention preference.
|
|
20
20
|
* Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
|
|
@@ -75,6 +75,16 @@ const fromClaudeCodeName = (name, tools) => {
|
|
|
75
75
|
}
|
|
76
76
|
return name;
|
|
77
77
|
};
|
|
78
|
+
function convertImageBlock(image) {
|
|
79
|
+
return {
|
|
80
|
+
type: "image",
|
|
81
|
+
source: {
|
|
82
|
+
type: "base64",
|
|
83
|
+
media_type: image.mimeType,
|
|
84
|
+
data: image.data,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
78
88
|
/**
|
|
79
89
|
* Convert content blocks to Anthropic API format
|
|
80
90
|
*/
|
|
@@ -92,14 +102,7 @@ function convertContentBlocks(content) {
|
|
|
92
102
|
text: sanitizeSurrogates(block.text),
|
|
93
103
|
};
|
|
94
104
|
}
|
|
95
|
-
return
|
|
96
|
-
type: "image",
|
|
97
|
-
source: {
|
|
98
|
-
type: "base64",
|
|
99
|
-
media_type: block.mimeType,
|
|
100
|
-
data: block.data,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
105
|
+
return convertImageBlock(block);
|
|
103
106
|
});
|
|
104
107
|
// If only images (no text), add placeholder text block
|
|
105
108
|
const hasText = blocks.some((b) => b.type === "text");
|
|
@@ -157,9 +160,6 @@ function mergeHeaders(...headerSources) {
|
|
|
157
160
|
}
|
|
158
161
|
return merged;
|
|
159
162
|
}
|
|
160
|
-
function mergeClientHeaders(...headerSources) {
|
|
161
|
-
return mergeHeaders({ "User-Agent": getPiUserAgent() }, ...headerSources);
|
|
162
|
-
}
|
|
163
163
|
function hasHeader(headers, name) {
|
|
164
164
|
if (!headers)
|
|
165
165
|
return false;
|
|
@@ -378,14 +378,12 @@ export const stream = (model, context, options) => {
|
|
|
378
378
|
const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);
|
|
379
379
|
const currentTools = getCurrentTools(normalizedContext.messages);
|
|
380
380
|
(async () => {
|
|
381
|
-
const providerThinkingLevel = model.compat?.supportsMidConvoEffort ? (options?.effort ?? "high") : undefined;
|
|
382
381
|
const output = {
|
|
383
382
|
role: "assistant",
|
|
384
383
|
content: [],
|
|
385
384
|
api: model.api,
|
|
386
385
|
provider: model.provider,
|
|
387
386
|
model: model.id,
|
|
388
|
-
...(providerThinkingLevel === undefined ? {} : { providerThinkingLevel }),
|
|
389
387
|
usage: {
|
|
390
388
|
input: 0,
|
|
391
389
|
output: 0,
|
|
@@ -421,7 +419,7 @@ export const stream = (model, context, options) => {
|
|
|
421
419
|
}
|
|
422
420
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
|
423
421
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
424
|
-
const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation);
|
|
422
|
+
const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation, options?.identity);
|
|
425
423
|
client = created.client;
|
|
426
424
|
isOAuth = created.isOAuthToken;
|
|
427
425
|
}
|
|
@@ -435,6 +433,8 @@ export const stream = (model, context, options) => {
|
|
|
435
433
|
for (;;) {
|
|
436
434
|
options?.signal?.throwIfAborted();
|
|
437
435
|
const built = buildParams(model, normalizedContext, isOAuth, options, disablePreferredStrictTools);
|
|
436
|
+
if (built.providerThinkingLevel)
|
|
437
|
+
output.providerThinkingLevel = built.providerThinkingLevel;
|
|
438
438
|
let params = built.params;
|
|
439
439
|
const nextParams = await options?.onPayload?.(params, model);
|
|
440
440
|
if (nextParams !== undefined) {
|
|
@@ -625,7 +625,7 @@ export const stream = (model, context, options) => {
|
|
|
625
625
|
});
|
|
626
626
|
}
|
|
627
627
|
else if (block.type === "toolCall") {
|
|
628
|
-
block
|
|
628
|
+
Object.assign(block, parseToolCallArguments(block.partialJson));
|
|
629
629
|
// Finalize in-place and strip the scratch buffer so replay only
|
|
630
630
|
// carries parsed arguments.
|
|
631
631
|
delete block.partialJson;
|
|
@@ -780,7 +780,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
780
780
|
function isOAuthToken(apiKey) {
|
|
781
781
|
return apiKey.includes("sk-ant-oat");
|
|
782
782
|
}
|
|
783
|
-
function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId, federation) {
|
|
783
|
+
function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId, federation, identity) {
|
|
784
784
|
// Copilot: Bearer auth.
|
|
785
785
|
if (model.provider === "github-copilot") {
|
|
786
786
|
const client = new PiAnthropic({
|
|
@@ -789,14 +789,14 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
789
789
|
baseURL: model.baseUrl,
|
|
790
790
|
dangerouslyAllowBrowser: true,
|
|
791
791
|
fetch,
|
|
792
|
-
defaultHeaders:
|
|
792
|
+
defaultHeaders: mergeHeaders(getIdentityHeaders(model, identity), {
|
|
793
793
|
accept: "application/json",
|
|
794
794
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
795
795
|
}, model.headers, dynamicHeaders, optionsHeaders),
|
|
796
796
|
});
|
|
797
797
|
return { client, isOAuthToken: false };
|
|
798
798
|
}
|
|
799
|
-
// OAuth: Bearer auth, Claude Code identity headers
|
|
799
|
+
// OAuth: Bearer auth, Claude Code identity headers instead of the client identity.
|
|
800
800
|
if (apiKey && isOAuthToken(apiKey)) {
|
|
801
801
|
const client = new PiAnthropic({
|
|
802
802
|
apiKey: null,
|
|
@@ -804,7 +804,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
804
804
|
baseURL: model.baseUrl,
|
|
805
805
|
dangerouslyAllowBrowser: true,
|
|
806
806
|
fetch,
|
|
807
|
-
defaultHeaders:
|
|
807
|
+
defaultHeaders: mergeHeaders({
|
|
808
808
|
accept: "application/json",
|
|
809
809
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
810
810
|
"user-agent": `claude-cli/${claudeCodeVersion}`,
|
|
@@ -820,7 +820,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
820
820
|
const header = compat.sessionAffinityFormat === "openrouter" ? "x-session-id" : "x-session-affinity";
|
|
821
821
|
sessionAffinityHeaders[header] = sessionId;
|
|
822
822
|
}
|
|
823
|
-
const defaultHeaders =
|
|
823
|
+
const defaultHeaders = mergeHeaders(getIdentityHeaders(model, identity), {
|
|
824
824
|
accept: "application/json",
|
|
825
825
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
826
826
|
}, sessionAffinityHeaders, model.headers, optionsHeaders);
|
|
@@ -911,14 +911,15 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
911
911
|
};
|
|
912
912
|
const initialTools = initialSystemMessage?.toolsAdded ?? [];
|
|
913
913
|
const nativeToolChanges = compat.supportsMidConvoSystemMessages && compat.supportsMidConvoToolChanges && initialTools.length > 0;
|
|
914
|
-
const
|
|
914
|
+
const thinkingDisabled = model.reasoning && options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null;
|
|
915
|
+
// Effort cannot change between messages while thinking is disabled, so "off" sends no effort markers.
|
|
916
|
+
const effortMessages = model.compat?.supportsMidConvoEffort === true && !thinkingDisabled;
|
|
917
|
+
const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, effortMessages ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
|
|
915
918
|
const activeEffort = options?.effort ?? "high";
|
|
916
919
|
const betaFeatures = getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options);
|
|
917
920
|
const params = {
|
|
918
921
|
model: model.id,
|
|
919
|
-
messages:
|
|
920
|
-
? insertThinkingLevelMessages(converted, activeEffort)
|
|
921
|
-
: converted.messages,
|
|
922
|
+
messages: effortMessages ? insertThinkingLevelMessages(converted, activeEffort) : converted.messages,
|
|
922
923
|
max_tokens: options?.maxTokens ?? model.maxTokens,
|
|
923
924
|
stream: true,
|
|
924
925
|
...(betaFeatures.length > 0 ? { betas: betaFeatures } : {}),
|
|
@@ -971,9 +972,14 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
971
972
|
params.tools = convertToolDefinitions(tools, toolCacheControl);
|
|
972
973
|
}
|
|
973
974
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
975
|
+
if (thinkingDisabled) {
|
|
976
|
+
// No effort is sent, so the model default applies. Anthropic rejects `block_binding`
|
|
977
|
+
// with disabled thinking.
|
|
978
|
+
params.thinking = { type: "disabled" };
|
|
979
|
+
}
|
|
980
|
+
else if (effortMessages) {
|
|
981
|
+
// Managed effort models use adaptive thinking so prefix mismatches can
|
|
982
|
+
// be dropped instead of surfacing as persistent 400 responses.
|
|
977
983
|
params.thinking = {
|
|
978
984
|
type: "adaptive",
|
|
979
985
|
display: options?.thinkingDisplay ?? "summarized",
|
|
@@ -981,29 +987,24 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
981
987
|
};
|
|
982
988
|
params.output_config = { effort: "high" };
|
|
983
989
|
}
|
|
984
|
-
else if (model.reasoning) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
params.output_config = { effort: options.effort };
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
else {
|
|
997
|
-
// Budget-based thinking for older models
|
|
998
|
-
params.thinking = {
|
|
999
|
-
type: "enabled",
|
|
1000
|
-
budget_tokens: options.thinkingBudgetTokens || 1024,
|
|
1001
|
-
display,
|
|
1002
|
-
};
|
|
990
|
+
else if (model.reasoning && options?.thinkingEnabled) {
|
|
991
|
+
// Default to "summarized" so Opus 4.7 and Mythos Preview behave like
|
|
992
|
+
// older Claude 4 models (whose API default is also "summarized").
|
|
993
|
+
const display = options.thinkingDisplay ?? "summarized";
|
|
994
|
+
if (model.compat?.forceAdaptiveThinking === true) {
|
|
995
|
+
// Adaptive thinking: Claude decides when and how much to think.
|
|
996
|
+
params.thinking = { type: "adaptive", display };
|
|
997
|
+
if (options.effort) {
|
|
998
|
+
params.output_config = { effort: options.effort };
|
|
1003
999
|
}
|
|
1004
1000
|
}
|
|
1005
|
-
else
|
|
1006
|
-
|
|
1001
|
+
else {
|
|
1002
|
+
// Budget-based thinking for older models
|
|
1003
|
+
params.thinking = {
|
|
1004
|
+
type: "enabled",
|
|
1005
|
+
budget_tokens: options.thinkingBudgetTokens || 1024,
|
|
1006
|
+
display,
|
|
1007
|
+
};
|
|
1007
1008
|
}
|
|
1008
1009
|
}
|
|
1009
1010
|
if (options?.metadata) {
|
|
@@ -1024,17 +1025,17 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
1024
1025
|
if (allowedFallbackModels && allowedFallbackModels.length > 0) {
|
|
1025
1026
|
params.fallbacks = allowedFallbackModels.map((fallback) => ({ model: fallback.model }));
|
|
1026
1027
|
}
|
|
1027
|
-
return { params, hasPreferredStrictTools };
|
|
1028
|
+
return { params, hasPreferredStrictTools, providerThinkingLevel: effortMessages ? activeEffort : undefined };
|
|
1028
1029
|
}
|
|
1029
1030
|
// Normalize tool call IDs to match Anthropic's required pattern and length
|
|
1030
1031
|
function normalizeToolCallId(id) {
|
|
1031
1032
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
1032
1033
|
}
|
|
1033
|
-
function convertToolResult(msg) {
|
|
1034
|
+
function convertToolResult(msg, content = msg.content) {
|
|
1034
1035
|
return {
|
|
1035
1036
|
type: "tool_result",
|
|
1036
1037
|
tool_use_id: msg.toolCallId,
|
|
1037
|
-
content: convertContentBlocks(
|
|
1038
|
+
content: convertContentBlocks(content),
|
|
1038
1039
|
is_error: msg.isError,
|
|
1039
1040
|
};
|
|
1040
1041
|
}
|
|
@@ -1099,14 +1100,7 @@ convertToolDefinitions) {
|
|
|
1099
1100
|
};
|
|
1100
1101
|
}
|
|
1101
1102
|
else {
|
|
1102
|
-
return
|
|
1103
|
-
type: "image",
|
|
1104
|
-
source: {
|
|
1105
|
-
type: "base64",
|
|
1106
|
-
media_type: item.mimeType,
|
|
1107
|
-
data: item.data,
|
|
1108
|
-
},
|
|
1109
|
-
};
|
|
1103
|
+
return convertImageBlock(item);
|
|
1110
1104
|
}
|
|
1111
1105
|
});
|
|
1112
1106
|
const filteredBlocks = blocks.filter((b) => {
|
|
@@ -1197,16 +1191,24 @@ convertToolDefinitions) {
|
|
|
1197
1191
|
else if (msg.role === "toolResult") {
|
|
1198
1192
|
// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
|
|
1199
1193
|
const toolResults = [];
|
|
1194
|
+
// Anthropic rejects error tool results that contain anything but text, so their images follow
|
|
1195
|
+
// all tool results of the turn, which must come first in the user message (#10794).
|
|
1196
|
+
const errorImages = [];
|
|
1200
1197
|
let j = i;
|
|
1201
1198
|
while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
|
|
1202
|
-
|
|
1199
|
+
const toolMsg = transformedMessages[j];
|
|
1200
|
+
const split = splitErrorToolResultImages(toolMsg);
|
|
1201
|
+
toolResults.push(convertToolResult(toolMsg, split?.content));
|
|
1202
|
+
if (split) {
|
|
1203
|
+
errorImages.push({ type: "text", text: split.caption }, ...split.images.map(convertImageBlock));
|
|
1204
|
+
}
|
|
1203
1205
|
j++;
|
|
1204
1206
|
}
|
|
1205
1207
|
// Skip the messages we've already processed.
|
|
1206
1208
|
i = j - 1;
|
|
1207
1209
|
params.push({
|
|
1208
1210
|
role: "user",
|
|
1209
|
-
content: toolResults,
|
|
1211
|
+
content: [...toolResults, ...errorImages],
|
|
1210
1212
|
});
|
|
1211
1213
|
}
|
|
1212
1214
|
}
|