@simpletoolsindiaorg/ai-provider 0.79.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 (218) hide show
  1. package/README.md +1393 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/bedrock-provider.d.ts +5 -0
  7. package/dist/bedrock-provider.d.ts.map +1 -0
  8. package/dist/bedrock-provider.js +6 -0
  9. package/dist/bedrock-provider.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +130 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/env-api-keys.d.ts +18 -0
  15. package/dist/env-api-keys.d.ts.map +1 -0
  16. package/dist/env-api-keys.js +182 -0
  17. package/dist/env-api-keys.js.map +1 -0
  18. package/dist/image-models.d.ts +10 -0
  19. package/dist/image-models.d.ts.map +1 -0
  20. package/dist/image-models.generated.d.ts +485 -0
  21. package/dist/image-models.generated.d.ts.map +1 -0
  22. package/dist/image-models.generated.js +487 -0
  23. package/dist/image-models.generated.js.map +1 -0
  24. package/dist/image-models.js +23 -0
  25. package/dist/image-models.js.map +1 -0
  26. package/dist/images-api-registry.d.ts +14 -0
  27. package/dist/images-api-registry.d.ts.map +1 -0
  28. package/dist/images-api-registry.js +22 -0
  29. package/dist/images-api-registry.js.map +1 -0
  30. package/dist/images.d.ts +4 -0
  31. package/dist/images.d.ts.map +1 -0
  32. package/dist/images.js +14 -0
  33. package/dist/images.js.map +1 -0
  34. package/dist/index.d.ts +32 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +20 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/models.d.ts +18 -0
  39. package/dist/models.d.ts.map +1 -0
  40. package/dist/models.generated.d.ts +19105 -0
  41. package/dist/models.generated.d.ts.map +1 -0
  42. package/dist/models.generated.js +17472 -0
  43. package/dist/models.generated.js.map +1 -0
  44. package/dist/models.js +71 -0
  45. package/dist/models.js.map +1 -0
  46. package/dist/oauth.d.ts +2 -0
  47. package/dist/oauth.d.ts.map +1 -0
  48. package/dist/oauth.js +2 -0
  49. package/dist/oauth.js.map +1 -0
  50. package/dist/providers/amazon-bedrock.d.ts +38 -0
  51. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  52. package/dist/providers/amazon-bedrock.js +826 -0
  53. package/dist/providers/amazon-bedrock.js.map +1 -0
  54. package/dist/providers/anthropic.d.ts +71 -0
  55. package/dist/providers/anthropic.d.ts.map +1 -0
  56. package/dist/providers/anthropic.js +959 -0
  57. package/dist/providers/anthropic.js.map +1 -0
  58. package/dist/providers/azure-openai-responses.d.ts +15 -0
  59. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  60. package/dist/providers/azure-openai-responses.js +221 -0
  61. package/dist/providers/azure-openai-responses.js.map +1 -0
  62. package/dist/providers/cloudflare.d.ts +13 -0
  63. package/dist/providers/cloudflare.d.ts.map +1 -0
  64. package/dist/providers/cloudflare.js +26 -0
  65. package/dist/providers/cloudflare.js.map +1 -0
  66. package/dist/providers/faux.d.ts +56 -0
  67. package/dist/providers/faux.d.ts.map +1 -0
  68. package/dist/providers/faux.js +368 -0
  69. package/dist/providers/faux.js.map +1 -0
  70. package/dist/providers/github-copilot-headers.d.ts +8 -0
  71. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  72. package/dist/providers/github-copilot-headers.js +29 -0
  73. package/dist/providers/github-copilot-headers.js.map +1 -0
  74. package/dist/providers/google-shared.d.ts +70 -0
  75. package/dist/providers/google-shared.d.ts.map +1 -0
  76. package/dist/providers/google-shared.js +329 -0
  77. package/dist/providers/google-shared.js.map +1 -0
  78. package/dist/providers/google-vertex.d.ts +15 -0
  79. package/dist/providers/google-vertex.d.ts.map +1 -0
  80. package/dist/providers/google-vertex.js +442 -0
  81. package/dist/providers/google-vertex.js.map +1 -0
  82. package/dist/providers/google.d.ts +13 -0
  83. package/dist/providers/google.d.ts.map +1 -0
  84. package/dist/providers/google.js +402 -0
  85. package/dist/providers/google.js.map +1 -0
  86. package/dist/providers/images/openrouter.d.ts +3 -0
  87. package/dist/providers/images/openrouter.d.ts.map +1 -0
  88. package/dist/providers/images/openrouter.js +128 -0
  89. package/dist/providers/images/openrouter.js.map +1 -0
  90. package/dist/providers/images/register-builtins.d.ts +4 -0
  91. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  92. package/dist/providers/images/register-builtins.js +34 -0
  93. package/dist/providers/images/register-builtins.js.map +1 -0
  94. package/dist/providers/mistral.d.ts +25 -0
  95. package/dist/providers/mistral.d.ts.map +1 -0
  96. package/dist/providers/mistral.js +534 -0
  97. package/dist/providers/mistral.js.map +1 -0
  98. package/dist/providers/openai-codex-responses.d.ts +30 -0
  99. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  100. package/dist/providers/openai-codex-responses.js +1171 -0
  101. package/dist/providers/openai-codex-responses.js.map +1 -0
  102. package/dist/providers/openai-completions.d.ts +19 -0
  103. package/dist/providers/openai-completions.d.ts.map +1 -0
  104. package/dist/providers/openai-completions.js +986 -0
  105. package/dist/providers/openai-completions.js.map +1 -0
  106. package/dist/providers/openai-prompt-cache.d.ts +3 -0
  107. package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
  108. package/dist/providers/openai-prompt-cache.js +10 -0
  109. package/dist/providers/openai-prompt-cache.js.map +1 -0
  110. package/dist/providers/openai-responses-shared.d.ts +18 -0
  111. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  112. package/dist/providers/openai-responses-shared.js +496 -0
  113. package/dist/providers/openai-responses-shared.js.map +1 -0
  114. package/dist/providers/openai-responses.d.ts +13 -0
  115. package/dist/providers/openai-responses.d.ts.map +1 -0
  116. package/dist/providers/openai-responses.js +234 -0
  117. package/dist/providers/openai-responses.js.map +1 -0
  118. package/dist/providers/register-builtins.d.ts +35 -0
  119. package/dist/providers/register-builtins.d.ts.map +1 -0
  120. package/dist/providers/register-builtins.js +254 -0
  121. package/dist/providers/register-builtins.js.map +1 -0
  122. package/dist/providers/simple-options.d.ts +8 -0
  123. package/dist/providers/simple-options.d.ts.map +1 -0
  124. package/dist/providers/simple-options.js +42 -0
  125. package/dist/providers/simple-options.js.map +1 -0
  126. package/dist/providers/transform-messages.d.ts +8 -0
  127. package/dist/providers/transform-messages.d.ts.map +1 -0
  128. package/dist/providers/transform-messages.js +184 -0
  129. package/dist/providers/transform-messages.js.map +1 -0
  130. package/dist/session-resources.d.ts +4 -0
  131. package/dist/session-resources.d.ts.map +1 -0
  132. package/dist/session-resources.js +22 -0
  133. package/dist/session-resources.js.map +1 -0
  134. package/dist/stream.d.ts +8 -0
  135. package/dist/stream.d.ts.map +1 -0
  136. package/dist/stream.js +39 -0
  137. package/dist/stream.js.map +1 -0
  138. package/dist/types.d.ts +516 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +2 -0
  141. package/dist/types.js.map +1 -0
  142. package/dist/utils/abort-signals.d.ts +6 -0
  143. package/dist/utils/abort-signals.d.ts.map +1 -0
  144. package/dist/utils/abort-signals.js +34 -0
  145. package/dist/utils/abort-signals.js.map +1 -0
  146. package/dist/utils/diagnostics.d.ts +19 -0
  147. package/dist/utils/diagnostics.d.ts.map +1 -0
  148. package/dist/utils/diagnostics.js +25 -0
  149. package/dist/utils/diagnostics.js.map +1 -0
  150. package/dist/utils/event-stream.d.ts +21 -0
  151. package/dist/utils/event-stream.d.ts.map +1 -0
  152. package/dist/utils/event-stream.js +81 -0
  153. package/dist/utils/event-stream.js.map +1 -0
  154. package/dist/utils/hash.d.ts +3 -0
  155. package/dist/utils/hash.d.ts.map +1 -0
  156. package/dist/utils/hash.js +14 -0
  157. package/dist/utils/hash.js.map +1 -0
  158. package/dist/utils/headers.d.ts +2 -0
  159. package/dist/utils/headers.d.ts.map +1 -0
  160. package/dist/utils/headers.js +8 -0
  161. package/dist/utils/headers.js.map +1 -0
  162. package/dist/utils/json-parse.d.ts +16 -0
  163. package/dist/utils/json-parse.d.ts.map +1 -0
  164. package/dist/utils/json-parse.js +113 -0
  165. package/dist/utils/json-parse.js.map +1 -0
  166. package/dist/utils/node-http-proxy.d.ts +10 -0
  167. package/dist/utils/node-http-proxy.d.ts.map +1 -0
  168. package/dist/utils/node-http-proxy.js +97 -0
  169. package/dist/utils/node-http-proxy.js.map +1 -0
  170. package/dist/utils/oauth/anthropic.d.ts +25 -0
  171. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  172. package/dist/utils/oauth/anthropic.js +335 -0
  173. package/dist/utils/oauth/anthropic.js.map +1 -0
  174. package/dist/utils/oauth/device-code.d.ts +21 -0
  175. package/dist/utils/oauth/device-code.d.ts.map +1 -0
  176. package/dist/utils/oauth/device-code.js +56 -0
  177. package/dist/utils/oauth/device-code.js.map +1 -0
  178. package/dist/utils/oauth/github-copilot.d.ts +30 -0
  179. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  180. package/dist/utils/oauth/github-copilot.js +280 -0
  181. package/dist/utils/oauth/github-copilot.js.map +1 -0
  182. package/dist/utils/oauth/index.d.ts +58 -0
  183. package/dist/utils/oauth/index.d.ts.map +1 -0
  184. package/dist/utils/oauth/index.js +122 -0
  185. package/dist/utils/oauth/index.js.map +1 -0
  186. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  187. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  188. package/dist/utils/oauth/oauth-page.js +105 -0
  189. package/dist/utils/oauth/oauth-page.js.map +1 -0
  190. package/dist/utils/oauth/openai-codex.d.ts +43 -0
  191. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  192. package/dist/utils/oauth/openai-codex.js +487 -0
  193. package/dist/utils/oauth/openai-codex.js.map +1 -0
  194. package/dist/utils/oauth/pkce.d.ts +13 -0
  195. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  196. package/dist/utils/oauth/pkce.js +31 -0
  197. package/dist/utils/oauth/pkce.js.map +1 -0
  198. package/dist/utils/oauth/types.d.ts +64 -0
  199. package/dist/utils/oauth/types.d.ts.map +1 -0
  200. package/dist/utils/oauth/types.js +2 -0
  201. package/dist/utils/oauth/types.js.map +1 -0
  202. package/dist/utils/overflow.d.ts +57 -0
  203. package/dist/utils/overflow.d.ts.map +1 -0
  204. package/dist/utils/overflow.js +154 -0
  205. package/dist/utils/overflow.js.map +1 -0
  206. package/dist/utils/sanitize-unicode.d.ts +22 -0
  207. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  208. package/dist/utils/sanitize-unicode.js +26 -0
  209. package/dist/utils/sanitize-unicode.js.map +1 -0
  210. package/dist/utils/typebox-helpers.d.ts +17 -0
  211. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  212. package/dist/utils/typebox-helpers.js +21 -0
  213. package/dist/utils/typebox-helpers.js.map +1 -0
  214. package/dist/utils/validation.d.ts +18 -0
  215. package/dist/utils/validation.d.ts.map +1 -0
  216. package/dist/utils/validation.js +281 -0
  217. package/dist/utils/validation.js.map +1 -0
  218. package/package.json +106 -0
