phi-code-ai 0.56.3 → 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.
Files changed (187) hide show
  1. package/README.md +258 -73
  2. package/dist/api-registry.d.ts.map +1 -1
  3. package/dist/api-registry.js.map +1 -1
  4. package/dist/bedrock-provider.d.ts.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/env-api-keys.d.ts +9 -0
  9. package/dist/env-api-keys.d.ts.map +1 -1
  10. package/dist/env-api-keys.js +96 -30
  11. package/dist/env-api-keys.js.map +1 -1
  12. package/dist/image-models.d.ts +10 -0
  13. package/dist/image-models.d.ts.map +1 -0
  14. package/dist/image-models.generated.d.ts +305 -0
  15. package/dist/image-models.generated.d.ts.map +1 -0
  16. package/dist/image-models.generated.js +307 -0
  17. package/dist/image-models.generated.js.map +1 -0
  18. package/dist/image-models.js +23 -0
  19. package/dist/image-models.js.map +1 -0
  20. package/dist/images-api-registry.d.ts +14 -0
  21. package/dist/images-api-registry.d.ts.map +1 -0
  22. package/dist/images-api-registry.js +22 -0
  23. package/dist/images-api-registry.js.map +1 -0
  24. package/dist/images.d.ts +4 -0
  25. package/dist/images.d.ts.map +1 -0
  26. package/dist/images.js +14 -0
  27. package/dist/images.js.map +1 -0
  28. package/dist/index.d.ts +20 -11
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/models.d.ts +3 -9
  33. package/dist/models.d.ts.map +1 -1
  34. package/dist/models.generated.d.ts +6525 -2231
  35. package/dist/models.generated.d.ts.map +1 -1
  36. package/dist/models.generated.js +8992 -5524
  37. package/dist/models.generated.js.map +1 -1
  38. package/dist/models.js +28 -12
  39. package/dist/models.js.map +1 -1
  40. package/dist/oauth.d.ts.map +1 -1
  41. package/dist/providers/amazon-bedrock.d.ts +23 -0
  42. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  43. package/dist/providers/amazon-bedrock.js +206 -44
  44. package/dist/providers/amazon-bedrock.js.map +1 -1
  45. package/dist/providers/anthropic.d.ts +23 -2
  46. package/dist/providers/anthropic.d.ts.map +1 -1
  47. package/dist/providers/anthropic.js +294 -63
  48. package/dist/providers/anthropic.js.map +1 -1
  49. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  50. package/dist/providers/azure-openai-responses.js +47 -23
  51. package/dist/providers/azure-openai-responses.js.map +1 -1
  52. package/dist/providers/cloudflare.d.ts +13 -0
  53. package/dist/providers/cloudflare.d.ts.map +1 -0
  54. package/dist/providers/cloudflare.js +26 -0
  55. package/dist/providers/cloudflare.js.map +1 -0
  56. package/dist/providers/faux.d.ts +56 -0
  57. package/dist/providers/faux.d.ts.map +1 -0
  58. package/dist/providers/faux.js +368 -0
  59. package/dist/providers/faux.js.map +1 -0
  60. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  61. package/dist/providers/github-copilot-headers.js.map +1 -1
  62. package/dist/providers/google-shared.d.ts +7 -2
  63. package/dist/providers/google-shared.d.ts.map +1 -1
  64. package/dist/providers/google-shared.js +53 -24
  65. package/dist/providers/google-shared.js.map +1 -1
  66. package/dist/providers/google-vertex.d.ts +1 -1
  67. package/dist/providers/google-vertex.d.ts.map +1 -1
  68. package/dist/providers/google-vertex.js +87 -16
  69. package/dist/providers/google-vertex.js.map +1 -1
  70. package/dist/providers/google.d.ts +1 -1
  71. package/dist/providers/google.d.ts.map +1 -1
  72. package/dist/providers/google.js +57 -9
  73. package/dist/providers/google.js.map +1 -1
  74. package/dist/providers/images/openrouter.d.ts +3 -0
  75. package/dist/providers/images/openrouter.d.ts.map +1 -0
  76. package/dist/providers/images/openrouter.js +129 -0
  77. package/dist/providers/images/openrouter.js.map +1 -0
  78. package/dist/providers/images/register-builtins.d.ts +4 -0
  79. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  80. package/dist/providers/images/register-builtins.js +34 -0
  81. package/dist/providers/images/register-builtins.js.map +1 -0
  82. package/dist/providers/mistral.d.ts +3 -0
  83. package/dist/providers/mistral.d.ts.map +1 -1
  84. package/dist/providers/mistral.js +49 -9
  85. package/dist/providers/mistral.js.map +1 -1
  86. package/dist/providers/openai-codex-responses.d.ts +21 -0
  87. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  88. package/dist/providers/openai-codex-responses.js +443 -86
  89. package/dist/providers/openai-codex-responses.js.map +1 -1
  90. package/dist/providers/openai-completions.d.ts +5 -1
  91. package/dist/providers/openai-completions.d.ts.map +1 -1
  92. package/dist/providers/openai-completions.js +460 -225
  93. package/dist/providers/openai-completions.js.map +1 -1
  94. package/dist/providers/openai-responses-shared.d.ts +1 -0
  95. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  96. package/dist/providers/openai-responses-shared.js +95 -45
  97. package/dist/providers/openai-responses-shared.js.map +1 -1
  98. package/dist/providers/openai-responses.d.ts.map +1 -1
  99. package/dist/providers/openai-responses.js +66 -44
  100. package/dist/providers/openai-responses.js.map +1 -1
  101. package/dist/providers/register-builtins.d.ts +27 -2
  102. package/dist/providers/register-builtins.d.ts.map +1 -1
  103. package/dist/providers/register-builtins.js +157 -52
  104. package/dist/providers/register-builtins.js.map +1 -1
  105. package/dist/providers/simple-options.d.ts.map +1 -1
  106. package/dist/providers/simple-options.js +5 -1
  107. package/dist/providers/simple-options.js.map +1 -1
  108. package/dist/providers/transform-messages.d.ts.map +1 -1
  109. package/dist/providers/transform-messages.js +63 -34
  110. package/dist/providers/transform-messages.js.map +1 -1
  111. package/dist/session-resources.d.ts +4 -0
  112. package/dist/session-resources.d.ts.map +1 -0
  113. package/dist/session-resources.js +22 -0
  114. package/dist/session-resources.js.map +1 -0
  115. package/dist/stream.d.ts.map +1 -1
  116. package/dist/stream.js.map +1 -1
  117. package/dist/types.d.ts +219 -15
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/utils/diagnostics.d.ts +19 -0
  121. package/dist/utils/diagnostics.d.ts.map +1 -0
  122. package/dist/utils/diagnostics.js +25 -0
  123. package/dist/utils/diagnostics.js.map +1 -0
  124. package/dist/utils/event-stream.d.ts.map +1 -1
  125. package/dist/utils/event-stream.js +7 -3
  126. package/dist/utils/event-stream.js.map +1 -1
  127. package/dist/utils/hash.d.ts.map +1 -1
  128. package/dist/utils/hash.js.map +1 -1
  129. package/dist/utils/headers.d.ts +2 -0
  130. package/dist/utils/headers.d.ts.map +1 -0
  131. package/dist/utils/headers.js +8 -0
  132. package/dist/utils/headers.js.map +1 -0
  133. package/dist/utils/json-parse.d.ts +8 -1
  134. package/dist/utils/json-parse.d.ts.map +1 -1
  135. package/dist/utils/json-parse.js +89 -5
  136. package/dist/utils/json-parse.js.map +1 -1
  137. package/dist/utils/oauth/anthropic.d.ts +14 -6
  138. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  139. package/dist/utils/oauth/anthropic.js +288 -57
  140. package/dist/utils/oauth/anthropic.js.map +1 -1
  141. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  142. package/dist/utils/oauth/github-copilot.js +23 -12
  143. package/dist/utils/oauth/github-copilot.js.map +1 -1
  144. package/dist/utils/oauth/index.d.ts +0 -4
  145. package/dist/utils/oauth/index.d.ts.map +1 -1
  146. package/dist/utils/oauth/index.js +0 -10
  147. package/dist/utils/oauth/index.js.map +1 -1
  148. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  149. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  150. package/dist/utils/oauth/oauth-page.js +105 -0
  151. package/dist/utils/oauth/oauth-page.js.map +1 -0
  152. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  153. package/dist/utils/oauth/openai-codex.js +51 -46
  154. package/dist/utils/oauth/openai-codex.js.map +1 -1
  155. package/dist/utils/oauth/pkce.d.ts.map +1 -1
  156. package/dist/utils/oauth/pkce.js.map +1 -1
  157. package/dist/utils/oauth/types.d.ts +10 -0
  158. package/dist/utils/oauth/types.d.ts.map +1 -1
  159. package/dist/utils/oauth/types.js.map +1 -1
  160. package/dist/utils/overflow.d.ts +7 -3
  161. package/dist/utils/overflow.d.ts.map +1 -1
  162. package/dist/utils/overflow.js +46 -13
  163. package/dist/utils/overflow.js.map +1 -1
  164. package/dist/utils/sanitize-unicode.d.ts.map +1 -1
  165. package/dist/utils/sanitize-unicode.js.map +1 -1
  166. package/dist/utils/typebox-helpers.d.ts +1 -1
  167. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  168. package/dist/utils/typebox-helpers.js +1 -1
  169. package/dist/utils/typebox-helpers.js.map +1 -1
  170. package/dist/utils/validation.d.ts.map +1 -1
  171. package/dist/utils/validation.js +247 -38
  172. package/dist/utils/validation.js.map +1 -1
  173. package/package.json +44 -14
  174. package/bedrock-provider.d.ts +0 -1
  175. package/bedrock-provider.js +0 -1
  176. package/dist/providers/google-gemini-cli.d.ts +0 -74
  177. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  178. package/dist/providers/google-gemini-cli.js +0 -754
  179. package/dist/providers/google-gemini-cli.js.map +0 -1
  180. package/dist/utils/oauth/google-antigravity.d.ts +0 -26
  181. package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
  182. package/dist/utils/oauth/google-antigravity.js +0 -373
  183. package/dist/utils/oauth/google-antigravity.js.map +0 -1
  184. package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
  185. package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
  186. package/dist/utils/oauth/google-gemini-cli.js +0 -478
  187. package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @mariozechner/pi-ai
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 (ADC)](#vertex-ai-adc)
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 @mariozechner/pi-ai
82
+ npm install @earendil-works/pi-ai
76
83
  ```
77
84
 
78
- TypeBox exports are re-exported from `@mariozechner/pi-ai`: `Type`, `Static`, and `TSchema`.
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 '@mariozechner/pi-ai';
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 AJV. TypeBox schemas can be serialized and deserialized as plain JSON, making them ideal for distributed systems.
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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' (xhigh maps to high on non-OpenAI providers)
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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
- thinkingFormat?: 'openai' | 'zai' | 'qwen'; // Format for reasoning param: 'openai' uses reasoning_effort, 'zai' uses thinking: { type: "enabled" }, 'qwen' uses enable_thinking: boolean (default: openai)
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 '@mariozechner/pi-ai';
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 `@mariozechner/pi-ai/oauth` entry point in Node.js.
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` (optional `AZURE_OPENAI_API_VERSION`, `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` like `model=deployment,model2=deployment2`) |
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 '@mariozechner/pi-ai';
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 (ADC)
1159
+ ### Vertex AI
979
1160
 
980
- Vertex AI models use Application Default Credentials (ADC):
1161
+ Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC):
981
1162
 
982
- - **Local development**: Run `gcloud auth application-default login`
983
- - **CI/Production**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
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
- Also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options.
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 '@mariozechner/pi-ai';
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 @mariozechner/pi-ai login # interactive provider selection
1022
- npx @mariozechner/pi-ai login anthropic # login to specific provider
1023
- npx @mariozechner/pi-ai list # list available providers
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 `@mariozechner/pi-ai/oauth` entry point. Credential storage is the caller's responsibility.
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, // 'anthropic' | 'openai-codex' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity'
1229
+ type OAuthProvider,
1047
1230
  type OAuthCredentials,
1048
- } from '@mariozechner/pi-ai/oauth';
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 '@mariozechner/pi-ai/oauth';
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 '@mariozechner/pi-ai';
1079
- import { getOAuthApiKey } from '@mariozechner/pi-ai/oauth';
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"]}