@earendil-works/pi-ai 1.4.0 → 1.5.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 +35 -35
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +59 -71
- 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 +0 -6
- 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 +125 -103
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/constrained-sampling.d.ts +15 -1
- package/dist/api/constrained-sampling.d.ts.map +1 -1
- package/dist/api/constrained-sampling.js +51 -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 +0 -6
- 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 +57 -2
- 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 +0 -6
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +17 -44
- 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 +0 -5
- 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 +42 -54
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +58 -57
- 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 +0 -6
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/openrouter-images.js +1 -0
- 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 +24 -9
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +4 -11
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
- package/dist/auth/oauth/kimi-coding.js +4 -22
- package/dist/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/auth/oauth/meta.d.ts.map +1 -1
- package/dist/auth/oauth/meta.js +1 -14
- package/dist/auth/oauth/meta.js.map +1 -1
- package/dist/auth/oauth/radius.d.ts.map +1 -1
- package/dist/auth/oauth/radius.js +9 -3
- package/dist/auth/oauth/radius.js.map +1 -1
- package/dist/auth/oauth/trusted-url.d.ts +7 -0
- package/dist/auth/oauth/trusted-url.d.ts.map +1 -0
- package/dist/auth/oauth/trusted-url.js +12 -0
- package/dist/auth/oauth/trusted-url.js.map +1 -0
- 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/cli.js +6 -2
- package/dist/cli.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 +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.js +1 -1
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +1 -0
- package/dist/models.js.map +1 -1
- package/dist/providers/amazon-bedrock.models.js +1 -1
- package/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/dist/providers/ant-ling.models.js +1 -1
- package/dist/providers/ant-ling.models.js.map +1 -1
- package/dist/providers/anthropic.models.js +1 -1
- package/dist/providers/anthropic.models.js.map +1 -1
- package/dist/providers/azure.models.js +1 -1
- package/dist/providers/azure.models.js.map +1 -1
- package/dist/providers/baseten.models.js +1 -1
- package/dist/providers/baseten.models.js.map +1 -1
- package/dist/providers/cerebras.models.js +1 -1
- package/dist/providers/cerebras.models.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/dist/providers/cloudflare-workers-ai.models.js +1 -1
- package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +428 -456
- package/dist/providers/compat-schema.d.ts.map +1 -1
- package/dist/providers/compat-schema.js +144 -111
- 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/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/huggingface.json +1 -1
- package/dist/providers/data/kimi-coding.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-individual.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/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/data/zai.json +1 -1
- package/dist/providers/deepseek.models.js +1 -1
- package/dist/providers/deepseek.models.js.map +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +7 -2
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/fireworks.models.js +1 -1
- package/dist/providers/fireworks.models.js.map +1 -1
- package/dist/providers/github-copilot.models.js +1 -1
- package/dist/providers/github-copilot.models.js.map +1 -1
- package/dist/providers/google-vertex.models.js +1 -1
- package/dist/providers/google-vertex.models.js.map +1 -1
- package/dist/providers/google.models.js +1 -1
- package/dist/providers/google.models.js.map +1 -1
- package/dist/providers/groq.models.js +1 -1
- package/dist/providers/groq.models.js.map +1 -1
- package/dist/providers/huggingface.models.js +1 -1
- package/dist/providers/huggingface.models.js.map +1 -1
- package/dist/providers/kimi-coding.models.js +1 -1
- package/dist/providers/kimi-coding.models.js.map +1 -1
- package/dist/providers/meta.models.js +1 -1
- package/dist/providers/meta.models.js.map +1 -1
- package/dist/providers/minimax-cn.models.js +1 -1
- package/dist/providers/minimax-cn.models.js.map +1 -1
- package/dist/providers/minimax.models.js +1 -1
- package/dist/providers/minimax.models.js.map +1 -1
- package/dist/providers/mistral.models.js +1 -1
- package/dist/providers/mistral.models.js.map +1 -1
- package/dist/providers/model-schema.d.ts +65 -65
- package/dist/providers/model-schema.d.ts.map +1 -1
- package/dist/providers/model-schema.js +41 -30
- package/dist/providers/model-schema.js.map +1 -1
- package/dist/providers/moonshotai-cn.models.js +1 -1
- package/dist/providers/moonshotai-cn.models.js.map +1 -1
- package/dist/providers/moonshotai.models.js +1 -1
- package/dist/providers/moonshotai.models.js.map +1 -1
- package/dist/providers/nvidia.models.js +1 -1
- package/dist/providers/nvidia.models.js.map +1 -1
- package/dist/providers/openai-codex.models.js +1 -1
- package/dist/providers/openai-codex.models.js.map +1 -1
- package/dist/providers/openai.models.js +1 -1
- package/dist/providers/openai.models.js.map +1 -1
- package/dist/providers/opencode-go.models.js +1 -1
- package/dist/providers/opencode-go.models.js.map +1 -1
- package/dist/providers/opencode.js +1 -1
- package/dist/providers/opencode.js.map +1 -1
- package/dist/providers/opencode.models.js +1 -1
- package/dist/providers/opencode.models.js.map +1 -1
- package/dist/providers/openrouter.models.js +1 -1
- package/dist/providers/openrouter.models.js.map +1 -1
- package/dist/providers/qwen-token-plan-cn.models.js +1 -1
- package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
- package/dist/providers/qwen-token-plan-individual.models.js +1 -1
- package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
- package/dist/providers/qwen-token-plan.models.js +1 -1
- package/dist/providers/qwen-token-plan.models.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.models.js +1 -1
- package/dist/providers/radius.models.js.map +1 -1
- package/dist/providers/schema-builders.d.ts +10 -0
- package/dist/providers/schema-builders.d.ts.map +1 -0
- package/dist/providers/schema-builders.js +18 -0
- package/dist/providers/schema-builders.js.map +1 -0
- package/dist/providers/together.models.js +1 -1
- package/dist/providers/together.models.js.map +1 -1
- package/dist/providers/typesafe.models.js +1 -1
- package/dist/providers/typesafe.models.js.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.js +1 -1
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/dist/providers/xai.models.js +1 -1
- package/dist/providers/xai.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-ams.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-cn.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-sgp.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/dist/providers/xiaomi.models.js +1 -1
- package/dist/providers/xiaomi.models.js.map +1 -1
- package/dist/providers/zai-coding-cn.models.js +1 -1
- package/dist/providers/zai-coding-cn.models.js.map +1 -1
- package/dist/providers/zai.models.js +1 -1
- package/dist/providers/zai.models.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +2 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +12 -2
- 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/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.map +1 -1
- package/dist/utils/overflow.js +2 -0
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +3 -0
- 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/{typebox-helpers.d.ts → string-enum.d.ts} +5 -5
- package/dist/utils/string-enum.d.ts.map +1 -0
- package/dist/utils/{typebox-helpers.js → string-enum.js} +5 -6
- package/dist/utils/string-enum.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 +1 -1
- package/dist/utils/transcript.js +1 -1
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validation.d.ts +5 -3
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +27 -277
- package/dist/utils/validation.js.map +1 -1
- 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 +11 -10
- package/dist/utils/typebox-helpers.d.ts.map +0 -1
- package/dist/utils/typebox-helpers.js.map +0 -1
package/README.md
CHANGED
|
@@ -103,15 +103,16 @@ Unified LLM API with provider collections, automatic auth resolution, token and
|
|
|
103
103
|
npm install @earendil-works/pi-ai
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Tool parameters are JSON Schema. The examples build them with `@earendil-works/pi-schema`, which also types the arguments (`npm install @earendil-works/pi-schema`); TypeBox schemas and plain JSON Schema objects work as well.
|
|
107
107
|
|
|
108
108
|
## Quick Start
|
|
109
109
|
|
|
110
110
|
You build a `Models` collection of providers and stream through it. The quickest start registers every built-in provider; apps that care about bundle size register individual providers instead (see [Provider Factories](#provider-factories) and [Bundling and Tree Shaking](#bundling-and-tree-shaking)).
|
|
111
111
|
|
|
112
112
|
```typescript
|
|
113
|
-
import {
|
|
113
|
+
import { type Context, type Tool } from '@earendil-works/pi-ai';
|
|
114
114
|
import { builtinModels } from '@earendil-works/pi-ai/providers/all';
|
|
115
|
+
import { obj, opt, str } from '@earendil-works/pi-schema';
|
|
115
116
|
|
|
116
117
|
// A Models collection with every built-in provider registered
|
|
117
118
|
const models = builtinModels();
|
|
@@ -119,12 +120,12 @@ const models = builtinModels();
|
|
|
119
120
|
// Sync lookup against the collection
|
|
120
121
|
const model = models.getModel('openai', 'gpt-4o-mini')!;
|
|
121
122
|
|
|
122
|
-
// Define tools with
|
|
123
|
+
// Define tools with JSON Schema; pi-schema's builders also type the arguments
|
|
123
124
|
const tools: Tool[] = [{
|
|
124
125
|
name: 'get_time',
|
|
125
126
|
description: 'Get the current time',
|
|
126
|
-
parameters:
|
|
127
|
-
timezone:
|
|
127
|
+
parameters: obj({
|
|
128
|
+
timezone: opt(str({ description: 'Optional timezone (e.g., America/New_York)' }))
|
|
128
129
|
})
|
|
129
130
|
}];
|
|
130
131
|
|
|
@@ -233,8 +234,12 @@ for (const block of response.content) {
|
|
|
233
234
|
}
|
|
234
235
|
```
|
|
235
236
|
|
|
237
|
+
`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`.
|
|
238
|
+
|
|
236
239
|
Snippets in the rest of this README assume a `models` collection set up like this (with the relevant providers registered).
|
|
237
240
|
|
|
241
|
+
`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.
|
|
242
|
+
|
|
238
243
|
## Providers and Models
|
|
239
244
|
|
|
240
245
|
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.
|
|
@@ -534,34 +539,35 @@ Amazon Bedrock resolves ambient AWS credentials (`AWS_PROFILE`, access key pairs
|
|
|
534
539
|
|
|
535
540
|
## Tools
|
|
536
541
|
|
|
537
|
-
Tools enable LLMs to interact with external systems.
|
|
542
|
+
Tools enable LLMs to interact with external systems. A tool's `parameters` is a JSON Schema object: built with `@earendil-works/pi-schema`'s builders (which also type the arguments through its `Static`), with TypeBox, or written by hand. `validateToolCall()` and `validateToolArguments()` validate with pi-schema, coercing loosely typed arguments first (numeric strings, `"true"`, a single value for an array, `null` for an omitted optional property). Schemas are plain JSON, so they serialize with the rest of a context.
|
|
538
543
|
|
|
539
544
|
### Defining Tools
|
|
540
545
|
|
|
541
546
|
```typescript
|
|
542
|
-
import {
|
|
547
|
+
import { type Tool, StringEnum } from '@earendil-works/pi-ai';
|
|
548
|
+
import { arr, obj, str } from '@earendil-works/pi-schema';
|
|
543
549
|
|
|
544
|
-
// Define tool parameters with
|
|
550
|
+
// Define tool parameters with pi-schema's builders
|
|
545
551
|
const weatherTool: Tool = {
|
|
546
552
|
name: 'get_weather',
|
|
547
553
|
description: 'Get current weather for a location',
|
|
548
|
-
parameters:
|
|
549
|
-
location:
|
|
554
|
+
parameters: obj({
|
|
555
|
+
location: str({ description: 'City name or coordinates' }),
|
|
550
556
|
units: StringEnum(['celsius', 'fahrenheit'], { default: 'celsius' })
|
|
551
557
|
})
|
|
552
558
|
};
|
|
553
559
|
|
|
554
|
-
// Note:
|
|
555
|
-
//
|
|
560
|
+
// Note: StringEnum gives { type: "string", enum: [...] }, which every provider accepts,
|
|
561
|
+
// including Google's, which rejects anyOf/const patterns
|
|
556
562
|
|
|
557
563
|
const bookMeetingTool: Tool = {
|
|
558
564
|
name: 'book_meeting',
|
|
559
565
|
description: 'Schedule a meeting',
|
|
560
|
-
parameters:
|
|
561
|
-
title:
|
|
562
|
-
startTime:
|
|
563
|
-
endTime:
|
|
564
|
-
attendees:
|
|
566
|
+
parameters: obj({
|
|
567
|
+
title: str({ minLength: 1 }),
|
|
568
|
+
startTime: str({ format: 'date-time' }),
|
|
569
|
+
endTime: str({ format: 'date-time' }),
|
|
570
|
+
attendees: arr(str({ format: 'email' }), { minItems: 1 })
|
|
565
571
|
})
|
|
566
572
|
};
|
|
567
573
|
```
|
|
@@ -574,10 +580,7 @@ Tools can opt in to provider-side constrained sampling. For JSON-schema tools, `
|
|
|
574
580
|
const strictTool: Tool = {
|
|
575
581
|
name: 'edit_file',
|
|
576
582
|
description: 'Edit a file',
|
|
577
|
-
parameters:
|
|
578
|
-
path: Type.String(),
|
|
579
|
-
content: Type.String()
|
|
580
|
-
}, { additionalProperties: false }),
|
|
583
|
+
parameters: obj({ path: str(), content: str() }), // obj() is closed: additionalProperties: false
|
|
581
584
|
constrainedSampling: { type: 'json_schema', strict: 'prefer' }
|
|
582
585
|
};
|
|
583
586
|
```
|
|
@@ -588,9 +591,7 @@ Strict JSON-schema constrained sampling is supported for OpenAI, Anthropic, supp
|
|
|
588
591
|
const patchTool: Tool = {
|
|
589
592
|
name: 'apply_patch',
|
|
590
593
|
description: 'Apply a patch',
|
|
591
|
-
parameters:
|
|
592
|
-
input: Type.String()
|
|
593
|
-
}, { additionalProperties: false }),
|
|
594
|
+
parameters: obj({ input: str() }),
|
|
594
595
|
constrainedSampling: {
|
|
595
596
|
type: 'grammar',
|
|
596
597
|
variants: {
|
|
@@ -694,7 +695,7 @@ for await (const event of s) {
|
|
|
694
695
|
|
|
695
696
|
### Validating Tool Arguments
|
|
696
697
|
|
|
697
|
-
When implementing your own tool execution loop, use `validateToolCall` to validate arguments before passing them to your tools:
|
|
698
|
+
When implementing your own tool execution loop, use `validateToolCall` to validate arguments before passing them to your tools. It coerces loosely typed arguments with pi-schema (`compile(schema, { coerce: true }).parse()`), returns the coerced arguments, and throws an error listing pi-schema's errors (`path: message`) followed by the arguments it received:
|
|
698
699
|
|
|
699
700
|
```typescript
|
|
700
701
|
import { validateToolCall, type Tool } from '@earendil-works/pi-ai';
|
|
@@ -1722,8 +1723,8 @@ models.setProvider(openaiProvider());
|
|
|
1722
1723
|
|
|
1723
1724
|
Rules:
|
|
1724
1725
|
|
|
1725
|
-
- `@earendil-works/pi-ai/models` exports the model runtime (`createModels`, `createProvider`, model helpers, and their types) without
|
|
1726
|
-
- `@earendil-works/pi-ai` is the core entrypoint and does not import built-in catalogs, real provider factories, or SDK implementations, but it eagerly imports
|
|
1726
|
+
- `@earendil-works/pi-ai/models` exports the model runtime (`createModels`, `createProvider`, model helpers, and their types) without schema validation, built-in catalogs, or SDK implementations. Other types can still use `import type` from the root.
|
|
1727
|
+
- `@earendil-works/pi-ai` is the core entrypoint and does not import built-in catalogs, real provider factories, or SDK implementations, but it eagerly imports schema validation (pi-schema). Unbundled Node scripts do not tree-shake its unused exports; prefer `./models`, `./providers/faux`, and specific `./utils/*` subpaths when those are all you need.
|
|
1727
1728
|
- `@earendil-works/pi-ai/providers/<provider>` imports that provider's catalog and lazy API wrapper only.
|
|
1728
1729
|
- `@earendil-works/pi-ai/providers/all` imports every built-in provider factory and all catalogs. Use it only when you want the full built-in set.
|
|
1729
1730
|
- With code splitting, provider SDKs stay in lazy chunks and load on first request.
|
|
@@ -1924,19 +1925,19 @@ Create a new API implementation file (for example `bedrock-converse-stream.ts`)
|
|
|
1924
1925
|
|
|
1925
1926
|
Add a lazy wrapper `src/api/<api-id>.lazy.ts` (`<name>Api()` via `lazyApi()`) so providers can reference the implementation without importing its SDK. Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@earendil-works/pi-ai`.
|
|
1926
1927
|
|
|
1927
|
-
#### 3. Model
|
|
1928
|
+
#### 3. Model Catalog (`catalog/providers/<id>.ts`)
|
|
1928
1929
|
|
|
1929
|
-
- Add
|
|
1930
|
+
- Add a provider definition and register it in `catalog/providers/index.ts`; see `catalog/README.md` for the build order and helpers
|
|
1930
1931
|
- Map chat/tool-capable provider data to `Model`, image-generation data to `ImageModel`, and models.dev `type: "decision"` entries to `ClassifierModel`; hydration groups the ignored `src/providers/data/<id>.json` values by API while stable `src/providers/<id>.models.ts` wrappers derive exact model/API types directly from those JSON keys
|
|
1931
1932
|
- Keep model ids unique within each provider and model type; emit separate entries when an upstream model supports multiple operations
|
|
1932
|
-
- Handle provider-specific quirks (pricing format, capability flags, model ID transformations)
|
|
1933
|
+
- Handle provider-specific quirks (pricing format, capability flags, model ID transformations) in the provider definition
|
|
1933
1934
|
|
|
1934
1935
|
#### 4. Provider Factory (`src/providers/<id>.ts`)
|
|
1935
1936
|
|
|
1936
1937
|
- `createProvider()` wiring catalog + auth + the lazy API wrapper
|
|
1937
1938
|
- Auth: `envApiKeyAuth` for standard key providers, a custom `ApiKeyAuth` for ambient auth (AWS profiles, ADC), `lazyOAuth` where an OAuth flow exists
|
|
1938
1939
|
- Register the factory in `src/providers/all.ts`
|
|
1939
|
-
- If it is a new API: register it in the builtin list in `src/compat.ts
|
|
1940
|
+
- 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
|
|
1940
1941
|
|
|
1941
1942
|
#### 5. Tests (`test/`)
|
|
1942
1943
|
|
|
@@ -1947,7 +1948,6 @@ Create or update test files to cover the new provider:
|
|
|
1947
1948
|
- `abort.test.ts` - Request cancellation
|
|
1948
1949
|
- `empty.test.ts` - Empty message handling
|
|
1949
1950
|
- `context-overflow.test.ts` - Context limit errors
|
|
1950
|
-
- `image-limits.test.ts` - Image support (if applicable)
|
|
1951
1951
|
- `unicode-surrogate.test.ts` - Unicode handling
|
|
1952
1952
|
- `tool-call-without-result.test.ts` - Orphaned tool calls
|
|
1953
1953
|
- `image-tool-result.test.ts` - Images in tool results
|
|
@@ -1963,15 +1963,15 @@ For providers with non-standard auth (AWS, Google Vertex), create a utility like
|
|
|
1963
1963
|
|
|
1964
1964
|
Update `src/core/model-resolver.ts`:
|
|
1965
1965
|
|
|
1966
|
-
- Add a default model ID for the provider in `
|
|
1966
|
+
- Add a default model ID for the provider in `defaultModelPerProvider`
|
|
1967
1967
|
|
|
1968
1968
|
Update `src/cli/args.ts`:
|
|
1969
1969
|
|
|
1970
1970
|
- Add environment variable documentation in the help text
|
|
1971
1971
|
|
|
1972
|
-
Update `
|
|
1972
|
+
Update `docs/providers.md`:
|
|
1973
1973
|
|
|
1974
|
-
- Add
|
|
1974
|
+
- Add setup instructions, the environment variable, and the `auth.json` key
|
|
1975
1975
|
|
|
1976
1976
|
#### 7. Documentation
|
|
1977
1977
|
|
|
@@ -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;AA+JrB,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,CA8VzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
|
|
@@ -5,13 +5,15 @@ import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
|
|
|
5
5
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
6
6
|
import { headersToRecord } from "../utils/headers.js";
|
|
7
7
|
import { getIdentityHeaders } from "../utils/identity.js";
|
|
8
|
-
import { parseJsonWithRepair
|
|
8
|
+
import { parseJsonWithRepair } from "../utils/json-parse.js";
|
|
9
|
+
import { applyOpenRouterReportedCost, isOpenRouterModel, } from "../utils/openrouter-cost.js";
|
|
9
10
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
10
11
|
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
11
12
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
13
|
+
import { ToolCallArgumentsStream } from "../utils/streaming-json.js";
|
|
12
14
|
import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
|
|
13
15
|
import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "../utils/transcript.js";
|
|
14
|
-
import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
16
|
+
import { getJsonSchemaToolParameters, isStrictToolLimitErrorMessage, prefersJsonSchemaStrictSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
15
17
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
16
18
|
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
|
|
17
19
|
import { splitErrorToolResultImages, transformMessages } from "./transform-messages.js";
|
|
@@ -456,11 +458,7 @@ export const stream = (model, context, options) => {
|
|
|
456
458
|
!(error instanceof Anthropic.APIError) ||
|
|
457
459
|
error.status !== 400 ||
|
|
458
460
|
error.type !== "invalid_request_error" ||
|
|
459
|
-
!
|
|
460
|
-
"The compiled grammar is too large",
|
|
461
|
-
"Too many strict tools (",
|
|
462
|
-
"Schemas contains too many parameters with union types (",
|
|
463
|
-
].some((message) => error.message.includes(message))) {
|
|
461
|
+
!isStrictToolLimitErrorMessage(error.message)) {
|
|
464
462
|
throw error;
|
|
465
463
|
}
|
|
466
464
|
disablePreferredStrictTools = true;
|
|
@@ -474,7 +472,12 @@ export const stream = (model, context, options) => {
|
|
|
474
472
|
}
|
|
475
473
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
476
474
|
stream.push({ type: "start", partial: output });
|
|
477
|
-
|
|
475
|
+
// Stream state per Anthropic block index, kept off the content blocks so it never reaches partials.
|
|
476
|
+
const openBlocks = new Map();
|
|
477
|
+
const openBlock = (anthropicIndex) => {
|
|
478
|
+
const open = openBlocks.get(anthropicIndex);
|
|
479
|
+
return open === undefined ? undefined : { ...open, block: output.content[open.contentIndex] };
|
|
480
|
+
};
|
|
478
481
|
for await (const event of iterateAnthropicEvents(response, options?.signal)) {
|
|
479
482
|
await options?.onProviderStreamEvent?.(event, model);
|
|
480
483
|
if (event.type === "message_start") {
|
|
@@ -509,105 +512,92 @@ export const stream = (model, context, options) => {
|
|
|
509
512
|
continue;
|
|
510
513
|
}
|
|
511
514
|
if (event.content_block.type === "text") {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
text: event.content_block.text ?? "",
|
|
515
|
-
index: event.index,
|
|
516
|
-
};
|
|
517
|
-
output.content.push(block);
|
|
515
|
+
output.content.push({ type: "text", text: event.content_block.text ?? "" });
|
|
516
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
518
517
|
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
|
|
519
518
|
}
|
|
520
519
|
else if (event.content_block.type === "thinking") {
|
|
521
|
-
|
|
520
|
+
output.content.push({
|
|
522
521
|
type: "thinking",
|
|
523
522
|
thinking: event.content_block.thinking ?? "",
|
|
524
523
|
thinkingSignature: event.content_block.signature ?? "",
|
|
525
|
-
|
|
526
|
-
};
|
|
527
|
-
output.content.push(block);
|
|
524
|
+
});
|
|
525
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
528
526
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
529
527
|
}
|
|
530
528
|
else if (event.content_block.type === "redacted_thinking") {
|
|
531
|
-
|
|
529
|
+
output.content.push({
|
|
532
530
|
type: "thinking",
|
|
533
531
|
thinking: "[Reasoning redacted]",
|
|
534
532
|
thinkingSignature: event.content_block.data,
|
|
535
533
|
redacted: true,
|
|
536
|
-
|
|
537
|
-
};
|
|
538
|
-
output.content.push(block);
|
|
534
|
+
});
|
|
535
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
539
536
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
540
537
|
}
|
|
541
538
|
else if (event.content_block.type === "tool_use") {
|
|
542
|
-
|
|
539
|
+
output.content.push({
|
|
543
540
|
type: "toolCall",
|
|
544
541
|
id: event.content_block.id,
|
|
545
542
|
name: isOAuth
|
|
546
543
|
? fromClaudeCodeName(event.content_block.name, currentTools)
|
|
547
544
|
: event.content_block.name,
|
|
548
545
|
arguments: event.content_block.input ?? {},
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
546
|
+
});
|
|
547
|
+
openBlocks.set(event.index, {
|
|
548
|
+
contentIndex: output.content.length - 1,
|
|
549
|
+
arguments: new ToolCallArgumentsStream(),
|
|
550
|
+
});
|
|
553
551
|
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
|
|
554
552
|
}
|
|
555
553
|
}
|
|
556
554
|
else if (event.type === "content_block_delta") {
|
|
555
|
+
const open = openBlock(event.index);
|
|
557
556
|
if (event.delta.type === "text_delta") {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (block && block.type === "text") {
|
|
561
|
-
block.text += event.delta.text;
|
|
557
|
+
if (open?.block.type === "text") {
|
|
558
|
+
open.block.text += event.delta.text;
|
|
562
559
|
stream.push({
|
|
563
560
|
type: "text_delta",
|
|
564
|
-
contentIndex:
|
|
561
|
+
contentIndex: open.contentIndex,
|
|
565
562
|
delta: event.delta.text,
|
|
566
563
|
partial: output,
|
|
567
564
|
});
|
|
568
565
|
}
|
|
569
566
|
}
|
|
570
567
|
else if (event.delta.type === "thinking_delta") {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (block && block.type === "thinking") {
|
|
574
|
-
block.thinking += event.delta.thinking;
|
|
568
|
+
if (open?.block.type === "thinking") {
|
|
569
|
+
open.block.thinking += event.delta.thinking;
|
|
575
570
|
stream.push({
|
|
576
571
|
type: "thinking_delta",
|
|
577
|
-
contentIndex:
|
|
572
|
+
contentIndex: open.contentIndex,
|
|
578
573
|
delta: event.delta.thinking,
|
|
579
574
|
partial: output,
|
|
580
575
|
});
|
|
581
576
|
}
|
|
582
577
|
}
|
|
583
578
|
else if (event.delta.type === "input_json_delta") {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (block && block.type === "toolCall") {
|
|
587
|
-
block.partialJson += event.delta.partial_json;
|
|
588
|
-
block.arguments = parseStreamingJson(block.partialJson);
|
|
579
|
+
if (open?.block.type === "toolCall" && open.arguments !== undefined) {
|
|
580
|
+
open.block.arguments = open.arguments.append(event.delta.partial_json);
|
|
589
581
|
stream.push({
|
|
590
582
|
type: "toolcall_delta",
|
|
591
|
-
contentIndex:
|
|
583
|
+
contentIndex: open.contentIndex,
|
|
592
584
|
delta: event.delta.partial_json,
|
|
593
585
|
partial: output,
|
|
594
586
|
});
|
|
595
587
|
}
|
|
596
588
|
}
|
|
597
589
|
else if (event.delta.type === "signature_delta") {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
block.thinkingSignature = block.thinkingSignature || "";
|
|
602
|
-
block.thinkingSignature += event.delta.signature;
|
|
590
|
+
if (open?.block.type === "thinking") {
|
|
591
|
+
open.block.thinkingSignature = open.block.thinkingSignature || "";
|
|
592
|
+
open.block.thinkingSignature += event.delta.signature;
|
|
603
593
|
}
|
|
604
594
|
}
|
|
605
595
|
}
|
|
606
596
|
else if (event.type === "content_block_stop") {
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
|
|
597
|
+
const open = openBlock(event.index);
|
|
598
|
+
openBlocks.delete(event.index);
|
|
599
|
+
if (open) {
|
|
600
|
+
const { block, contentIndex: index } = open;
|
|
611
601
|
if (block.type === "text") {
|
|
612
602
|
stream.push({
|
|
613
603
|
type: "text_end",
|
|
@@ -624,11 +614,8 @@ export const stream = (model, context, options) => {
|
|
|
624
614
|
partial: output,
|
|
625
615
|
});
|
|
626
616
|
}
|
|
627
|
-
else if (block.type === "toolCall") {
|
|
628
|
-
Object.assign(block,
|
|
629
|
-
// Finalize in-place and strip the scratch buffer so replay only
|
|
630
|
-
// carries parsed arguments.
|
|
631
|
-
delete block.partialJson;
|
|
617
|
+
else if (block.type === "toolCall" && open.arguments !== undefined) {
|
|
618
|
+
Object.assign(block, open.arguments.finish());
|
|
632
619
|
stream.push({
|
|
633
620
|
type: "toolcall_end",
|
|
634
621
|
contentIndex: index,
|
|
@@ -680,6 +667,11 @@ export const stream = (model, context, options) => {
|
|
|
680
667
|
output.usage.totalTokens =
|
|
681
668
|
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
682
669
|
calculateCost(usageModel, output.usage);
|
|
670
|
+
if (isOpenRouterModel(model) && event.usage) {
|
|
671
|
+
// OpenRouter adds billing fields to streamed message_delta usage at runtime,
|
|
672
|
+
// although its OpenAPI schema and the Anthropic SDK do not type those fields.
|
|
673
|
+
applyOpenRouterReportedCost(output.usage, event.usage);
|
|
674
|
+
}
|
|
683
675
|
}
|
|
684
676
|
}
|
|
685
677
|
if (options?.signal?.aborted) {
|
|
@@ -708,11 +700,6 @@ export const stream = (model, context, options) => {
|
|
|
708
700
|
stream.end();
|
|
709
701
|
}
|
|
710
702
|
catch (error) {
|
|
711
|
-
for (const block of output.content) {
|
|
712
|
-
delete block.index;
|
|
713
|
-
// partialJson is only a streaming scratch buffer; never persist it.
|
|
714
|
-
delete block.partialJson;
|
|
715
|
-
}
|
|
716
703
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
717
704
|
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
718
705
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
@@ -901,10 +888,7 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
901
888
|
let hasPreferredStrictTools = false;
|
|
902
889
|
const convertToolDefinitions = (tools, toolCacheControl) => {
|
|
903
890
|
const definitions = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl, disablePreferredStrictTools);
|
|
904
|
-
if (definitions.some((definition, index) => {
|
|
905
|
-
const config = tools[index].constrainedSampling;
|
|
906
|
-
return definition.strict === true && config && config.type === "json_schema" && config.strict === "prefer";
|
|
907
|
-
})) {
|
|
891
|
+
if (definitions.some((definition, index) => definition.strict === true && prefersJsonSchemaStrictSampling(tools[index]))) {
|
|
908
892
|
hasPreferredStrictTools = true;
|
|
909
893
|
}
|
|
910
894
|
return definitions;
|
|
@@ -985,7 +969,10 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
985
969
|
display: options?.thinkingDisplay ?? "summarized",
|
|
986
970
|
block_binding: { prefix_mismatch_behavior: "drop_block" },
|
|
987
971
|
};
|
|
988
|
-
|
|
972
|
+
// Effort markers set the level for every reply. The top-level effort is part of the cached
|
|
973
|
+
// prompt, so it stays fixed. It is "low" because organizations can cap effort and reject
|
|
974
|
+
// requests containing any level above the cap.
|
|
975
|
+
params.output_config = { effort: "low" };
|
|
989
976
|
}
|
|
990
977
|
else if (model.reasoning && options?.thinkingEnabled) {
|
|
991
978
|
// Default to "summarized" so Opus 4.7 and Mythos Preview behave like
|
|
@@ -1045,7 +1032,9 @@ convertToolDefinitions) {
|
|
|
1045
1032
|
const params = [];
|
|
1046
1033
|
const assistantLevels = new Map();
|
|
1047
1034
|
// Later system messages are held back and emitted directly before the next assistant
|
|
1048
|
-
// message (or at the end of the transcript).
|
|
1035
|
+
// message that is sent (or at the end of the transcript). Assistant messages that convert to
|
|
1036
|
+
// no blocks are dropped, so holding past them keeps an update from landing before a user
|
|
1037
|
+
// message (#11091). Anthropic requires `tool_result` blocks to
|
|
1049
1038
|
// immediately follow their `tool_use`, so a system message between them is rejected; this
|
|
1050
1039
|
// also mirrors where the managed-effort system messages are inserted. As a result an
|
|
1051
1040
|
// update placed before a user message in the transcript lands after it on the wire.
|
|
@@ -1118,7 +1107,6 @@ convertToolDefinitions) {
|
|
|
1118
1107
|
}
|
|
1119
1108
|
}
|
|
1120
1109
|
else if (msg.role === "assistant") {
|
|
1121
|
-
flushPendingSystemMessages();
|
|
1122
1110
|
const blocks = [];
|
|
1123
1111
|
for (const block of msg.content) {
|
|
1124
1112
|
if (block.type === "text") {
|
|
@@ -1176,6 +1164,7 @@ convertToolDefinitions) {
|
|
|
1176
1164
|
}
|
|
1177
1165
|
if (blocks.length === 0)
|
|
1178
1166
|
continue;
|
|
1167
|
+
flushPendingSystemMessages();
|
|
1179
1168
|
const messageIndex = params.length;
|
|
1180
1169
|
params.push({
|
|
1181
1170
|
role: "assistant",
|
|
@@ -1299,8 +1288,7 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
|
|
|
1299
1288
|
if (!tools)
|
|
1300
1289
|
return [];
|
|
1301
1290
|
return tools.map((tool, index) => {
|
|
1302
|
-
const
|
|
1303
|
-
const strict = disablePreferredStrictTools && config && config.type === "json_schema" && config.strict === "prefer"
|
|
1291
|
+
const strict = disablePreferredStrictTools && prefersJsonSchemaStrictSampling(tool)
|
|
1304
1292
|
? undefined
|
|
1305
1293
|
: resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
|
|
1306
1294
|
const parameters = getJsonSchemaToolParameters(tool, strict);
|