package/README.md ADDED
@@ -0,0 +1,1393 @@
1
+ # @simpletoolsindiaorg/ai-provider
2
+
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
+
5
+ **Note**: This library only includes models that support tool calling (function calling), as this is essential for agentic workflows.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Supported Providers](#supported-providers)
10
+ - [Installation](#installation)
11
+ - [Quick Start](#quick-start)
12
+ - [Tools](#tools)
13
+ - [Defining Tools](#defining-tools)
14
+ - [Handling Tool Calls](#handling-tool-calls)
15
+ - [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
16
+ - [Validating Tool Arguments](#validating-tool-arguments)
17
+ - [Complete Event Reference](#complete-event-reference)
18
+ - [Image Input](#image-input)
19
+ - [Image Generation](#image-generation)
20
+ - [Basic Image Generation](#basic-image-generation)
21
+ - [Notes and Limitations](#notes-and-limitations)
22
+ - [Thinking/Reasoning](#thinkingreasoning)
23
+ - [Unified Interface](#unified-interface-streamsimplecompletesimple)
24
+ - [Provider-Specific Options](#provider-specific-options-streamcomplete)
25
+ - [Streaming Thinking Content](#streaming-thinking-content)
26
+ - [Stop Reasons](#stop-reasons)
27
+ - [Error Handling](#error-handling)
28
+ - [Aborting Requests](#aborting-requests)
29
+ - [Continuing After Abort](#continuing-after-abort)
30
+ - [APIs, Models, and Providers](#apis-models-and-providers)
31
+ - [Providers and Models](#providers-and-models)
32
+ - [Querying Providers and Models](#querying-providers-and-models)
33
+ - [Custom Models](#custom-models)
34
+ - [OpenAI Compatibility Settings](#openai-compatibility-settings)
35
+ - [Type Safety](#type-safety)
36
+ - [Cross-Provider Handoffs](#cross-provider-handoffs)
37
+ - [Context Serialization](#context-serialization)
38
+ - [Browser Usage](#browser-usage)
39
+ - [Browser Compatibility Notes](#browser-compatibility-notes)
40
+ - [Environment Variables](#environment-variables-nodejs-only)
41
+ - [Checking Environment Variables](#checking-environment-variables)
42
+ - [OAuth Providers](#oauth-providers)
43
+ - [Vertex AI](#vertex-ai)
44
+ - [CLI Login](#cli-login)
45
+ - [Programmatic OAuth](#programmatic-oauth)
46
+ - [Login Flow Example](#login-flow-example)
47
+ - [Using OAuth Tokens](#using-oauth-tokens)
48
+ - [Provider Notes](#provider-notes)
49
+ - [License](#license)
50
+
51
+ ## Supported Providers
52
+
53
+ - **OpenAI**
54
+ - **Ant Ling**
55
+ - **Azure OpenAI (Responses)**
56
+ - **OpenAI Codex** (ChatGPT Plus/Pro subscription, requires OAuth, see below)
57
+ - **DeepSeek**
58
+ - **NVIDIA NIM**
59
+ - **Anthropic**
60
+ - **Google**
61
+ - **Vertex AI** (Gemini via Vertex AI)
62
+ - **Mistral**
63
+ - **Groq**
64
+ - **Cerebras**
65
+ - **Cloudflare AI Gateway**
66
+ - **Cloudflare Workers AI**
67
+ - **xAI**
68
+ - **OpenRouter**
69
+ - **Vercel AI Gateway**
70
+ - **ZAI** (with separate Coding Plan China provider)
71
+ - **MiniMax**
72
+ - **Together AI**
73
+ - **GitHub Copilot** (requires OAuth, see below)
74
+ - **Amazon Bedrock**
75
+ - **OpenCode Zen**
76
+ - **OpenCode Go**
77
+ - **Fireworks** (uses Anthropic-compatible API)
78
+ - **Kimi For Coding** (Moonshot AI, uses Anthropic-compatible API)
79
+ - **Xiaomi MiMo** (uses Anthropic-compatible API; defaults to API billing endpoint, with separate Token Plan providers for `cn`/`ams`/`sgp` regions)
80
+ - **Ollama Cloud** (managed inference at `ollama.com`, OpenAI-compatible; set `OLLAMA_API_KEY` to authenticate)
81
+ - **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc.
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ npm install @simpletoolsindiaorg/ai-provider
87
+ ```
88
+
89
+ TypeBox exports are re-exported from `@simpletoolsindiaorg/ai-provider`: `Type`, `Static`, and `TSchema`.
90
+
91
+ ## Quick Start
92
+
93
+ ```typescript
94
+ import { Type, getModel, stream, complete, Context, Tool, StringEnum } from '@simpletoolsindiaorg/ai-provider';
95
+
96
+ // Fully typed with auto-complete support for both providers and models
97
+ const model = getModel('openai', 'gpt-4o-mini');
98
+
99
+ // Define tools with TypeBox schemas for type safety and validation
100
+ const tools: Tool[] = [{
101
+ name: 'get_time',
102
+ description: 'Get the current time',
103
+ parameters: Type.Object({
104
+ timezone: Type.Optional(Type.String({ description: 'Optional timezone (e.g., America/New_York)' }))
105
+ })
106
+ }];
107
+
108
+ // Build a conversation context (easily serializable and transferable between models)
109
+ const context: Context = {
110
+ systemPrompt: 'You are a helpful assistant.',
111
+ messages: [{ role: 'user', content: 'What time is it?' }],
112
+ tools
113
+ };
114
+
115
+ // Option 1: Streaming with all event types
116
+ const s = stream(model, context);
117
+
118
+ for await (const event of s) {
119
+ switch (event.type) {
120
+ case 'start':
121
+ console.log(`Starting with ${event.partial.model}`);
122
+ break;
123
+ case 'text_start':
124
+ console.log('\n[Text started]');
125
+ break;
126
+ case 'text_delta':
127
+ process.stdout.write(event.delta);
128
+ break;
129
+ case 'text_end':
130
+ console.log('\n[Text ended]');
131
+ break;
132
+ case 'thinking_start':
133
+ console.log('[Model is thinking...]');
134
+ break;
135
+ case 'thinking_delta':
136
+ process.stdout.write(event.delta);
137
+ break;
138
+ case 'thinking_end':
139
+ console.log('[Thinking complete]');
140
+ break;
141
+ case 'toolcall_start':
142
+ console.log(`\n[Tool call started: index ${event.contentIndex}]`);
143
+ break;
144
+ case 'toolcall_delta':
145
+ // Partial tool arguments are being streamed
146
+ const partialCall = event.partial.content[event.contentIndex];
147
+ if (partialCall.type === 'toolCall') {
148
+ console.log(`[Streaming args for ${partialCall.name}]`);
149
+ }
150
+ break;
151
+ case 'toolcall_end':
152
+ console.log(`\nTool called: ${event.toolCall.name}`);
153
+ console.log(`Arguments: ${JSON.stringify(event.toolCall.arguments)}`);
154
+ break;
155
+ case 'done':
156
+ console.log(`\nFinished: ${event.reason}`);
157
+ break;
158
+ case 'error':
159
+ console.error(`Error: ${event.error}`);
160
+ break;
161
+ }
162
+ }
163
+
164
+ // Get the final message after streaming, add it to the context
165
+ const finalMessage = await s.result();
166
+ context.messages.push(finalMessage);
167
+
168
+ // Handle tool calls if any
169
+ const toolCalls = finalMessage.content.filter(b => b.type === 'toolCall');
170
+ for (const call of toolCalls) {
171
+ // Execute the tool
172
+ const result = call.name === 'get_time'
173
+ ? new Date().toLocaleString('en-US', {
174
+ timeZone: call.arguments.timezone || 'UTC',
175
+ dateStyle: 'full',
176
+ timeStyle: 'long'
177
+ })
178
+ : 'Unknown tool';
179
+
180
+ // Add tool result to context (supports text and images)
181
+ context.messages.push({
182
+ role: 'toolResult',
183
+ toolCallId: call.id,
184
+ toolName: call.name,
185
+ content: [{ type: 'text', text: result }],
186
+ isError: false,
187
+ timestamp: Date.now()
188
+ });
189
+ }
190
+
191
+ // Continue if there were tool calls
192
+ if (toolCalls.length > 0) {
193
+ const continuation = await complete(model, context);
194
+ context.messages.push(continuation);
195
+ console.log('After tool execution:', continuation.content);
196
+ }
197
+
198
+ console.log(`Total tokens: ${finalMessage.usage.input} in, ${finalMessage.usage.output} out`);
199
+ console.log(`Cost: $${finalMessage.usage.cost.total.toFixed(4)}`);
200
+
201
+ // Option 2: Get complete response without streaming
202
+ const response = await complete(model, context);
203
+
204
+ for (const block of response.content) {
205
+ if (block.type === 'text') {
206
+ console.log(block.text);
207
+ } else if (block.type === 'toolCall') {
208
+ console.log(`Tool: ${block.name}(${JSON.stringify(block.arguments)})`);
209
+ }
210
+ }
211
+ ```
212
+
213
+ ## Tools
214
+
215
+ 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.
216
+
217
+ ### Defining Tools
218
+
219
+ ```typescript
220
+ import { Type, Tool, StringEnum } from '@simpletoolsindiaorg/ai-provider';
221
+
222
+ // Define tool parameters with TypeBox
223
+ const weatherTool: Tool = {
224
+ name: 'get_weather',
225
+ description: 'Get current weather for a location',
226
+ parameters: Type.Object({
227
+ location: Type.String({ description: 'City name or coordinates' }),
228
+ units: StringEnum(['celsius', 'fahrenheit'], { default: 'celsius' })
229
+ })
230
+ };
231
+
232
+ // Note: For Google API compatibility, use StringEnum helper instead of Type.Enum
233
+ // Type.Enum generates anyOf/const patterns that Google doesn't support
234
+
235
+ const bookMeetingTool: Tool = {
236
+ name: 'book_meeting',
237
+ description: 'Schedule a meeting',
238
+ parameters: Type.Object({
239
+ title: Type.String({ minLength: 1 }),
240
+ startTime: Type.String({ format: 'date-time' }),
241
+ endTime: Type.String({ format: 'date-time' }),
242
+ attendees: Type.Array(Type.String({ format: 'email' }), { minItems: 1 })
243
+ })
244
+ };
245
+ ```
246
+
247
+ ### Handling Tool Calls
248
+
249
+ Tool results use content blocks and can include both text and images:
250
+
251
+ ```typescript
252
+ import { readFileSync } from 'fs';
253
+
254
+ const context: Context = {
255
+ messages: [{ role: 'user', content: 'What is the weather in London?' }],
256
+ tools: [weatherTool]
257
+ };
258
+
259
+ const response = await complete(model, context);
260
+
261
+ // Check for tool calls in the response
262
+ for (const block of response.content) {
263
+ if (block.type === 'toolCall') {
264
+ // Execute your tool with the arguments
265
+ // See "Validating Tool Arguments" section for validation
266
+ const result = await executeWeatherApi(block.arguments);
267
+
268
+ // Add tool result with text content
269
+ context.messages.push({
270
+ role: 'toolResult',
271
+ toolCallId: block.id,
272
+ toolName: block.name,
273
+ content: [{ type: 'text', text: JSON.stringify(result) }],
274
+ isError: false,
275
+ timestamp: Date.now()
276
+ });
277
+ }
278
+ }
279
+
280
+ // Tool results can also include images (for vision-capable models)
281
+ const imageBuffer = readFileSync('chart.png');
282
+ context.messages.push({
283
+ role: 'toolResult',
284
+ toolCallId: 'tool_xyz',
285
+ toolName: 'generate_chart',
286
+ content: [
287
+ { type: 'text', text: 'Generated chart showing temperature trends' },
288
+ { type: 'image', data: imageBuffer.toString('base64'), mimeType: 'image/png' }
289
+ ],
290
+ isError: false,
291
+ timestamp: Date.now()
292
+ });
293
+ ```
294
+
295
+ ### Streaming Tool Calls with Partial JSON
296
+
297
+ During streaming, tool call arguments are progressively parsed as they arrive. This enables real-time UI updates before the complete arguments are available:
298
+
299
+ ```typescript
300
+ const s = stream(model, context);
301
+
302
+ for await (const event of s) {
303
+ if (event.type === 'toolcall_delta') {
304
+ const toolCall = event.partial.content[event.contentIndex];
305
+
306
+ // toolCall.arguments contains partially parsed JSON during streaming
307
+ // This allows for progressive UI updates
308
+ if (toolCall.type === 'toolCall' && toolCall.arguments) {
309
+ // BE DEFENSIVE: arguments may be incomplete
310
+ // Example: Show file path being written even before content is complete
311
+ if (toolCall.name === 'write_file' && toolCall.arguments.path) {
312
+ console.log(`Writing to: ${toolCall.arguments.path}`);
313
+
314
+ // Content might be partial or missing
315
+ if (toolCall.arguments.content) {
316
+ console.log(`Content preview: ${toolCall.arguments.content.substring(0, 100)}...`);
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ if (event.type === 'toolcall_end') {
323
+ // Here toolCall.arguments is complete (but not yet validated)
324
+ const toolCall = event.toolCall;
325
+ console.log(`Tool completed: ${toolCall.name}`, toolCall.arguments);
326
+ }
327
+ }
328
+ ```
329
+
330
+ **Important notes about partial tool arguments:**
331
+ - During `toolcall_delta` events, `arguments` contains the best-effort parse of partial JSON
332
+ - Fields may be missing or incomplete - always check for existence before use
333
+ - String values may be truncated mid-word
334
+ - Arrays may be incomplete
335
+ - Nested objects may be partially populated
336
+ - At minimum, `arguments` will be an empty object `{}`, never `undefined`
337
+ - The Google provider does not support function call streaming. Instead, you will receive a single `toolcall_delta` event with the full arguments.
338
+
339
+ ### Validating Tool Arguments
340
+
341
+ When using `agentLoop`, tool arguments are automatically validated against your TypeBox schemas before execution. If validation fails, the error is returned to the model as a tool result, allowing it to retry.
342
+
343
+ When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
344
+
345
+ ```typescript
346
+ import { stream, validateToolCall, Tool } from '@simpletoolsindiaorg/ai-provider';
347
+
348
+ const tools: Tool[] = [weatherTool, calculatorTool];
349
+ const s = stream(model, { messages, tools });
350
+
351
+ for await (const event of s) {
352
+ if (event.type === 'toolcall_end') {
353
+ const toolCall = event.toolCall;
354
+
355
+ try {
356
+ // Validate arguments against the tool's schema (throws on invalid args)
357
+ const validatedArgs = validateToolCall(tools, toolCall);
358
+ const result = await executeMyTool(toolCall.name, validatedArgs);
359
+ // ... add tool result to context
360
+ } catch (error) {
361
+ // Validation failed - return error as tool result so model can retry
362
+ context.messages.push({
363
+ role: 'toolResult',
364
+ toolCallId: toolCall.id,
365
+ toolName: toolCall.name,
366
+ content: [{ type: 'text', text: error.message }],
367
+ isError: true,
368
+ timestamp: Date.now()
369
+ });
370
+ }
371
+ }
372
+ }
373
+ ```
374
+
375
+ ### Complete Event Reference
376
+
377
+ All streaming events emitted during assistant message generation:
378
+
379
+ | Event Type | Description | Key Properties |
380
+ |------------|-------------|----------------|
381
+ | `start` | Stream begins | `partial`: Initial assistant message structure |
382
+ | `text_start` | Text block starts | `contentIndex`: Position in content array |
383
+ | `text_delta` | Text chunk received | `delta`: New text, `contentIndex`: Position |
384
+ | `text_end` | Text block complete | `content`: Full text, `contentIndex`: Position |
385
+ | `thinking_start` | Thinking block starts | `contentIndex`: Position in content array |
386
+ | `thinking_delta` | Thinking chunk received | `delta`: New text, `contentIndex`: Position |
387
+ | `thinking_end` | Thinking block complete | `content`: Full thinking, `contentIndex`: Position |
388
+ | `toolcall_start` | Tool call begins | `contentIndex`: Position in content array |
389
+ | `toolcall_delta` | Tool arguments streaming | `delta`: JSON chunk, `partial.content[contentIndex].arguments`: Partial parsed args |
390
+ | `toolcall_end` | Tool call complete | `toolCall`: Complete validated tool call with `id`, `name`, `arguments` |
391
+ | `done` | Stream complete | `reason`: Stop reason ("stop", "length", "toolUse"), `message`: Final assistant message |
392
+ | `error` | Error occurred | `reason`: Error type ("error" or "aborted"), `error`: AssistantMessage with partial content |
393
+
394
+ 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.
395
+
396
+ ## Image Input
397
+
398
+ 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.
399
+
400
+ ```typescript
401
+ import { readFileSync } from 'fs';
402
+ import { getModel, complete } from '@simpletoolsindiaorg/ai-provider';
403
+
404
+ const model = getModel('openai', 'gpt-4o-mini');
405
+
406
+ // Check if model supports images
407
+ if (model.input.includes('image')) {
408
+ console.log('Model supports vision');
409
+ }
410
+
411
+ const imageBuffer = readFileSync('image.png');
412
+ const base64Image = imageBuffer.toString('base64');
413
+
414
+ const response = await complete(model, {
415
+ messages: [{
416
+ role: 'user',
417
+ content: [
418
+ { type: 'text', text: 'What is in this image?' },
419
+ { type: 'image', data: base64Image, mimeType: 'image/png' }
420
+ ]
421
+ }]
422
+ });
423
+
424
+ // Access the response
425
+ for (const block of response.content) {
426
+ if (block.type === 'text') {
427
+ console.log(block.text);
428
+ }
429
+ }
430
+ ```
431
+
432
+ ## Image Generation
433
+
434
+ 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.
435
+
436
+ 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.
437
+
438
+ ### Basic Image Generation
439
+
440
+ ```typescript
441
+ import { getImageModel, generateImages } from '@mariozechner/pi-ai';
442
+
443
+ const model = getImageModel('openrouter', 'google/gemini-2.5-flash-image');
444
+
445
+ const result = await generateImages(model, {
446
+ input: [{ type: 'text', text: 'Generate a red circle on a plain white background.' }]
447
+ }, {
448
+ apiKey: process.env.OPENROUTER_API_KEY
449
+ });
450
+
451
+ for (const block of result.output) {
452
+ if (block.type === 'text') {
453
+ console.log(block.text);
454
+ } else if (block.type === 'image') {
455
+ console.log(block.mimeType);
456
+ console.log(block.data.substring(0, 32));
457
+ }
458
+ }
459
+ ```
460
+
461
+ Some models also support image input:
462
+
463
+ ```typescript
464
+ import { readFileSync } from 'fs';
465
+
466
+ const imageBuffer = readFileSync('input.png');
467
+ const result = await generateImages(model, {
468
+ input: [
469
+ { type: 'text', text: 'Create a variation of this image with a blue background.' },
470
+ { type: 'image', data: imageBuffer.toString('base64'), mimeType: 'image/png' }
471
+ ]
472
+ }, {
473
+ apiKey: process.env.OPENROUTER_API_KEY
474
+ });
475
+ ```
476
+
477
+ Check capabilities on the model metadata:
478
+
479
+ ```typescript
480
+ console.log(model.input); // ['text', 'image']
481
+ console.log(model.output); // ['image'] or ['image', 'text']
482
+ ```
483
+
484
+ ### Notes and Limitations
485
+
486
+ - Use `getImageModel(...)`, not `getModel(...)`.
487
+ - Use `generateImages()`, not `stream()` / `complete()`.
488
+ - Image-generation models do not participate in tool calling.
489
+ - Outputs are returned in `AssistantImages.output` and can include both base64-encoded `ImageContent` blocks and `TextContent` blocks.
490
+ - Some models return only images, others return images plus text. Check `model.output`.
491
+ - Some models accept image input, others are text-to-image only. Check `model.input`.
492
+ - Like the streaming APIs, image generation supports options such as `apiKey`, `signal`, `headers`, `onPayload`, and `onResponse`, and results may include `stopReason`, `responseId`, and `usage`.
493
+ - 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.
494
+ - At the moment, image generation is available through only one provider, OpenRouter.
495
+
496
+ ## Thinking/Reasoning
497
+
498
+ 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.
499
+
500
+ ### Unified Interface (streamSimple/completeSimple)
501
+
502
+ ```typescript
503
+ import { getModel, streamSimple, completeSimple } from '@simpletoolsindiaorg/ai-provider';
504
+
505
+ // Many models across providers support thinking/reasoning
506
+ const model = getModel('anthropic', 'claude-sonnet-4-20250514');
507
+ // or getModel('openai', 'gpt-5-mini');
508
+ // or getModel('google', 'gemini-2.5-flash');
509
+ // or getModel('xai', 'grok-code-fast-1');
510
+ // or getModel('groq', 'openai/gpt-oss-20b');
511
+ // or getModel('cerebras', 'gpt-oss-120b');
512
+ // or getModel('openrouter', 'z-ai/glm-4.5v');
513
+
514
+ // Check if model supports reasoning
515
+ if (model.reasoning) {
516
+ console.log('Model supports reasoning/thinking');
517
+ }
518
+
519
+ // Use the simplified reasoning option
520
+ const response = await completeSimple(model, {
521
+ messages: [{ role: 'user', content: 'Solve: 2x + 5 = 13' }]
522
+ }, {
523
+ reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
524
+ });
525
+
526
+ // Access thinking and text blocks
527
+ for (const block of response.content) {
528
+ if (block.type === 'thinking') {
529
+ console.log('Thinking:', block.thinking);
530
+ } else if (block.type === 'text') {
531
+ console.log('Response:', block.text);
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### Provider-Specific Options (stream/complete)
537
+
538
+ For fine-grained control, use the provider-specific options:
539
+
540
+ ```typescript
541
+ import { getModel, complete } from '@simpletoolsindiaorg/ai-provider';
542
+
543
+ // OpenAI Reasoning (o1, o3, gpt-5)
544
+ const openaiModel = getModel('openai', 'gpt-5-mini');
545
+ await complete(openaiModel, context, {
546
+ reasoningEffort: 'medium',
547
+ reasoningSummary: 'detailed' // OpenAI Responses API only
548
+ });
549
+
550
+ // Anthropic Thinking (Claude Sonnet 4)
551
+ const anthropicModel = getModel('anthropic', 'claude-sonnet-4-20250514');
552
+ await complete(anthropicModel, context, {
553
+ thinkingEnabled: true,
554
+ thinkingBudgetTokens: 8192 // Optional token limit
555
+ });
556
+
557
+ // Google Gemini Thinking
558
+ const googleModel = getModel('google', 'gemini-2.5-flash');
559
+ await complete(googleModel, context, {
560
+ thinking: {
561
+ enabled: true,
562
+ budgetTokens: 8192 // -1 for dynamic, 0 to disable
563
+ }
564
+ });
565
+ ```
566
+
567
+ ### Streaming Thinking Content
568
+
569
+ When streaming, thinking content is delivered through specific events:
570
+
571
+ ```typescript
572
+ const s = streamSimple(model, context, { reasoning: 'high' });
573
+
574
+ for await (const event of s) {
575
+ switch (event.type) {
576
+ case 'thinking_start':
577
+ console.log('[Model started thinking]');
578
+ break;
579
+ case 'thinking_delta':
580
+ process.stdout.write(event.delta); // Stream thinking content
581
+ break;
582
+ case 'thinking_end':
583
+ console.log('\n[Thinking complete]');
584
+ break;
585
+ }
586
+ }
587
+ ```
588
+
589
+ ## Stop Reasons
590
+
591
+ Every `AssistantMessage` includes a `stopReason` field that indicates how the generation ended:
592
+
593
+ - `"stop"` - Normal completion, the model finished its response
594
+ - `"length"` - Output hit the maximum token limit
595
+ - `"toolUse"` - Model is calling tools and expects tool results
596
+ - `"error"` - An error occurred during generation
597
+ - `"aborted"` - Request was cancelled via abort signal
598
+
599
+ `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.
600
+
601
+ ## Error Handling
602
+
603
+ When a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event:
604
+
605
+ ```typescript
606
+ // In streaming
607
+ for await (const event of stream) {
608
+ if (event.type === 'error') {
609
+ // event.reason is either "error" or "aborted"
610
+ // event.error is the AssistantMessage with partial content
611
+ console.error(`Error (${event.reason}):`, event.error.errorMessage);
612
+ console.log('Partial content:', event.error.content);
613
+ }
614
+ }
615
+
616
+ // The final message will have the error details
617
+ const message = await stream.result();
618
+ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
619
+ console.error('Request failed:', message.errorMessage);
620
+ // message.content contains any partial content received before the error
621
+ // message.usage contains partial token counts and costs
622
+ }
623
+ ```
624
+
625
+ ### Aborting Requests
626
+
627
+ The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
628
+
629
+ ```typescript
630
+ import { getModel, stream } from '@simpletoolsindiaorg/ai-provider';
631
+
632
+ const model = getModel('openai', 'gpt-4o-mini');
633
+ const controller = new AbortController();
634
+
635
+ // Abort after 2 seconds
636
+ setTimeout(() => controller.abort(), 2000);
637
+
638
+ const s = stream(model, {
639
+ messages: [{ role: 'user', content: 'Write a long story' }]
640
+ }, {
641
+ signal: controller.signal
642
+ });
643
+
644
+ for await (const event of s) {
645
+ if (event.type === 'text_delta') {
646
+ process.stdout.write(event.delta);
647
+ } else if (event.type === 'error') {
648
+ // event.reason tells you if it was "error" or "aborted"
649
+ console.log(`${event.reason === 'aborted' ? 'Aborted' : 'Error'}:`, event.error.errorMessage);
650
+ }
651
+ }
652
+
653
+ // Get results (may be partial if aborted)
654
+ const response = await s.result();
655
+ if (response.stopReason === 'aborted') {
656
+ console.log('Request was aborted:', response.errorMessage);
657
+ console.log('Partial content received:', response.content);
658
+ console.log('Tokens used:', response.usage);
659
+ }
660
+ ```
661
+
662
+ ### Continuing After Abort
663
+
664
+ Aborted messages can be added to the conversation context and continued in subsequent requests:
665
+
666
+ ```typescript
667
+ const context = {
668
+ messages: [
669
+ { role: 'user', content: 'Explain quantum computing in detail' }
670
+ ]
671
+ };
672
+
673
+ // First request gets aborted after 2 seconds
674
+ const controller1 = new AbortController();
675
+ setTimeout(() => controller1.abort(), 2000);
676
+
677
+ const partial = await complete(model, context, { signal: controller1.signal });
678
+
679
+ // Add the partial response to context
680
+ context.messages.push(partial);
681
+ context.messages.push({ role: 'user', content: 'Please continue' });
682
+
683
+ // Continue the conversation
684
+ const continuation = await complete(model, context);
685
+ ```
686
+
687
+ ### Debugging Provider Payloads
688
+
689
+ Use the `onPayload` callback to inspect the request payload sent to the provider. This is useful for debugging request formatting issues or provider validation errors.
690
+
691
+ ```typescript
692
+ const response = await complete(model, context, {
693
+ onPayload: (payload) => {
694
+ console.log('Provider payload:', JSON.stringify(payload, null, 2));
695
+ }
696
+ });
697
+ ```
698
+
699
+ The callback is supported by `stream`, `complete`, `streamSimple`, and `completeSimple`.
700
+
701
+ ## APIs, Models, and Providers
702
+
703
+ The library uses a registry of API implementations. Built-in APIs include:
704
+
705
+ - **`anthropic-messages`**: Anthropic Messages API (`streamAnthropic`, `AnthropicOptions`)
706
+ - **`google-generative-ai`**: Google Generative AI API (`streamGoogle`, `GoogleOptions`)
707
+ - **`google-vertex`**: Google Vertex AI API (`streamGoogleVertex`, `GoogleVertexOptions`)
708
+ - **`mistral-conversations`**: Mistral Conversations API (`streamMistral`, `MistralOptions`)
709
+ - **`openai-completions`**: OpenAI Chat Completions API (`streamOpenAICompletions`, `OpenAICompletionsOptions`)
710
+ - **`openai-responses`**: OpenAI Responses API (`streamOpenAIResponses`, `OpenAIResponsesOptions`)
711
+ - **`openai-codex-responses`**: OpenAI Codex Responses API (`streamOpenAICodexResponses`, `OpenAICodexResponsesOptions`)
712
+ - **`azure-openai-responses`**: Azure OpenAI Responses API (`streamAzureOpenAIResponses`, `AzureOpenAIResponsesOptions`)
713
+ - **`bedrock-converse-stream`**: Amazon Bedrock Converse API (`streamBedrock`, `BedrockOptions`)
714
+
715
+ ### Faux provider for tests
716
+
717
+ `registerFauxProvider()` registers a temporary in-memory provider for tests and demos. It is opt-in and not part of the built-in provider set.
718
+
719
+ ```typescript
720
+ import {
721
+ complete,
722
+ fauxAssistantMessage,
723
+ fauxText,
724
+ fauxThinking,
725
+ fauxToolCall,
726
+ registerFauxProvider,
727
+ stream,
728
+ } from '@simpletoolsindiaorg/ai-provider';
729
+
730
+ const registration = registerFauxProvider({
731
+ tokensPerSecond: 50 // optional
732
+ });
733
+
734
+ const model = registration.getModel();
735
+ const context = {
736
+ messages: [{ role: 'user', content: 'Summarize package.json and then call echo', timestamp: Date.now() }]
737
+ };
738
+
739
+ registration.setResponses([
740
+ fauxAssistantMessage([
741
+ fauxThinking('Need to inspect package metadata first.'),
742
+ fauxToolCall('echo', { text: 'package.json' })
743
+ ], { stopReason: 'toolUse' })
744
+ ]);
745
+
746
+ const first = await complete(model, context, {
747
+ sessionId: 'session-1',
748
+ cacheRetention: 'short'
749
+ });
750
+ context.messages.push(first);
751
+
752
+ context.messages.push({
753
+ role: 'toolResult',
754
+ toolCallId: first.content.find((block) => block.type === 'toolCall')!.id,
755
+ toolName: 'echo',
756
+ content: [{ type: 'text', text: 'package.json contents here' }],
757
+ isError: false,
758
+ timestamp: Date.now()
759
+ });
760
+
761
+ registration.setResponses([
762
+ fauxAssistantMessage([
763
+ fauxThinking('Now I can summarize the tool output.'),
764
+ fauxText('Here is the summary.')
765
+ ])
766
+ ]);
767
+
768
+ const s = stream(model, context);
769
+ for await (const event of s) {
770
+ console.log(event.type);
771
+ }
772
+
773
+ // Optional: register multiple faux models for model-switching tests
774
+ const multiModel = registerFauxProvider({
775
+ models: [
776
+ { id: 'faux-fast', reasoning: false },
777
+ { id: 'faux-thinker', reasoning: true }
778
+ ]
779
+ });
780
+ const thinker = multiModel.getModel('faux-thinker');
781
+
782
+ console.log(thinker?.reasoning);
783
+ console.log(registration.getPendingResponseCount());
784
+ console.log(registration.state.callCount);
785
+ registration.unregister();
786
+ multiModel.unregister();
787
+ ```
788
+
789
+ Notes:
790
+ - Responses are consumed from a queue in request start order.
791
+ - If the queue is empty, the faux provider returns an assistant error message with `errorMessage: "No more faux responses queued"`.
792
+ - Use `registration.setResponses([...])` to replace the remaining queue and `registration.appendResponses([...])` to add more responses.
793
+ - `registration.models` exposes all registered faux models. `registration.getModel()` returns the first one, and `registration.getModel(id)` returns a specific one.
794
+ - Use `fauxAssistantMessage(...)` for scripted assistant replies. Use `fauxText(...)`, `fauxThinking(...)`, and `fauxToolCall(...)` to build content blocks without filling in low-level fields manually.
795
+ - `registration.unregister()` removes the temporary provider from the global API registry.
796
+ - 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.
797
+ - Tool call arguments stream incrementally via `toolcall_delta` chunks.
798
+ - By default, each streamed chunk is emitted on its own microtask. Set `tokensPerSecond` to pace chunk delivery in real time.
799
+ - The intended use is one deterministic scripted flow per registration. If you need independent concurrent flows, register separate faux providers.
800
+
801
+ ### Providers and Models
802
+
803
+ A **provider** offers models through a specific API. For example:
804
+ - **Anthropic** models use the `anthropic-messages` API
805
+ - **Google** models use the `google-generative-ai` API
806
+ - **OpenAI** models use the `openai-responses` API
807
+ - **Mistral** models use the `mistral-conversations` API
808
+ - **xAI, Cerebras, Groq, NVIDIA NIM, Together AI, etc.** models use the `openai-completions` API (OpenAI-compatible)
809
+
810
+ ### Querying Providers and Models
811
+
812
+ ```typescript
813
+ import { getProviders, getModels, getModel } from '@simpletoolsindiaorg/ai-provider';
814
+
815
+ // Get all available providers
816
+ const providers = getProviders();
817
+ console.log(providers); // ['openai', 'anthropic', 'google', 'xai', 'groq', ...]
818
+
819
+ // Get all models from a provider (fully typed)
820
+ const anthropicModels = getModels('anthropic');
821
+ for (const model of anthropicModels) {
822
+ console.log(`${model.id}: ${model.name}`);
823
+ console.log(` API: ${model.api}`); // 'anthropic-messages'
824
+ console.log(` Context: ${model.contextWindow} tokens`);
825
+ console.log(` Vision: ${model.input.includes('image')}`);
826
+ console.log(` Reasoning: ${model.reasoning}`);
827
+ }
828
+
829
+ // Get a specific model (both provider and model ID are auto-completed in IDEs)
830
+ const model = getModel('openai', 'gpt-4o-mini');
831
+ console.log(`Using ${model.name} via ${model.api} API`);
832
+ ```
833
+
834
+ ### Custom Models
835
+
836
+ You can create custom models for local inference servers or custom endpoints:
837
+
838
+ ```typescript
839
+ import { Model, stream } from '@simpletoolsindiaorg/ai-provider';
840
+
841
+ // Example: Ollama using OpenAI-compatible API
842
+ const ollamaModel: Model<'openai-completions'> = {
843
+ id: 'llama-3.1-8b',
844
+ name: 'Llama 3.1 8B (Ollama)',
845
+ api: 'openai-completions',
846
+ provider: 'ollama',
847
+ baseUrl: 'http://localhost:11434/v1',
848
+ reasoning: false,
849
+ input: ['text'],
850
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
851
+ contextWindow: 128000,
852
+ maxTokens: 32000
853
+ };
854
+
855
+ // Example: LiteLLM proxy with explicit compat settings
856
+ const litellmModel: Model<'openai-completions'> = {
857
+ id: 'gpt-4o',
858
+ name: 'GPT-4o (via LiteLLM)',
859
+ api: 'openai-completions',
860
+ provider: 'litellm',
861
+ baseUrl: 'http://localhost:4000/v1',
862
+ reasoning: false,
863
+ input: ['text', 'image'],
864
+ cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
865
+ contextWindow: 128000,
866
+ maxTokens: 16384,
867
+ compat: {
868
+ supportsStore: false, // LiteLLM doesn't support the store field
869
+ }
870
+ };
871
+
872
+ // Example: Custom endpoint with headers (bypassing Cloudflare bot detection)
873
+ const proxyModel: Model<'anthropic-messages'> = {
874
+ id: 'claude-sonnet-4',
875
+ name: 'Claude Sonnet 4 (Proxied)',
876
+ api: 'anthropic-messages',
877
+ provider: 'custom-proxy',
878
+ baseUrl: 'https://proxy.example.com/v1',
879
+ reasoning: true,
880
+ input: ['text', 'image'],
881
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
882
+ contextWindow: 200000,
883
+ maxTokens: 8192,
884
+ headers: {
885
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
886
+ 'X-Custom-Auth': 'bearer-token-here'
887
+ }
888
+ };
889
+
890
+ // Use the custom model
891
+ const response = await stream(ollamaModel, context, {
892
+ apiKey: 'dummy' // Ollama doesn't need a real key
893
+ });
894
+ ```
895
+
896
+ 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.
897
+
898
+ 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.
899
+
900
+ This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers. You can set `compat` at the provider level or per model.
901
+
902
+ ```typescript
903
+ const ollamaReasoningModel: Model<'openai-completions'> = {
904
+ id: 'gpt-oss:20b',
905
+ name: 'GPT-OSS 20B (Ollama)',
906
+ api: 'openai-completions',
907
+ provider: 'ollama',
908
+ baseUrl: 'http://localhost:11434/v1',
909
+ reasoning: true,
910
+ input: ['text'],
911
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
912
+ contextWindow: 131072,
913
+ maxTokens: 32000,
914
+ thinkingLevelMap: {
915
+ minimal: null,
916
+ low: null,
917
+ medium: null,
918
+ high: 'high',
919
+ xhigh: null,
920
+ },
921
+ compat: {
922
+ supportsDeveloperRole: false,
923
+ supportsReasoningEffort: false,
924
+ }
925
+ };
926
+ ```
927
+
928
+ ### OpenAI Compatibility Settings
929
+
930
+ 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, NVIDIA NIM, 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 supports Responses-specific flags.
931
+
932
+ ```typescript
933
+ interface OpenAICompletionsCompat {
934
+ supportsStore?: boolean; // Whether provider supports the `store` field (default: true)
935
+ supportsDeveloperRole?: boolean; // Whether provider supports `developer` role vs `system` (default: true)
936
+ supportsReasoningEffort?: boolean; // Whether provider supports `reasoning_effort` (default: true)
937
+ supportsUsageInStreaming?: boolean; // Whether provider supports `stream_options: { include_usage: true }` (default: true)
938
+ supportsStrictMode?: boolean; // Whether provider supports `strict` in tool definitions (default: true)
939
+ sendSessionAffinityHeaders?: boolean; // Whether to send `session_id`, `x-client-request-id`, and `x-session-affinity` from `sessionId` when caching is enabled (default: false)
940
+ maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
941
+ requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
942
+ requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
943
+ requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
944
+ requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
945
+ thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, '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, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
946
+ cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content
947
+ openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {})
948
+ vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {})
949
+ }
950
+
951
+ interface OpenAIResponsesCompat {
952
+ supportsDeveloperRole?: boolean; // Whether provider supports `developer` role vs `system` (default: true)
953
+ sendSessionIdHeader?: boolean; // Whether to send `session_id` from `sessionId` when caching is enabled (default: true)
954
+ supportsLongCacheRetention?: boolean; // Whether provider supports `prompt_cache_retention: "24h"` (default: true)
955
+ }
956
+ ```
957
+
958
+ If `compat` is not set, the library falls back to URL-based detection. If `compat` is partially set, unspecified fields use the detected defaults. This is useful for:
959
+
960
+ - **LiteLLM proxies**: May not support `store` field
961
+ - **Custom inference servers**: May use non-standard field names
962
+ - **Self-hosted endpoints**: May have different feature support
963
+
964
+ ### Type Safety
965
+
966
+ 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.
967
+
968
+ ```typescript
969
+ import { streamAnthropic, type AnthropicOptions } from '@simpletoolsindiaorg/ai-provider';
970
+
971
+ // TypeScript knows this is an Anthropic model
972
+ const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
973
+
974
+ const options: AnthropicOptions = {
975
+ thinkingEnabled: true,
976
+ thinkingBudgetTokens: 2048
977
+ };
978
+
979
+ await streamAnthropic(claude, context, options);
980
+ ```
981
+
982
+ ## Cross-Provider Handoffs
983
+
984
+ The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including thinking blocks, tool calls, and tool results.
985
+
986
+ ### How It Works
987
+
988
+ When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
989
+
990
+ - **User and tool result messages** are passed through unchanged
991
+ - **Assistant messages from the same provider/API** are preserved as-is
992
+ - **Assistant messages from different providers** have their thinking blocks converted to text with `<thinking>` tags
993
+ - **Tool calls and regular text** are preserved unchanged
994
+
995
+ ### Example: Multi-Provider Conversation
996
+
997
+ ```typescript
998
+ import { getModel, complete, Context } from '@simpletoolsindiaorg/ai-provider';
999
+
1000
+ // Start with Claude
1001
+ const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
1002
+ const context: Context = {
1003
+ messages: []
1004
+ };
1005
+
1006
+ context.messages.push({ role: 'user', content: 'What is 25 * 18?' });
1007
+ const claudeResponse = await complete(claude, context, {
1008
+ thinkingEnabled: true
1009
+ });
1010
+ context.messages.push(claudeResponse);
1011
+
1012
+ // Switch to GPT-5 - it will see Claude's thinking as <thinking> tagged text
1013
+ const gpt5 = getModel('openai', 'gpt-5-mini');
1014
+ context.messages.push({ role: 'user', content: 'Is that calculation correct?' });
1015
+ const gptResponse = await complete(gpt5, context);
1016
+ context.messages.push(gptResponse);
1017
+
1018
+ // Switch to Gemini
1019
+ const gemini = getModel('google', 'gemini-2.5-flash');
1020
+ context.messages.push({ role: 'user', content: 'What was the original question?' });
1021
+ const geminiResponse = await complete(gemini, context);
1022
+ ```
1023
+
1024
+ ### Provider Compatibility
1025
+
1026
+ All providers can handle messages from other providers, including:
1027
+ - Text content
1028
+ - Tool calls and tool results (including images in tool results)
1029
+ - Thinking/reasoning blocks (transformed to tagged text for cross-provider compatibility)
1030
+ - Aborted messages with partial content
1031
+
1032
+ This enables flexible workflows where you can:
1033
+ - Start with a fast model for initial responses
1034
+ - Switch to a more capable model for complex reasoning
1035
+ - Use specialized models for specific tasks
1036
+ - Maintain conversation continuity across provider outages
1037
+
1038
+ ## Context Serialization
1039
+
1040
+ 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:
1041
+
1042
+ ```typescript
1043
+ import { Context, getModel, complete } from '@simpletoolsindiaorg/ai-provider';
1044
+
1045
+ // Create and use a context
1046
+ const context: Context = {
1047
+ systemPrompt: 'You are a helpful assistant.',
1048
+ messages: [
1049
+ { role: 'user', content: 'What is TypeScript?' }
1050
+ ]
1051
+ };
1052
+
1053
+ const model = getModel('openai', 'gpt-4o-mini');
1054
+ const response = await complete(model, context);
1055
+ context.messages.push(response);
1056
+
1057
+ // Serialize the entire context
1058
+ const serialized = JSON.stringify(context);
1059
+ console.log('Serialized context size:', serialized.length, 'bytes');
1060
+
1061
+ // Save to database, localStorage, file, etc.
1062
+ localStorage.setItem('conversation', serialized);
1063
+
1064
+ // Later: deserialize and continue the conversation
1065
+ const restored: Context = JSON.parse(localStorage.getItem('conversation')!);
1066
+ restored.messages.push({ role: 'user', content: 'Tell me more about its type system' });
1067
+
1068
+ // Continue with any model
1069
+ const newModel = getModel('anthropic', 'claude-3-5-haiku-20241022');
1070
+ const continuation = await complete(newModel, restored);
1071
+ ```
1072
+
1073
+ > **Note**: If the context contains images (encoded as base64 as shown in the Image Input section), those will also be serialized.
1074
+
1075
+ ## Browser Usage
1076
+
1077
+ The library supports browser environments. You must pass the API key explicitly since environment variables are not available in browsers:
1078
+
1079
+ ```typescript
1080
+ import { getModel, complete } from '@simpletoolsindiaorg/ai-provider';
1081
+
1082
+ // API key must be passed explicitly in browser
1083
+ const model = getModel('anthropic', 'claude-3-5-haiku-20241022');
1084
+
1085
+ const response = await complete(model, {
1086
+ messages: [{ role: 'user', content: 'Hello!' }]
1087
+ }, {
1088
+ apiKey: 'your-api-key'
1089
+ });
1090
+ ```
1091
+
1092
+ > **Security Warning**: Exposing API keys in frontend code is dangerous. Anyone can extract and abuse your keys. Only use this approach for internal tools or demos. For production applications, use a backend proxy that keeps your API keys secure.
1093
+
1094
+ ### Browser Compatibility Notes
1095
+
1096
+ - Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments.
1097
+ - OAuth login flows are not supported in browser environments. Use the `@simpletoolsindiaorg/ai-provider/oauth` entry point in Node.js.
1098
+ - In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime.
1099
+ - Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
1100
+
1101
+ ### Environment Variables (Node.js only)
1102
+
1103
+ In Node.js environments, you can set environment variables to avoid passing API keys:
1104
+
1105
+ | Provider | Environment Variable(s) |
1106
+ |----------|------------------------|
1107
+ | OpenAI | `OPENAI_API_KEY` |
1108
+ | Ant Ling | `ANT_LING_API_KEY` |
1109
+ | 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`. |
1110
+ | Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
1111
+ | DeepSeek | `DEEPSEEK_API_KEY` |
1112
+ | NVIDIA NIM | `NVIDIA_API_KEY` |
1113
+ | Google | `GEMINI_API_KEY` |
1114
+ | Vertex AI | `GOOGLE_CLOUD_API_KEY` or `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
1115
+ | Mistral | `MISTRAL_API_KEY` |
1116
+ | Groq | `GROQ_API_KEY` |
1117
+ | Cerebras | `CEREBRAS_API_KEY` |
1118
+ | Cloudflare AI Gateway | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_GATEWAY_ID` |
1119
+ | Cloudflare Workers AI | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` |
1120
+ | xAI | `XAI_API_KEY` |
1121
+ | Fireworks | `FIREWORKS_API_KEY` |
1122
+ | Together AI | `TOGETHER_API_KEY` |
1123
+ | OpenRouter | `OPENROUTER_API_KEY` |
1124
+ | Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
1125
+ | zAI | `ZAI_API_KEY` |
1126
+ | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` |
1127
+ | MiniMax | `MINIMAX_API_KEY` |
1128
+ | OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
1129
+ | Kimi For Coding | `KIMI_API_KEY` |
1130
+ | Xiaomi MiMo (API billing) | `XIAOMI_API_KEY` |
1131
+ | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` |
1132
+ | Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` |
1133
+ | Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` |
1134
+ | Ollama Cloud | `OLLAMA_API_KEY` |
1135
+ | GitHub Copilot | `COPILOT_GITHUB_TOKEN` |
1136
+
1137
+ When set, the library automatically uses these keys:
1138
+
1139
+ ```typescript
1140
+ // Uses OPENAI_API_KEY from environment
1141
+ const model = getModel('openai', 'gpt-4o-mini');
1142
+ const response = await complete(model, context);
1143
+
1144
+ // Or override with explicit key
1145
+ const response = await complete(model, context, {
1146
+ apiKey: 'sk-different-key'
1147
+ });
1148
+ ```
1149
+
1150
+ ### Checking Environment Variables
1151
+
1152
+ ```typescript
1153
+ import { getEnvApiKey } from '@simpletoolsindiaorg/ai-provider';
1154
+
1155
+ // Check if an API key is set in environment variables
1156
+ const key = getEnvApiKey('openai'); // checks OPENAI_API_KEY
1157
+ ```
1158
+
1159
+ ## OAuth Providers
1160
+
1161
+ Several providers require OAuth authentication instead of static API keys:
1162
+
1163
+ - **Anthropic** (Claude Pro/Max subscription)
1164
+ - **OpenAI Codex** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
1165
+ - **GitHub Copilot** (Copilot subscription)
1166
+
1167
+ For paid Cloud Code Assist subscriptions, set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` to your project ID.
1168
+
1169
+ ### Vertex AI
1170
+
1171
+ Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC):
1172
+
1173
+ - **API key**: Set `GOOGLE_CLOUD_API_KEY` or pass `apiKey` in the call options.
1174
+ - **Local development (ADC)**: Run `gcloud auth application-default login`
1175
+ - **CI/Production (ADC)**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
1176
+
1177
+ 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.
1178
+
1179
+ Example:
1180
+
1181
+ ```bash
1182
+ # Local (uses your user credentials)
1183
+ gcloud auth application-default login
1184
+ export GOOGLE_CLOUD_PROJECT="my-project"
1185
+ export GOOGLE_CLOUD_LOCATION="us-central1"
1186
+
1187
+ # CI/Production (service account key file)
1188
+ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
1189
+ ```
1190
+
1191
+ ```typescript
1192
+ import { getModel, complete } from '@simpletoolsindiaorg/ai-provider';
1193
+
1194
+ (async () => {
1195
+ const model = getModel('google-vertex', 'gemini-2.5-flash');
1196
+ const response = await complete(model, {
1197
+ messages: [{ role: 'user', content: 'Hello from Vertex AI' }]
1198
+ }, {
1199
+ apiKey: process.env.GOOGLE_CLOUD_API_KEY,
1200
+ });
1201
+
1202
+ for (const block of response.content) {
1203
+ if (block.type === 'text') console.log(block.text);
1204
+ }
1205
+ })().catch(console.error);
1206
+ ```
1207
+
1208
+ Official docs: [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials)
1209
+
1210
+ ### CLI Login
1211
+
1212
+ The quickest way to authenticate:
1213
+
1214
+ ```bash
1215
+ npx @simpletoolsindiaorg/ai-provider login # interactive provider selection
1216
+ npx @simpletoolsindiaorg/ai-provider login anthropic # login to specific provider
1217
+ npx @simpletoolsindiaorg/ai-provider list # list available providers
1218
+ ```
1219
+
1220
+ Credentials are saved to `auth.json` in the current directory.
1221
+
1222
+ ### Programmatic OAuth
1223
+
1224
+ The library provides login and token refresh functions via the `@simpletoolsindiaorg/ai-provider/oauth` entry point. Credential storage is the caller's responsibility.
1225
+
1226
+ ```typescript
1227
+ import {
1228
+ // Login functions (return credentials, do not store)
1229
+ loginAnthropic,
1230
+ loginOpenAICodex,
1231
+ loginGitHubCopilot,
1232
+ loginGeminiCli,
1233
+
1234
+ // Token management
1235
+ refreshOAuthToken, // (provider, credentials) => new credentials
1236
+ getOAuthApiKey, // (provider, credentialsMap) => { newCredentials, apiKey } | null
1237
+
1238
+ // Types
1239
+ type OAuthProvider,
1240
+ type OAuthCredentials,
1241
+ } from '@simpletoolsindiaorg/ai-provider/oauth';
1242
+ ```
1243
+
1244
+ ### Login Flow Example
1245
+
1246
+ ```typescript
1247
+ import { loginGitHubCopilot } from '@simpletoolsindiaorg/ai-provider/oauth';
1248
+ import { writeFileSync } from 'fs';
1249
+
1250
+ const credentials = await loginGitHubCopilot({
1251
+ onAuth: (url, instructions) => {
1252
+ console.log(`Open: ${url}`);
1253
+ if (instructions) console.log(instructions);
1254
+ },
1255
+ onPrompt: async (prompt) => {
1256
+ return await getUserInput(prompt.message);
1257
+ },
1258
+ onProgress: (message) => console.log(message)
1259
+ });
1260
+
1261
+ // Store credentials yourself
1262
+ const auth = { 'github-copilot': { type: 'oauth', ...credentials } };
1263
+ writeFileSync('auth.json', JSON.stringify(auth, null, 2));
1264
+ ```
1265
+
1266
+ ### Using OAuth Tokens
1267
+
1268
+ Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired:
1269
+
1270
+ ```typescript
1271
+ import { getModel, complete } from '@simpletoolsindiaorg/ai-provider';
1272
+ import { getOAuthApiKey } from '@simpletoolsindiaorg/ai-provider/oauth';
1273
+ import { readFileSync, writeFileSync } from 'fs';
1274
+
1275
+ // Load your stored credentials
1276
+ const auth = JSON.parse(readFileSync('auth.json', 'utf-8'));
1277
+
1278
+ // Get API key (refreshes if expired)
1279
+ const result = await getOAuthApiKey('github-copilot', auth);
1280
+ if (!result) throw new Error('Not logged in');
1281
+
1282
+ // Save refreshed credentials
1283
+ auth['github-copilot'] = { type: 'oauth', ...result.newCredentials };
1284
+ writeFileSync('auth.json', JSON.stringify(auth, null, 2));
1285
+
1286
+ // Use the API key
1287
+ const model = getModel('github-copilot', 'gpt-4o');
1288
+ const response = await complete(model, {
1289
+ messages: [{ role: 'user', content: 'Hello!' }]
1290
+ }, { apiKey: result.apiKey });
1291
+ ```
1292
+
1293
+ ### Provider Notes
1294
+
1295
+ **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.
1296
+
1297
+ **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.
1298
+
1299
+ **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".
1300
+
1301
+ ## Development
1302
+
1303
+ ### Adding a New Provider
1304
+
1305
+ Adding a new LLM provider requires changes across multiple files. This checklist covers all necessary steps:
1306
+
1307
+ #### 1. Core Types (`src/types.ts`)
1308
+
1309
+ - Add the API identifier to `KnownApi` (for example `"bedrock-converse-stream"`)
1310
+ - Create an options interface extending `StreamOptions` (for example `BedrockOptions`)
1311
+ - Add the provider name to `KnownProvider` (for example `"amazon-bedrock"`)
1312
+
1313
+ #### 2. Provider Implementation (`src/providers/`)
1314
+
1315
+ Create a new provider file (for example `amazon-bedrock.ts`) that exports:
1316
+
1317
+ - `stream<Provider>()` function returning `AssistantMessageEventStream`
1318
+ - `streamSimple<Provider>()` for `SimpleStreamOptions` mapping
1319
+ - Provider-specific options interface
1320
+ - Message conversion functions to transform `Context` to provider format
1321
+ - Tool conversion if the provider supports tools
1322
+ - Response parsing to emit standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`)
1323
+
1324
+ #### 3. API Registry Integration (`src/providers/register-builtins.ts`)
1325
+
1326
+ - Register the API with `registerApiProvider()`
1327
+ - Add a package subpath export in `package.json` for the provider module (`./dist/providers/<provider>.js`)
1328
+ - Add lazy loader wrappers in `src/providers/register-builtins.ts`, do not statically import provider implementation modules there
1329
+ - Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@simpletoolsindiaorg/ai-provider`
1330
+ - Add credential detection in `env-api-keys.ts` for the new provider
1331
+ - Ensure `streamSimple` handles auth lookup via `getEnvApiKey()` or provider-specific auth
1332
+
1333
+ #### 4. Model Generation (`scripts/generate-models.ts`, `scripts/generate-image-models.ts`)
1334
+
1335
+ - Add logic to fetch and parse models from the provider's source (e.g., models.dev API)
1336
+ - Map chat/tool-capable provider model data to the standardized `Model` interface via `scripts/generate-models.ts`
1337
+ - Map image-generation provider model data to the standardized `ImagesModel` interface via `scripts/generate-image-models.ts`
1338
+ - Handle provider-specific quirks (pricing format, capability flags, model ID transformations)
1339
+
1340
+ #### 5. Tests (`test/`)
1341
+
1342
+ Create or update test files to cover the new provider:
1343
+
1344
+ - `stream.test.ts` - Basic streaming and tool use
1345
+ - `tokens.test.ts` - Token usage reporting
1346
+ - `abort.test.ts` - Request cancellation
1347
+ - `empty.test.ts` - Empty message handling
1348
+ - `context-overflow.test.ts` - Context limit errors
1349
+ - `image-limits.test.ts` - Image support (if applicable)
1350
+ - `unicode-surrogate.test.ts` - Unicode handling
1351
+ - `tool-call-without-result.test.ts` - Orphaned tool calls
1352
+ - `image-tool-result.test.ts` - Images in tool results
1353
+ - `total-tokens.test.ts` - Token counting accuracy
1354
+ - `cross-provider-handoff.test.ts` - Cross-provider context replay
1355
+
1356
+ For `cross-provider-handoff.test.ts`, add at least one provider/model pair. If the provider exposes multiple model families (for example GPT and Claude), add at least one pair per family.
1357
+
1358
+ For providers with non-standard auth (AWS, Google Vertex), create a utility like `bedrock-utils.ts` with credential detection helpers.
1359
+
1360
+ #### 6. Coding Agent Integration (`../coding-agent/`)
1361
+
1362
+ Update `src/core/model-resolver.ts`:
1363
+
1364
+ - Add a default model ID for the provider in `DEFAULT_MODELS`
1365
+
1366
+ Update `src/cli/args.ts`:
1367
+
1368
+ - Add environment variable documentation in the help text
1369
+
1370
+ Update `README.md`:
1371
+
1372
+ - Add the provider to the providers section with setup instructions
1373
+
1374
+ #### 7. Documentation
1375
+
1376
+ Update `packages/ai/README.md`:
1377
+
1378
+ - Add to the Supported Providers table
1379
+ - Document any provider-specific options or authentication requirements
1380
+ - Add environment variable to the Environment Variables section
1381
+
1382
+ #### 8. Changelog
1383
+
1384
+ Add an entry to `packages/ai/CHANGELOG.md` under `## [Unreleased]`:
1385
+
1386
+ ```markdown
1387
+ ### Added
1388
+ - Added support for [Provider Name] provider ([#PR](link) by [@author](link))
1389
+ ```
1390
+
1391
+ ## License
1392
+
1393
+ MIT