phi-code-ai 0.56.4 → 0.74.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 +258 -73
- package/dist/api-registry.d.ts.map +1 -1
- package/dist/api-registry.js.map +1 -1
- package/dist/bedrock-provider.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts +9 -0
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +96 -30
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.d.ts +10 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.generated.d.ts +305 -0
- package/dist/image-models.generated.d.ts.map +1 -0
- package/dist/image-models.generated.js +307 -0
- package/dist/image-models.generated.js.map +1 -0
- package/dist/image-models.js +23 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +4 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +20 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +3 -9
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6525 -2231
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +8992 -5524
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +28 -12
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +23 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +206 -44
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +23 -2
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +294 -63
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +47 -23
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +13 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +26 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +56 -0
- package/dist/providers/faux.d.ts.map +1 -0
- package/dist/providers/faux.js +368 -0
- package/dist/providers/faux.js.map +1 -0
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +7 -2
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +53 -24
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +1 -1
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +87 -16
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +57 -9
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts +3 -0
- package/dist/providers/images/openrouter.d.ts.map +1 -0
- package/dist/providers/images/openrouter.js +129 -0
- package/dist/providers/images/openrouter.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/mistral.d.ts +3 -0
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +49 -9
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +21 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +443 -86
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +5 -1
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +459 -225
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +1 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +95 -45
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +66 -44
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/register-builtins.d.ts +27 -2
- package/dist/providers/register-builtins.d.ts.map +1 -1
- package/dist/providers/register-builtins.js +157 -52
- package/dist/providers/register-builtins.js.map +1 -1
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +5 -1
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +63 -34
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +219 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +7 -3
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/hash.d.ts.map +1 -1
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +8 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +89 -5
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/oauth/anthropic.d.ts +14 -6
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +288 -57
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +23 -12
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +0 -4
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +0 -10
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/oauth-page.d.ts +3 -0
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth/oauth-page.js +105 -0
- package/dist/utils/oauth/oauth-page.js.map +1 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +51 -46
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/pkce.d.ts.map +1 -1
- package/dist/utils/oauth/pkce.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +10 -0
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/overflow.d.ts +7 -3
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +46 -13
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/sanitize-unicode.d.ts.map +1 -1
- package/dist/utils/sanitize-unicode.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +1 -1
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +247 -38
- package/dist/utils/validation.js.map +1 -1
- package/package.json +43 -13
- package/bedrock-provider.d.ts +0 -1
- package/bedrock-provider.js +0 -1
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -754
- package/dist/providers/google-gemini-cli.js.map +0 -1
- package/dist/utils/oauth/google-antigravity.d.ts +0 -26
- package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
- package/dist/utils/oauth/google-antigravity.js +0 -373
- package/dist/utils/oauth/google-antigravity.js.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.js +0 -478
- package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @earendil-works/pi-ai
|
|
2
2
|
|
|
3
3
|
Unified LLM API with automatic model discovery, provider configuration, token and cost tracking, and simple context persistence and hand-off to other models mid-session.
|
|
4
4
|
|
|
@@ -16,6 +16,9 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
16
16
|
- [Validating Tool Arguments](#validating-tool-arguments)
|
|
17
17
|
- [Complete Event Reference](#complete-event-reference)
|
|
18
18
|
- [Image Input](#image-input)
|
|
19
|
+
- [Image Generation](#image-generation)
|
|
20
|
+
- [Basic Image Generation](#basic-image-generation)
|
|
21
|
+
- [Notes and Limitations](#notes-and-limitations)
|
|
19
22
|
- [Thinking/Reasoning](#thinkingreasoning)
|
|
20
23
|
- [Unified Interface](#unified-interface-streamsimplecompletesimple)
|
|
21
24
|
- [Provider-Specific Options](#provider-specific-options-streamcomplete)
|
|
@@ -37,7 +40,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
37
40
|
- [Environment Variables](#environment-variables-nodejs-only)
|
|
38
41
|
- [Checking Environment Variables](#checking-environment-variables)
|
|
39
42
|
- [OAuth Providers](#oauth-providers)
|
|
40
|
-
- [Vertex AI
|
|
43
|
+
- [Vertex AI](#vertex-ai)
|
|
41
44
|
- [CLI Login](#cli-login)
|
|
42
45
|
- [Programmatic OAuth](#programmatic-oauth)
|
|
43
46
|
- [Login Flow Example](#login-flow-example)
|
|
@@ -50,37 +53,41 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
50
53
|
- **OpenAI**
|
|
51
54
|
- **Azure OpenAI (Responses)**
|
|
52
55
|
- **OpenAI Codex** (ChatGPT Plus/Pro subscription, requires OAuth, see below)
|
|
56
|
+
- **DeepSeek**
|
|
53
57
|
- **Anthropic**
|
|
54
58
|
- **Google**
|
|
55
59
|
- **Vertex AI** (Gemini via Vertex AI)
|
|
56
60
|
- **Mistral**
|
|
57
61
|
- **Groq**
|
|
58
62
|
- **Cerebras**
|
|
63
|
+
- **Cloudflare AI Gateway**
|
|
64
|
+
- **Cloudflare Workers AI**
|
|
59
65
|
- **xAI**
|
|
60
66
|
- **OpenRouter**
|
|
61
67
|
- **Vercel AI Gateway**
|
|
62
68
|
- **MiniMax**
|
|
69
|
+
- **Together AI**
|
|
63
70
|
- **GitHub Copilot** (requires OAuth, see below)
|
|
64
|
-
- **Google Gemini CLI** (requires OAuth, see below)
|
|
65
|
-
- **Antigravity** (requires OAuth, see below)
|
|
66
71
|
- **Amazon Bedrock**
|
|
67
72
|
- **OpenCode Zen**
|
|
68
73
|
- **OpenCode Go**
|
|
74
|
+
- **Fireworks** (uses Anthropic-compatible API)
|
|
69
75
|
- **Kimi For Coding** (Moonshot AI, uses Anthropic-compatible API)
|
|
76
|
+
- **Xiaomi MiMo** (uses Anthropic-compatible API; defaults to API billing endpoint, with separate Token Plan providers for `cn`/`ams`/`sgp` regions)
|
|
70
77
|
- **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc.
|
|
71
78
|
|
|
72
79
|
## Installation
|
|
73
80
|
|
|
74
81
|
```bash
|
|
75
|
-
npm install @
|
|
82
|
+
npm install @earendil-works/pi-ai
|
|
76
83
|
```
|
|
77
84
|
|
|
78
|
-
TypeBox exports are re-exported from `@
|
|
85
|
+
TypeBox exports are re-exported from `@earendil-works/pi-ai`: `Type`, `Static`, and `TSchema`.
|
|
79
86
|
|
|
80
87
|
## Quick Start
|
|
81
88
|
|
|
82
89
|
```typescript
|
|
83
|
-
import { Type, getModel, stream, complete, Context, Tool, StringEnum } from '@
|
|
90
|
+
import { Type, getModel, stream, complete, Context, Tool, StringEnum } from '@earendil-works/pi-ai';
|
|
84
91
|
|
|
85
92
|
// Fully typed with auto-complete support for both providers and models
|
|
86
93
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
@@ -201,12 +208,12 @@ for (const block of response.content) {
|
|
|
201
208
|
|
|
202
209
|
## Tools
|
|
203
210
|
|
|
204
|
-
Tools enable LLMs to interact with external systems. This library uses TypeBox schemas for type-safe tool definitions with automatic validation using
|
|
211
|
+
Tools enable LLMs to interact with external systems. This library uses TypeBox schemas for type-safe tool definitions with automatic validation using TypeBox's built-in validator and value conversion utilities. TypeBox schemas can be serialized and deserialized as plain JSON, making them ideal for distributed systems.
|
|
205
212
|
|
|
206
213
|
### Defining Tools
|
|
207
214
|
|
|
208
215
|
```typescript
|
|
209
|
-
import { Type, Tool, StringEnum } from '@
|
|
216
|
+
import { Type, Tool, StringEnum } from '@earendil-works/pi-ai';
|
|
210
217
|
|
|
211
218
|
// Define tool parameters with TypeBox
|
|
212
219
|
const weatherTool: Tool = {
|
|
@@ -332,7 +339,7 @@ When using `agentLoop`, tool arguments are automatically validated against your
|
|
|
332
339
|
When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
|
|
333
340
|
|
|
334
341
|
```typescript
|
|
335
|
-
import { stream, validateToolCall, Tool } from '@
|
|
342
|
+
import { stream, validateToolCall, Tool } from '@earendil-works/pi-ai';
|
|
336
343
|
|
|
337
344
|
const tools: Tool[] = [weatherTool, calculatorTool];
|
|
338
345
|
const s = stream(model, { messages, tools });
|
|
@@ -380,13 +387,15 @@ All streaming events emitted during assistant message generation:
|
|
|
380
387
|
| `done` | Stream complete | `reason`: Stop reason ("stop", "length", "toolUse"), `message`: Final assistant message |
|
|
381
388
|
| `error` | Error occurred | `reason`: Error type ("error" or "aborted"), `error`: AssistantMessage with partial content |
|
|
382
389
|
|
|
390
|
+
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.
|
|
391
|
+
|
|
383
392
|
## Image Input
|
|
384
393
|
|
|
385
394
|
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.
|
|
386
395
|
|
|
387
396
|
```typescript
|
|
388
397
|
import { readFileSync } from 'fs';
|
|
389
|
-
import { getModel, complete } from '@
|
|
398
|
+
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
390
399
|
|
|
391
400
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
392
401
|
|
|
@@ -416,6 +425,70 @@ for (const block of response.content) {
|
|
|
416
425
|
}
|
|
417
426
|
```
|
|
418
427
|
|
|
428
|
+
## Image Generation
|
|
429
|
+
|
|
430
|
+
Image generation uses a separate API surface from text/chat generation. Use `getImageModel()` / `getImageModels()` / `getImageProviders()` to discover image-generation models, and `generateImages()` to get the final result.
|
|
431
|
+
|
|
432
|
+
Do not use `stream()` or `complete()` for image generation. Image generation is a one-shot API: `generateImages()` waits for the provider response and returns the final `AssistantImages` result.
|
|
433
|
+
|
|
434
|
+
### Basic Image Generation
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import { getImageModel, generateImages } from '@mariozechner/pi-ai';
|
|
438
|
+
|
|
439
|
+
const model = getImageModel('openrouter', 'google/gemini-2.5-flash-image');
|
|
440
|
+
|
|
441
|
+
const result = await generateImages(model, {
|
|
442
|
+
input: [{ type: 'text', text: 'Generate a red circle on a plain white background.' }]
|
|
443
|
+
}, {
|
|
444
|
+
apiKey: process.env.OPENROUTER_API_KEY
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
for (const block of result.output) {
|
|
448
|
+
if (block.type === 'text') {
|
|
449
|
+
console.log(block.text);
|
|
450
|
+
} else if (block.type === 'image') {
|
|
451
|
+
console.log(block.mimeType);
|
|
452
|
+
console.log(block.data.substring(0, 32));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Some models also support image input:
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import { readFileSync } from 'fs';
|
|
461
|
+
|
|
462
|
+
const imageBuffer = readFileSync('input.png');
|
|
463
|
+
const result = await generateImages(model, {
|
|
464
|
+
input: [
|
|
465
|
+
{ type: 'text', text: 'Create a variation of this image with a blue background.' },
|
|
466
|
+
{ type: 'image', data: imageBuffer.toString('base64'), mimeType: 'image/png' }
|
|
467
|
+
]
|
|
468
|
+
}, {
|
|
469
|
+
apiKey: process.env.OPENROUTER_API_KEY
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Check capabilities on the model metadata:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
console.log(model.input); // ['text', 'image']
|
|
477
|
+
console.log(model.output); // ['image'] or ['image', 'text']
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Notes and Limitations
|
|
481
|
+
|
|
482
|
+
- Use `getImageModel(...)`, not `getModel(...)`.
|
|
483
|
+
- Use `generateImages()`, not `stream()` / `complete()`.
|
|
484
|
+
- Image-generation models do not participate in tool calling.
|
|
485
|
+
- Outputs are returned in `AssistantImages.output` and can include both base64-encoded `ImageContent` blocks and `TextContent` blocks.
|
|
486
|
+
- Some models return only images, others return images plus text. Check `model.output`.
|
|
487
|
+
- Some models accept image input, others are text-to-image only. Check `model.input`.
|
|
488
|
+
- Like the streaming APIs, image generation supports options such as `apiKey`, `signal`, `headers`, `onPayload`, and `onResponse`, and results may include `stopReason`, `responseId`, and `usage`.
|
|
489
|
+
- If you want a model to analyze images in a conversation or call tools, use the regular `stream()` / `complete()` APIs with a model that supports image input.
|
|
490
|
+
- At the moment, image generation is available through only one provider, OpenRouter.
|
|
491
|
+
|
|
419
492
|
## Thinking/Reasoning
|
|
420
493
|
|
|
421
494
|
Many models support thinking/reasoning capabilities where they can show their internal thought process. You can check if a model supports reasoning via the `reasoning` property. If you pass reasoning options to a non-reasoning model, they are silently ignored.
|
|
@@ -423,7 +496,7 @@ Many models support thinking/reasoning capabilities where they can show their in
|
|
|
423
496
|
### Unified Interface (streamSimple/completeSimple)
|
|
424
497
|
|
|
425
498
|
```typescript
|
|
426
|
-
import { getModel, streamSimple, completeSimple } from '@
|
|
499
|
+
import { getModel, streamSimple, completeSimple } from '@earendil-works/pi-ai';
|
|
427
500
|
|
|
428
501
|
// Many models across providers support thinking/reasoning
|
|
429
502
|
const model = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
@@ -443,7 +516,7 @@ if (model.reasoning) {
|
|
|
443
516
|
const response = await completeSimple(model, {
|
|
444
517
|
messages: [{ role: 'user', content: 'Solve: 2x + 5 = 13' }]
|
|
445
518
|
}, {
|
|
446
|
-
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
519
|
+
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
447
520
|
});
|
|
448
521
|
|
|
449
522
|
// Access thinking and text blocks
|
|
@@ -461,7 +534,7 @@ for (const block of response.content) {
|
|
|
461
534
|
For fine-grained control, use the provider-specific options:
|
|
462
535
|
|
|
463
536
|
```typescript
|
|
464
|
-
import { getModel, complete } from '@
|
|
537
|
+
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
465
538
|
|
|
466
539
|
// OpenAI Reasoning (o1, o3, gpt-5)
|
|
467
540
|
const openaiModel = getModel('openai', 'gpt-5-mini');
|
|
@@ -519,6 +592,8 @@ Every `AssistantMessage` includes a `stopReason` field that indicates how the ge
|
|
|
519
592
|
- `"error"` - An error occurred during generation
|
|
520
593
|
- `"aborted"` - Request was cancelled via abort signal
|
|
521
594
|
|
|
595
|
+
`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.
|
|
596
|
+
|
|
522
597
|
## Error Handling
|
|
523
598
|
|
|
524
599
|
When a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event:
|
|
@@ -548,7 +623,7 @@ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
|
548
623
|
The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
|
|
549
624
|
|
|
550
625
|
```typescript
|
|
551
|
-
import { getModel, stream } from '@
|
|
626
|
+
import { getModel, stream } from '@earendil-works/pi-ai';
|
|
552
627
|
|
|
553
628
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
554
629
|
const controller = new AbortController();
|
|
@@ -625,7 +700,6 @@ The library uses a registry of API implementations. Built-in APIs include:
|
|
|
625
700
|
|
|
626
701
|
- **`anthropic-messages`**: Anthropic Messages API (`streamAnthropic`, `AnthropicOptions`)
|
|
627
702
|
- **`google-generative-ai`**: Google Generative AI API (`streamGoogle`, `GoogleOptions`)
|
|
628
|
-
- **`google-gemini-cli`**: Google Cloud Code Assist API (`streamGoogleGeminiCli`, `GoogleGeminiCliOptions`)
|
|
629
703
|
- **`google-vertex`**: Google Vertex AI API (`streamGoogleVertex`, `GoogleVertexOptions`)
|
|
630
704
|
- **`mistral-conversations`**: Mistral Conversations API (`streamMistral`, `MistralOptions`)
|
|
631
705
|
- **`openai-completions`**: OpenAI Chat Completions API (`streamOpenAICompletions`, `OpenAICompletionsOptions`)
|
|
@@ -634,6 +708,92 @@ The library uses a registry of API implementations. Built-in APIs include:
|
|
|
634
708
|
- **`azure-openai-responses`**: Azure OpenAI Responses API (`streamAzureOpenAIResponses`, `AzureOpenAIResponsesOptions`)
|
|
635
709
|
- **`bedrock-converse-stream`**: Amazon Bedrock Converse API (`streamBedrock`, `BedrockOptions`)
|
|
636
710
|
|
|
711
|
+
### Faux provider for tests
|
|
712
|
+
|
|
713
|
+
`registerFauxProvider()` registers a temporary in-memory provider for tests and demos. It is opt-in and not part of the built-in provider set.
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
import {
|
|
717
|
+
complete,
|
|
718
|
+
fauxAssistantMessage,
|
|
719
|
+
fauxText,
|
|
720
|
+
fauxThinking,
|
|
721
|
+
fauxToolCall,
|
|
722
|
+
registerFauxProvider,
|
|
723
|
+
stream,
|
|
724
|
+
} from '@earendil-works/pi-ai';
|
|
725
|
+
|
|
726
|
+
const registration = registerFauxProvider({
|
|
727
|
+
tokensPerSecond: 50 // optional
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const model = registration.getModel();
|
|
731
|
+
const context = {
|
|
732
|
+
messages: [{ role: 'user', content: 'Summarize package.json and then call echo', timestamp: Date.now() }]
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
registration.setResponses([
|
|
736
|
+
fauxAssistantMessage([
|
|
737
|
+
fauxThinking('Need to inspect package metadata first.'),
|
|
738
|
+
fauxToolCall('echo', { text: 'package.json' })
|
|
739
|
+
], { stopReason: 'toolUse' })
|
|
740
|
+
]);
|
|
741
|
+
|
|
742
|
+
const first = await complete(model, context, {
|
|
743
|
+
sessionId: 'session-1',
|
|
744
|
+
cacheRetention: 'short'
|
|
745
|
+
});
|
|
746
|
+
context.messages.push(first);
|
|
747
|
+
|
|
748
|
+
context.messages.push({
|
|
749
|
+
role: 'toolResult',
|
|
750
|
+
toolCallId: first.content.find((block) => block.type === 'toolCall')!.id,
|
|
751
|
+
toolName: 'echo',
|
|
752
|
+
content: [{ type: 'text', text: 'package.json contents here' }],
|
|
753
|
+
isError: false,
|
|
754
|
+
timestamp: Date.now()
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
registration.setResponses([
|
|
758
|
+
fauxAssistantMessage([
|
|
759
|
+
fauxThinking('Now I can summarize the tool output.'),
|
|
760
|
+
fauxText('Here is the summary.')
|
|
761
|
+
])
|
|
762
|
+
]);
|
|
763
|
+
|
|
764
|
+
const s = stream(model, context);
|
|
765
|
+
for await (const event of s) {
|
|
766
|
+
console.log(event.type);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Optional: register multiple faux models for model-switching tests
|
|
770
|
+
const multiModel = registerFauxProvider({
|
|
771
|
+
models: [
|
|
772
|
+
{ id: 'faux-fast', reasoning: false },
|
|
773
|
+
{ id: 'faux-thinker', reasoning: true }
|
|
774
|
+
]
|
|
775
|
+
});
|
|
776
|
+
const thinker = multiModel.getModel('faux-thinker');
|
|
777
|
+
|
|
778
|
+
console.log(thinker?.reasoning);
|
|
779
|
+
console.log(registration.getPendingResponseCount());
|
|
780
|
+
console.log(registration.state.callCount);
|
|
781
|
+
registration.unregister();
|
|
782
|
+
multiModel.unregister();
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Notes:
|
|
786
|
+
- Responses are consumed from a queue in request start order.
|
|
787
|
+
- If the queue is empty, the faux provider returns an assistant error message with `errorMessage: "No more faux responses queued"`.
|
|
788
|
+
- Use `registration.setResponses([...])` to replace the remaining queue and `registration.appendResponses([...])` to add more responses.
|
|
789
|
+
- `registration.models` exposes all registered faux models. `registration.getModel()` returns the first one, and `registration.getModel(id)` returns a specific one.
|
|
790
|
+
- Use `fauxAssistantMessage(...)` for scripted assistant replies. Use `fauxText(...)`, `fauxThinking(...)`, and `fauxToolCall(...)` to build content blocks without filling in low-level fields manually.
|
|
791
|
+
- `registration.unregister()` removes the temporary provider from the global API registry.
|
|
792
|
+
- Usage is estimated at roughly 1 token per 4 characters. When `sessionId` is present and `cacheRetention` is not `"none"`, prompt cache reads and writes are simulated automatically.
|
|
793
|
+
- Tool call arguments stream incrementally via `toolcall_delta` chunks.
|
|
794
|
+
- By default, each streamed chunk is emitted on its own microtask. Set `tokensPerSecond` to pace chunk delivery in real time.
|
|
795
|
+
- The intended use is one deterministic scripted flow per registration. If you need independent concurrent flows, register separate faux providers.
|
|
796
|
+
|
|
637
797
|
### Providers and Models
|
|
638
798
|
|
|
639
799
|
A **provider** offers models through a specific API. For example:
|
|
@@ -641,12 +801,12 @@ A **provider** offers models through a specific API. For example:
|
|
|
641
801
|
- **Google** models use the `google-generative-ai` API
|
|
642
802
|
- **OpenAI** models use the `openai-responses` API
|
|
643
803
|
- **Mistral** models use the `mistral-conversations` API
|
|
644
|
-
- **xAI, Cerebras, Groq, etc.** models use the `openai-completions` API (OpenAI-compatible)
|
|
804
|
+
- **xAI, Cerebras, Groq, Together AI, etc.** models use the `openai-completions` API (OpenAI-compatible)
|
|
645
805
|
|
|
646
806
|
### Querying Providers and Models
|
|
647
807
|
|
|
648
808
|
```typescript
|
|
649
|
-
import { getProviders, getModels, getModel } from '@
|
|
809
|
+
import { getProviders, getModels, getModel } from '@earendil-works/pi-ai';
|
|
650
810
|
|
|
651
811
|
// Get all available providers
|
|
652
812
|
const providers = getProviders();
|
|
@@ -672,7 +832,7 @@ console.log(`Using ${model.name} via ${model.api} API`);
|
|
|
672
832
|
You can create custom models for local inference servers or custom endpoints:
|
|
673
833
|
|
|
674
834
|
```typescript
|
|
675
|
-
import { Model, stream } from '@
|
|
835
|
+
import { Model, stream } from '@earendil-works/pi-ai';
|
|
676
836
|
|
|
677
837
|
// Example: Ollama using OpenAI-compatible API
|
|
678
838
|
const ollamaModel: Model<'openai-completions'> = {
|
|
@@ -729,9 +889,41 @@ const response = await stream(ollamaModel, context, {
|
|
|
729
889
|
});
|
|
730
890
|
```
|
|
731
891
|
|
|
892
|
+
Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so the system prompt is sent as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too.
|
|
893
|
+
|
|
894
|
+
Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`). Missing keys use provider defaults, string values are sent to the provider, and `null` marks a level unsupported.
|
|
895
|
+
|
|
896
|
+
This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers. You can set `compat` at the provider level or per model.
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
const ollamaReasoningModel: Model<'openai-completions'> = {
|
|
900
|
+
id: 'gpt-oss:20b',
|
|
901
|
+
name: 'GPT-OSS 20B (Ollama)',
|
|
902
|
+
api: 'openai-completions',
|
|
903
|
+
provider: 'ollama',
|
|
904
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
905
|
+
reasoning: true,
|
|
906
|
+
input: ['text'],
|
|
907
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
908
|
+
contextWindow: 131072,
|
|
909
|
+
maxTokens: 32000,
|
|
910
|
+
thinkingLevelMap: {
|
|
911
|
+
minimal: null,
|
|
912
|
+
low: null,
|
|
913
|
+
medium: null,
|
|
914
|
+
high: 'high',
|
|
915
|
+
xhigh: null,
|
|
916
|
+
},
|
|
917
|
+
compat: {
|
|
918
|
+
supportsDeveloperRole: false,
|
|
919
|
+
supportsReasoningEffort: false,
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
```
|
|
923
|
+
|
|
732
924
|
### OpenAI Compatibility Settings
|
|
733
925
|
|
|
734
|
-
The `openai-completions` API is implemented by many providers with minor differences. By default, the library auto-detects compatibility settings based on `baseUrl` for a small set of known OpenAI-compatible providers (Cerebras, xAI, Chutes, DeepSeek, zAi, OpenCode, etc.). For custom proxies or unknown endpoints, you can override these settings via the `compat` field. For `openai-responses` models, the compat field only supports Responses-specific flags.
|
|
926
|
+
The `openai-completions` API is implemented by many providers with minor differences. By default, the library auto-detects compatibility settings based on `baseUrl` for a small set of known OpenAI-compatible providers (Cerebras, xAI, Chutes, DeepSeek, Together AI, zAi, OpenCode, Cloudflare Workers AI, etc.). For custom proxies or unknown endpoints, you can override these settings via the `compat` field. For `openai-responses` models, the compat field only supports Responses-specific flags.
|
|
735
927
|
|
|
736
928
|
```typescript
|
|
737
929
|
interface OpenAICompletionsCompat {
|
|
@@ -740,11 +932,14 @@ interface OpenAICompletionsCompat {
|
|
|
740
932
|
supportsReasoningEffort?: boolean; // Whether provider supports `reasoning_effort` (default: true)
|
|
741
933
|
supportsUsageInStreaming?: boolean; // Whether provider supports `stream_options: { include_usage: true }` (default: true)
|
|
742
934
|
supportsStrictMode?: boolean; // Whether provider supports `strict` in tool definitions (default: true)
|
|
935
|
+
sendSessionAffinityHeaders?: boolean; // Whether to send `session_id`, `x-client-request-id`, and `x-session-affinity` from `sessionId` when caching is enabled (default: false)
|
|
743
936
|
maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
|
|
744
937
|
requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
|
|
745
938
|
requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
|
|
746
939
|
requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
|
|
747
|
-
|
|
940
|
+
requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
|
|
941
|
+
thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'qwen-chat-template'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'zai' uses enable_thinking, 'qwen' uses enable_thinking, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking (default: openai)
|
|
942
|
+
cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content
|
|
748
943
|
openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {})
|
|
749
944
|
vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {})
|
|
750
945
|
}
|
|
@@ -765,7 +960,7 @@ If `compat` is not set, the library falls back to URL-based detection. If `compa
|
|
|
765
960
|
Models are typed by their API, which keeps the model metadata accurate. Provider-specific option types are enforced when you call the provider functions directly. The generic `stream` and `complete` functions accept `StreamOptions` with additional provider fields.
|
|
766
961
|
|
|
767
962
|
```typescript
|
|
768
|
-
import { streamAnthropic, type AnthropicOptions } from '@
|
|
963
|
+
import { streamAnthropic, type AnthropicOptions } from '@earendil-works/pi-ai';
|
|
769
964
|
|
|
770
965
|
// TypeScript knows this is an Anthropic model
|
|
771
966
|
const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
@@ -794,7 +989,7 @@ When messages from one provider are sent to a different provider, the library au
|
|
|
794
989
|
### Example: Multi-Provider Conversation
|
|
795
990
|
|
|
796
991
|
```typescript
|
|
797
|
-
import { getModel, complete, Context } from '@
|
|
992
|
+
import { getModel, complete, Context } from '@earendil-works/pi-ai';
|
|
798
993
|
|
|
799
994
|
// Start with Claude
|
|
800
995
|
const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
@@ -839,7 +1034,7 @@ This enables flexible workflows where you can:
|
|
|
839
1034
|
The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services:
|
|
840
1035
|
|
|
841
1036
|
```typescript
|
|
842
|
-
import { Context, getModel, complete } from '@
|
|
1037
|
+
import { Context, getModel, complete } from '@earendil-works/pi-ai';
|
|
843
1038
|
|
|
844
1039
|
// Create and use a context
|
|
845
1040
|
const context: Context = {
|
|
@@ -876,7 +1071,7 @@ const continuation = await complete(newModel, restored);
|
|
|
876
1071
|
The library supports browser environments. You must pass the API key explicitly since environment variables are not available in browsers:
|
|
877
1072
|
|
|
878
1073
|
```typescript
|
|
879
|
-
import { getModel, complete } from '@
|
|
1074
|
+
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
880
1075
|
|
|
881
1076
|
// API key must be passed explicitly in browser
|
|
882
1077
|
const model = getModel('anthropic', 'claude-3-5-haiku-20241022');
|
|
@@ -893,7 +1088,7 @@ const response = await complete(model, {
|
|
|
893
1088
|
### Browser Compatibility Notes
|
|
894
1089
|
|
|
895
1090
|
- Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments.
|
|
896
|
-
- OAuth login flows are not supported in browser environments. Use the `@
|
|
1091
|
+
- OAuth login flows are not supported in browser environments. Use the `@earendil-works/pi-ai/oauth` entry point in Node.js.
|
|
897
1092
|
- In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime.
|
|
898
1093
|
- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
|
|
899
1094
|
|
|
@@ -904,20 +1099,29 @@ In Node.js environments, you can set environment variables to avoid passing API
|
|
|
904
1099
|
| Provider | Environment Variable(s) |
|
|
905
1100
|
|----------|------------------------|
|
|
906
1101
|
| OpenAI | `OPENAI_API_KEY` |
|
|
907
|
-
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME`
|
|
1102
|
+
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.openai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com` and `*.cognitiveservices.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. |
|
|
908
1103
|
| Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
|
|
1104
|
+
| DeepSeek | `DEEPSEEK_API_KEY` |
|
|
909
1105
|
| Google | `GEMINI_API_KEY` |
|
|
910
|
-
| Vertex AI | `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
|
|
1106
|
+
| Vertex AI | `GOOGLE_CLOUD_API_KEY` or `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
|
|
911
1107
|
| Mistral | `MISTRAL_API_KEY` |
|
|
912
1108
|
| Groq | `GROQ_API_KEY` |
|
|
913
1109
|
| Cerebras | `CEREBRAS_API_KEY` |
|
|
1110
|
+
| Cloudflare AI Gateway | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_GATEWAY_ID` |
|
|
1111
|
+
| Cloudflare Workers AI | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` |
|
|
914
1112
|
| xAI | `XAI_API_KEY` |
|
|
1113
|
+
| Fireworks | `FIREWORKS_API_KEY` |
|
|
1114
|
+
| Together AI | `TOGETHER_API_KEY` |
|
|
915
1115
|
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
916
1116
|
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
|
|
917
1117
|
| zAI | `ZAI_API_KEY` |
|
|
918
1118
|
| MiniMax | `MINIMAX_API_KEY` |
|
|
919
1119
|
| OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
|
|
920
1120
|
| Kimi For Coding | `KIMI_API_KEY` |
|
|
1121
|
+
| Xiaomi MiMo (API billing) | `XIAOMI_API_KEY` |
|
|
1122
|
+
| Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` |
|
|
1123
|
+
| Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` |
|
|
1124
|
+
| Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` |
|
|
921
1125
|
| GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` |
|
|
922
1126
|
|
|
923
1127
|
When set, the library automatically uses these keys:
|
|
@@ -933,31 +1137,10 @@ const response = await complete(model, context, {
|
|
|
933
1137
|
});
|
|
934
1138
|
```
|
|
935
1139
|
|
|
936
|
-
#### Antigravity Version Override
|
|
937
|
-
|
|
938
|
-
Set `PI_AI_ANTIGRAVITY_VERSION` to override the Antigravity User-Agent version when Google updates their requirements:
|
|
939
|
-
|
|
940
|
-
```bash
|
|
941
|
-
export PI_AI_ANTIGRAVITY_VERSION="1.23.0"
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
#### Cache Retention
|
|
945
|
-
|
|
946
|
-
Set `PI_CACHE_RETENTION=long` to extend prompt cache retention:
|
|
947
|
-
|
|
948
|
-
| Provider | Default | With `PI_CACHE_RETENTION=long` |
|
|
949
|
-
|----------|---------|-------------------------------|
|
|
950
|
-
| Anthropic | 5 minutes | 1 hour |
|
|
951
|
-
| OpenAI | in-memory | 24 hours |
|
|
952
|
-
|
|
953
|
-
This only affects direct API calls to `api.anthropic.com` and `api.openai.com`. Proxies and other providers are unaffected.
|
|
954
|
-
|
|
955
|
-
> **Note**: Extended cache retention may increase costs for Anthropic (cache writes are charged at a higher rate). OpenAI's 24h retention has no additional cost.
|
|
956
|
-
|
|
957
1140
|
### Checking Environment Variables
|
|
958
1141
|
|
|
959
1142
|
```typescript
|
|
960
|
-
import { getEnvApiKey } from '@
|
|
1143
|
+
import { getEnvApiKey } from '@earendil-works/pi-ai';
|
|
961
1144
|
|
|
962
1145
|
// Check if an API key is set in environment variables
|
|
963
1146
|
const key = getEnvApiKey('openai'); // checks OPENAI_API_KEY
|
|
@@ -970,19 +1153,18 @@ Several providers require OAuth authentication instead of static API keys:
|
|
|
970
1153
|
- **Anthropic** (Claude Pro/Max subscription)
|
|
971
1154
|
- **OpenAI Codex** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
|
|
972
1155
|
- **GitHub Copilot** (Copilot subscription)
|
|
973
|
-
- **Google Gemini CLI** (Gemini 2.0/2.5 via Google Cloud Code Assist; free tier or paid subscription)
|
|
974
|
-
- **Antigravity** (Free Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
975
1156
|
|
|
976
1157
|
For paid Cloud Code Assist subscriptions, set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` to your project ID.
|
|
977
1158
|
|
|
978
|
-
### Vertex AI
|
|
1159
|
+
### Vertex AI
|
|
979
1160
|
|
|
980
|
-
Vertex AI models
|
|
1161
|
+
Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC):
|
|
981
1162
|
|
|
982
|
-
- **
|
|
983
|
-
- **
|
|
1163
|
+
- **API key**: Set `GOOGLE_CLOUD_API_KEY` or pass `apiKey` in the call options.
|
|
1164
|
+
- **Local development (ADC)**: Run `gcloud auth application-default login`
|
|
1165
|
+
- **CI/Production (ADC)**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
|
|
984
1166
|
|
|
985
|
-
|
|
1167
|
+
When using ADC, also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options. When using `GOOGLE_CLOUD_API_KEY`, `project` and `location` are not required.
|
|
986
1168
|
|
|
987
1169
|
Example:
|
|
988
1170
|
|
|
@@ -997,12 +1179,14 @@ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
|
|
|
997
1179
|
```
|
|
998
1180
|
|
|
999
1181
|
```typescript
|
|
1000
|
-
import { getModel, complete } from '@
|
|
1182
|
+
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
1001
1183
|
|
|
1002
1184
|
(async () => {
|
|
1003
1185
|
const model = getModel('google-vertex', 'gemini-2.5-flash');
|
|
1004
1186
|
const response = await complete(model, {
|
|
1005
1187
|
messages: [{ role: 'user', content: 'Hello from Vertex AI' }]
|
|
1188
|
+
}, {
|
|
1189
|
+
apiKey: process.env.GOOGLE_CLOUD_API_KEY,
|
|
1006
1190
|
});
|
|
1007
1191
|
|
|
1008
1192
|
for (const block of response.content) {
|
|
@@ -1018,16 +1202,16 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
|
|
|
1018
1202
|
The quickest way to authenticate:
|
|
1019
1203
|
|
|
1020
1204
|
```bash
|
|
1021
|
-
npx @
|
|
1022
|
-
npx @
|
|
1023
|
-
npx @
|
|
1205
|
+
npx @earendil-works/pi-ai login # interactive provider selection
|
|
1206
|
+
npx @earendil-works/pi-ai login anthropic # login to specific provider
|
|
1207
|
+
npx @earendil-works/pi-ai list # list available providers
|
|
1024
1208
|
```
|
|
1025
1209
|
|
|
1026
1210
|
Credentials are saved to `auth.json` in the current directory.
|
|
1027
1211
|
|
|
1028
1212
|
### Programmatic OAuth
|
|
1029
1213
|
|
|
1030
|
-
The library provides login and token refresh functions via the `@
|
|
1214
|
+
The library provides login and token refresh functions via the `@earendil-works/pi-ai/oauth` entry point. Credential storage is the caller's responsibility.
|
|
1031
1215
|
|
|
1032
1216
|
```typescript
|
|
1033
1217
|
import {
|
|
@@ -1036,22 +1220,21 @@ import {
|
|
|
1036
1220
|
loginOpenAICodex,
|
|
1037
1221
|
loginGitHubCopilot,
|
|
1038
1222
|
loginGeminiCli,
|
|
1039
|
-
loginAntigravity,
|
|
1040
1223
|
|
|
1041
1224
|
// Token management
|
|
1042
1225
|
refreshOAuthToken, // (provider, credentials) => new credentials
|
|
1043
1226
|
getOAuthApiKey, // (provider, credentialsMap) => { newCredentials, apiKey } | null
|
|
1044
1227
|
|
|
1045
1228
|
// Types
|
|
1046
|
-
type OAuthProvider,
|
|
1229
|
+
type OAuthProvider,
|
|
1047
1230
|
type OAuthCredentials,
|
|
1048
|
-
} from '@
|
|
1231
|
+
} from '@earendil-works/pi-ai/oauth';
|
|
1049
1232
|
```
|
|
1050
1233
|
|
|
1051
1234
|
### Login Flow Example
|
|
1052
1235
|
|
|
1053
1236
|
```typescript
|
|
1054
|
-
import { loginGitHubCopilot } from '@
|
|
1237
|
+
import { loginGitHubCopilot } from '@earendil-works/pi-ai/oauth';
|
|
1055
1238
|
import { writeFileSync } from 'fs';
|
|
1056
1239
|
|
|
1057
1240
|
const credentials = await loginGitHubCopilot({
|
|
@@ -1075,8 +1258,8 @@ writeFileSync('auth.json', JSON.stringify(auth, null, 2));
|
|
|
1075
1258
|
Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired:
|
|
1076
1259
|
|
|
1077
1260
|
```typescript
|
|
1078
|
-
import { getModel, complete } from '@
|
|
1079
|
-
import { getOAuthApiKey } from '@
|
|
1261
|
+
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
1262
|
+
import { getOAuthApiKey } from '@earendil-works/pi-ai/oauth';
|
|
1080
1263
|
import { readFileSync, writeFileSync } from 'fs';
|
|
1081
1264
|
|
|
1082
1265
|
// Load your stored credentials
|
|
@@ -1101,12 +1284,10 @@ const response = await complete(model, {
|
|
|
1101
1284
|
|
|
1102
1285
|
**OpenAI Codex**: Requires a ChatGPT Plus or Pro subscription. Provides access to GPT-5.x Codex models with extended context windows and reasoning capabilities. The library automatically handles session-based prompt caching when `sessionId` is provided in stream options. You can set `transport` in stream options to `"sse"`, `"websocket"`, or `"auto"` for Codex Responses transport selection. When using WebSocket with a `sessionId`, connections are reused per session and expire after 5 minutes of inactivity.
|
|
1103
1286
|
|
|
1104
|
-
**Azure OpenAI (Responses)**: Uses the Responses API only. Set `AZURE_OPENAI_API_KEY` and either `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME`. Use `AZURE_OPENAI_API_VERSION` (defaults to `v1`) to override the API version if needed. Deployment names are treated as model IDs by default, override with `azureDeploymentName` or `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` using comma-separated `model-id=deployment` pairs (for example `gpt-4o-mini=my-deployment,gpt-4o=prod`). Legacy deployment-based URLs are intentionally unsupported.
|
|
1287
|
+
**Azure OpenAI (Responses)**: Uses the Responses API only. Set `AZURE_OPENAI_API_KEY` and either `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME`. `AZURE_OPENAI_BASE_URL` supports both `https://<resource>.openai.azure.com` and `https://<resource>.cognitiveservices.azure.com`; root endpoints are normalized to `.../openai/v1` automatically. Use `AZURE_OPENAI_API_VERSION` (defaults to `v1`) to override the API version if needed. Deployment names are treated as model IDs by default, override with `azureDeploymentName` or `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` using comma-separated `model-id=deployment` pairs (for example `gpt-4o-mini=my-deployment,gpt-4o=prod`). Legacy deployment-based URLs are intentionally unsupported.
|
|
1105
1288
|
|
|
1106
1289
|
**GitHub Copilot**: If you get "The requested model is not supported" error, enable the model manually in VS Code: open Copilot Chat, click the model selector, select the model (warning icon), and click "Enable".
|
|
1107
1290
|
|
|
1108
|
-
**Google Gemini CLI / Antigravity**: These use Google Cloud OAuth. The `apiKey` returned by `getOAuthApiKey()` is a JSON string containing both the token and project ID, which the library handles automatically.
|
|
1109
|
-
|
|
1110
1291
|
## Development
|
|
1111
1292
|
|
|
1112
1293
|
### Adding a New Provider
|
|
@@ -1133,13 +1314,17 @@ Create a new provider file (for example `amazon-bedrock.ts`) that exports:
|
|
|
1133
1314
|
#### 3. API Registry Integration (`src/providers/register-builtins.ts`)
|
|
1134
1315
|
|
|
1135
1316
|
- Register the API with `registerApiProvider()`
|
|
1317
|
+
- Add a package subpath export in `package.json` for the provider module (`./dist/providers/<provider>.js`)
|
|
1318
|
+
- Add lazy loader wrappers in `src/providers/register-builtins.ts`, do not statically import provider implementation modules there
|
|
1319
|
+
- Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@earendil-works/pi-ai`
|
|
1136
1320
|
- Add credential detection in `env-api-keys.ts` for the new provider
|
|
1137
1321
|
- Ensure `streamSimple` handles auth lookup via `getEnvApiKey()` or provider-specific auth
|
|
1138
1322
|
|
|
1139
|
-
#### 4. Model Generation (`scripts/generate-models.ts`)
|
|
1323
|
+
#### 4. Model Generation (`scripts/generate-models.ts`, `scripts/generate-image-models.ts`)
|
|
1140
1324
|
|
|
1141
1325
|
- Add logic to fetch and parse models from the provider's source (e.g., models.dev API)
|
|
1142
|
-
- Map provider model data to the standardized `Model` interface
|
|
1326
|
+
- Map chat/tool-capable provider model data to the standardized `Model` interface via `scripts/generate-models.ts`
|
|
1327
|
+
- Map image-generation provider model data to the standardized `ImagesModel` interface via `scripts/generate-image-models.ts`
|
|
1143
1328
|
- Handle provider-specific quirks (pricing format, capability flags, model ID transformations)
|
|
1144
1329
|
|
|
1145
1330
|
#### 5. Tests (`test/`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-registry.d.ts","sourceRoot":"","sources":["../src/api-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,GAAG,EACH,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,iBAAiB,GAAG,CAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,aAAa,KACnB,2BAA2B,CAAC;AAEjC,MAAM,MAAM,uBAAuB,GAAG,CACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,KACzB,2BAA2B,CAAC;AAEjC,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,EAAE,QAAQ,SAAS,aAAa,GAAG,aAAa;IAClG,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvC,YAAY,EAAE,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;CACxD;AAED,UAAU,mBAAmB;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,uBAAuB,CAAC;CACtC;AAiCD,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,GAAG,EAAE,QAAQ,SAAS,aAAa,EACnF,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GACf,IAAI,CASN;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,mBAAmB,GAAG,SAAS,CAExE;AAED,wBAAgB,eAAe,IAAI,mBAAmB,EAAE,CAEvD;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM7D;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
1
|
+
{"version":3,"file":"api-registry.d.ts","sourceRoot":"","sources":["../src/api-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,GAAG,EACH,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,iBAAiB,GAAG,CAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,aAAa,KACnB,2BAA2B,CAAC;AAEjC,MAAM,MAAM,uBAAuB,GAAG,CACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,KACzB,2BAA2B,CAAC;AAEjC,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,EAAE,QAAQ,SAAS,aAAa,GAAG,aAAa;IAClG,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvC,YAAY,EAAE,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;CACxD;AAED,UAAU,mBAAmB;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,uBAAuB,CAAC;CACtC;AAiCD,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,GAAG,EAAE,QAAQ,SAAS,aAAa,EACnF,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GACf,IAAI,CASN;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,mBAAmB,GAAG,SAAS,CAExE;AAED,wBAAgB,eAAe,IAAI,mBAAmB,EAAE,CAEvD;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM7D;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC","sourcesContent":["import type {\n\tApi,\n\tAssistantMessageEventStream,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n} from \"./types.js\";\n\nexport type ApiStreamFunction = (\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions?: StreamOptions,\n) => AssistantMessageEventStream;\n\nexport type ApiStreamSimpleFunction = (\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n) => AssistantMessageEventStream;\n\nexport interface ApiProvider<TApi extends Api = Api, TOptions extends StreamOptions = StreamOptions> {\n\tapi: TApi;\n\tstream: StreamFunction<TApi, TOptions>;\n\tstreamSimple: StreamFunction<TApi, SimpleStreamOptions>;\n}\n\ninterface ApiProviderInternal {\n\tapi: Api;\n\tstream: ApiStreamFunction;\n\tstreamSimple: ApiStreamSimpleFunction;\n}\n\ntype RegisteredApiProvider = {\n\tprovider: ApiProviderInternal;\n\tsourceId?: string;\n};\n\nconst apiProviderRegistry = new Map<string, RegisteredApiProvider>();\n\nfunction wrapStream<TApi extends Api, TOptions extends StreamOptions>(\n\tapi: TApi,\n\tstream: StreamFunction<TApi, TOptions>,\n): ApiStreamFunction {\n\treturn (model, context, options) => {\n\t\tif (model.api !== api) {\n\t\t\tthrow new Error(`Mismatched api: ${model.api} expected ${api}`);\n\t\t}\n\t\treturn stream(model as Model<TApi>, context, options as TOptions);\n\t};\n}\n\nfunction wrapStreamSimple<TApi extends Api>(\n\tapi: TApi,\n\tstreamSimple: StreamFunction<TApi, SimpleStreamOptions>,\n): ApiStreamSimpleFunction {\n\treturn (model, context, options) => {\n\t\tif (model.api !== api) {\n\t\t\tthrow new Error(`Mismatched api: ${model.api} expected ${api}`);\n\t\t}\n\t\treturn streamSimple(model as Model<TApi>, context, options);\n\t};\n}\n\nexport function registerApiProvider<TApi extends Api, TOptions extends StreamOptions>(\n\tprovider: ApiProvider<TApi, TOptions>,\n\tsourceId?: string,\n): void {\n\tapiProviderRegistry.set(provider.api, {\n\t\tprovider: {\n\t\t\tapi: provider.api,\n\t\t\tstream: wrapStream(provider.api, provider.stream),\n\t\t\tstreamSimple: wrapStreamSimple(provider.api, provider.streamSimple),\n\t\t},\n\t\tsourceId,\n\t});\n}\n\nexport function getApiProvider(api: Api): ApiProviderInternal | undefined {\n\treturn apiProviderRegistry.get(api)?.provider;\n}\n\nexport function getApiProviders(): ApiProviderInternal[] {\n\treturn Array.from(apiProviderRegistry.values(), (entry) => entry.provider);\n}\n\nexport function unregisterApiProviders(sourceId: string): void {\n\tfor (const [api, entry] of apiProviderRegistry.entries()) {\n\t\tif (entry.sourceId === sourceId) {\n\t\t\tapiProviderRegistry.delete(api);\n\t\t}\n\t}\n}\n\nexport function clearApiProviders(): void {\n\tapiProviderRegistry.clear();\n}\n"]}
|