@jz92/ai-provider 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,14 @@
1
- # @jithin/ai-provider
1
+ # @jz92/ai-provider
2
2
 
3
3
  A zero-config AI routing layer for Node.js and Next.js projects.
4
4
 
5
- Import one function — get Ollama locally and Anthropic/OpenAI/Groq in production, automatically, based on `NODE_ENV`. No provider-switching logic in your feature code, ever.
5
+ Import one function — get Ollama locally and any cloud provider in production, automatically, based on `NODE_ENV`. No provider-switching logic in your feature code, ever.
6
+
7
+ | Environment | Provider | Model | Cost |
8
+ |---|---|---|---|
9
+ | `development` | Ollama (local) | `qwen2.5-coder:14b` | $0 |
10
+ | `test` / CI | Anthropic | `claude-haiku-4-5` | ~$0.001/req |
11
+ | `production` | Anthropic | `claude-sonnet-4-6` | ~$0.03/req |
6
12
 
7
13
  ---
8
14
 
@@ -25,155 +31,164 @@ You bring Ollama. This package talks to it.
25
31
 
26
32
  ---
27
33
 
28
- ## Prerequisites
34
+ ## Quick setup for a Next.js project
29
35
 
30
- For local development you need Ollama installed and running on your machine.
36
+ ### 1. Install production deps
31
37
 
32
38
  ```bash
33
- # Install (macOS)
34
- brew install ollama
35
-
36
- # Start as a background service
37
- brew services start ollama
38
-
39
- # Pull a model (one-time, ~9GB)
40
- ollama pull qwen2.5-coder:14b
39
+ npm install @jz92/ai-provider @ai-sdk/anthropic ai zod
41
40
  ```
42
41
 
43
- Verify it's running:
42
+ ### 2. Install local dev dep (Ollama)
43
+
44
44
  ```bash
45
- curl http://localhost:11434 # should return: Ollama is running
45
+ npm install ollama-ai-provider --save-dev --legacy-peer-deps
46
46
  ```
47
47
 
48
- For production (Vercel, AWS, etc.) you only need an API key from your chosen provider — no Ollama required.
48
+ > **Why `--legacy-peer-deps`?**
49
+ > `ollama-ai-provider@1.2.0` has a peer dependency conflict with `zod@4`.
50
+ > This flag is required until `ollama-ai-provider` releases a `zod@4` compatible version.
51
+ > This is an upstream issue and **does not affect production** — `devDependencies` are
52
+ > never installed on Vercel or AWS.
53
+ > Track the upstream issue: [ollama-ai-provider on GitHub](https://github.com/sgomez/ollama-ai-provider)
54
+
55
+ ### 3. Your `package.json` should look like this
56
+
57
+ ```json
58
+ {
59
+ "dependencies": {
60
+ "@jz92/ai-provider": "^0.3.2",
61
+ "@ai-sdk/anthropic": "^4.0.0",
62
+ "ai": "^7.0.0",
63
+ "zod": "^4.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "ollama-ai-provider": "^1.2.0"
67
+ }
68
+ }
69
+ ```
49
70
 
50
- ---
71
+ This ensures:
72
+ - **Vercel / AWS** — `ollama-ai-provider` is never installed, no conflict, clean build
73
+ - **Local dev** — `ollama-ai-provider` installed, Ollama runs free at `localhost:11434`
51
74
 
52
- ## Installation
75
+ ### 4. Set up Ollama locally (first time only)
53
76
 
54
77
  ```bash
55
- npm install @jithin/ai-provider
56
- ```
78
+ # Install Ollama
79
+ brew install ollama
57
80
 
58
- After install, a setup guide prints automatically telling you exactly which peer deps to install based on the providers you want to use. The short version:
81
+ # Start as a background service
82
+ brew services start ollama
59
83
 
60
- ```bash
61
- # Always required
62
- npm install ai zod
63
-
64
- # Local dev (free, no API key)
65
- npm install ollama-ai-provider
66
-
67
- # Cloud — install only the provider(s) you use
68
- npm install @ai-sdk/anthropic # → ANTHROPIC_API_KEY
69
- npm install @ai-sdk/openai # → OPENAI_API_KEY
70
- npm install @ai-sdk/google # → GOOGLE_GENERATIVE_AI_API_KEY
71
- npm install @ai-sdk/groq # → GROQ_API_KEY
72
- npm install @ai-sdk/mistral # → MISTRAL_API_KEY
84
+ # Pull the default model (~9GB)
85
+ ollama pull qwen2.5-coder:14b
86
+
87
+ # Verify
88
+ curl http://localhost:11434 # Ollama is running
73
89
  ```
74
90
 
75
- **Only install adapters for providers you actually use.** Unused ones are never loaded — the package uses dynamic imports so missing adapters don't cause errors unless you try to use them.
91
+ ### 5. Set environment variables
76
92
 
77
- **Switching providers later is one env var change** — `AI_PROVIDER=openai` — no code changes needed in your feature files.
93
+ ```bash
94
+ # .env.development (commit the .example, not the real file)
95
+ NODE_ENV=development
96
+ OLLAMA_BASE_URL=http://localhost:11434
97
+ OLLAMA_MODEL=qwen2.5-coder:14b
98
+ AI_LOG_USAGE=true
78
99
 
79
- ---
100
+ # .env.production (set as secrets in Vercel / AWS — never commit)
101
+ ANTHROPIC_API_KEY=sk-ant-...
102
+ ```
80
103
 
81
- ## Usage
104
+ On Vercel, set `ANTHROPIC_API_KEY` in **Project Settings → Environment Variables**.
105
+ `NODE_ENV=production` is set automatically.
106
+
107
+ ### 6. Use it
82
108
 
83
109
  ```typescript
84
- import { generateStructured, generatePlainText } from '@jithin/ai-provider'
110
+ import { generateStructured, generatePlainText } from '@jz92/ai-provider'
85
111
  import { z } from 'zod'
86
112
 
87
- // Structured output — returns validated, typed JSON
88
113
  const result = await generateStructured({
89
114
  systemPrompt: 'Extract data. Respond in JSON only.',
90
- prompt: 'Find all orders placed in the last 30 days with status delivered.',
115
+ prompt: userInput,
91
116
  schema: z.object({ name: z.string(), city: z.string() }),
92
- cacheKey: `extract:${input}`, // optional — skips API on repeat calls
117
+ cacheKey: `extract:${userInput}`,
93
118
  })
94
119
 
95
- console.log(result.data) // { collection: 'orders', operation: 'find', query: { ... } }
120
+ console.log(result.data) // { name: 'Alex', city: 'London' }
96
121
  console.log(result.provider) // 'ollama' locally · 'anthropic' in prod
97
122
  console.log(result.fromCache) // true on cache hit
98
-
99
- // Plain text output
100
- const result = await generatePlainText({
101
- systemPrompt: 'You are a helpful assistant.',
102
- prompt: 'Summarise this in one sentence...',
103
- })
104
123
  ```
105
124
 
106
125
  Your code is identical in every environment. The provider switches automatically.
107
126
 
108
127
  ---
109
128
 
110
- ## How routing works
129
+ ## Switching cloud providers
111
130
 
112
- | `NODE_ENV` | Provider | Model | Cost |
113
- |---|---|---|---|
114
- | `development` | Ollama (local) | `qwen2.5-coder:14b` | $0 |
115
- | `test` / CI | Anthropic | `claude-haiku-4-5` | ~$0.001/req |
116
- | `production` | Anthropic | `claude-sonnet-4-6` | ~$0.03/req |
131
+ Switching from Anthropic to OpenAI (or any other provider) is one env var and one package:
117
132
 
118
- Override anything with env vars:
133
+ ```bash
134
+ npm install @ai-sdk/openai
135
+ ```
119
136
 
120
137
  ```bash
121
- # Force a specific provider
122
- AI_PROVIDER=openai npm run dev
138
+ # .env.production
139
+ AI_PROVIDER=openai
140
+ OPENAI_API_KEY=sk-...
141
+ ```
123
142
 
124
- # Force a specific model
125
- AI_MODEL=gpt-4o npm run dev
143
+ No code changes. Your `generateStructured()` calls are unchanged.
126
144
 
127
- # Use a custom Ollama model variant
128
- OLLAMA_MODEL=my-custom-model npm run dev
129
- ```
145
+ ### Supported providers
146
+
147
+ | Provider | Package | Env var |
148
+ |---|---|---|
149
+ | Anthropic (default) | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
150
+ | OpenAI | `@ai-sdk/openai` | `OPENAI_API_KEY` |
151
+ | Google Gemini | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
152
+ | Groq | `@ai-sdk/groq` | `GROQ_API_KEY` |
153
+ | Mistral | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
154
+ | Ollama (local) | `ollama-ai-provider` | — |
130
155
 
131
156
  ---
132
157
 
133
- ## Setting your API key
158
+ ## Usage
134
159
 
135
- Keys are read from environment variables at runtime. The package never sees or stores them.
160
+ ### `generateStructured` typed JSON output
136
161
 
137
- ### Local dev — no key needed
138
- Ollama runs entirely on your machine. Just set `NODE_ENV=development` (the default).
162
+ ```typescript
163
+ import { generateStructured } from '@jz92/ai-provider'
164
+ import { z } from 'zod'
139
165
 
140
- ```bash
141
- # .env.development
142
- NODE_ENV=development
143
- OLLAMA_BASE_URL=http://localhost:11434
144
- OLLAMA_MODEL=qwen2.5-coder:14b
145
- AI_LOG_USAGE=true
146
- ```
166
+ const result = await generateStructured({
167
+ systemPrompt: 'You are a data extraction assistant. Respond in JSON only.',
168
+ prompt: 'Extract name and city from: "Hi I am Alex from London"',
169
+ schema: z.object({ name: z.string(), city: z.string() }),
170
+ cacheKey: 'extract:alex', // optional — repeat calls skip the API
171
+ maxInputTokens: 4000, // optional — throws if exceeded
172
+ })
147
173
 
148
- ### Production Vercel
149
- Set one environment variable in your Vercel project dashboard:
150
- ```
151
- ANTHROPIC_API_KEY = sk-ant-...
174
+ console.log(result.data) // { name: 'Alex', city: 'London' }
175
+ console.log(result.provider) // 'ollama' | 'anthropic' | 'openai' ...
176
+ console.log(result.fromCache) // true if served from response cache
177
+ console.log(result.usage) // { inputTokens, outputTokens, cachedTokens }
152
178
  ```
153
- `NODE_ENV=production` is set automatically by Vercel. Done.
154
179
 
155
- ### ProductionAWS (ECS / Lambda / EC2)
156
- ```bash
157
- ANTHROPIC_API_KEY=sk-ant-...
158
- NODE_ENV=production
159
- ```
180
+ ### `generatePlainText`unstructured text output
160
181
 
161
- ### CI — GitHub Actions
162
- ```yaml
163
- env:
164
- NODE_ENV: test
165
- ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
166
- ```
182
+ ```typescript
183
+ import { generatePlainText } from '@jz92/ai-provider'
167
184
 
168
- ### Supported provider keys
185
+ const result = await generatePlainText({
186
+ systemPrompt: 'You are a helpful assistant.',
187
+ prompt: 'Summarise this in one sentence...',
188
+ })
169
189
 
170
- | Provider | Env var |
171
- |---|---|
172
- | Anthropic | `ANTHROPIC_API_KEY` |
173
- | OpenAI | `OPENAI_API_KEY` |
174
- | Google | `GOOGLE_GENERATIVE_AI_API_KEY` |
175
- | Groq | `GROQ_API_KEY` |
176
- | Mistral | `MISTRAL_API_KEY` |
190
+ console.log(result.data) // the text response
191
+ ```
177
192
 
178
193
  ---
179
194
 
@@ -193,107 +208,30 @@ env:
193
208
 
194
209
  ---
195
210
 
196
- ## Architecture
197
-
198
- ```mermaid
199
- flowchart TD
200
- A["Your feature code\ngenerateStructured() · generatePlainText()"]
201
- B["Gateway\ncache · token guard · retry · timeout"]
202
- C["Provider resolver\nreads NODE_ENV + overrides"]
203
-
204
- D["development\nNODE_ENV=development"]
205
- E["test / CI\nNODE_ENV=test"]
206
- F["production\nNODE_ENV=production"]
207
-
208
- G["Ollama\nlocalhost:11434 · free"]
209
- H["Anthropic · OpenAI\nGoogle · Groq · Mistral"]
210
-
211
- A --> B --> C
212
- C --> D --> G
213
- C --> E --> H
214
- C --> F --> H
215
-
216
- style A fill:#F1EFE8,stroke:#5F5E5A
217
- style B fill:#EEEDFE,stroke:#534AB7
218
- style C fill:#EEEDFE,stroke:#534AB7
219
- style D fill:#E1F5EE,stroke:#0F6E56
220
- style E fill:#FAEEDA,stroke:#854F0B
221
- style F fill:#FAECE7,stroke:#993C1D
222
- style G fill:#E1F5EE,stroke:#0F6E56
223
- style H fill:#FAECE7,stroke:#993C1D
224
- ```
225
-
226
- ---
227
-
228
211
  ## What's included in the gateway
229
212
 
230
213
  Every request passes through the gateway regardless of provider:
231
214
 
232
- - **Response cache** — same `cacheKey` skips the API entirely. Bounded at 500 entries, 5 min TTL. Configurable via env vars.
233
- - **Token budget guard** — estimates input size and throws before the API call if it exceeds the limit. Set `maxInputTokens` per call.
234
- - **Smart retry** — retries only transient errors (rate limit, server error, timeout). Never retries auth or billing failures — those won't recover and would waste money.
235
- - **Hard timeout** — 60s for Ollama (model load time), 30s for cloud. Override with `AI_TIMEOUT_MS`.
236
- - **Prompt caching** — automatically enabled for Anthropic in production. Marks the system prompt for server-side caching, reducing input costs by ~90% on repeat calls.
237
-
238
- ---
239
-
240
- ## Custom Ollama model variants
241
-
242
- You can bake your system prompt into a named local model using an Ollama `Modelfile`. This mirrors what prompt caching does in production — the stable context is paid once, not on every request.
243
-
244
- ```dockerfile
245
- # modelfiles/Modelfile.my-feature
246
- FROM qwen2.5-coder:14b
247
-
248
- SYSTEM """
249
- Your stable system prompt here.
250
- Respond only in JSON.
251
- """
252
-
253
- PARAMETER temperature 0.1
254
- PARAMETER num_predict 1024
255
- ```
256
-
257
- ```bash
258
- ollama create my-feature -f modelfiles/Modelfile.my-feature
259
- ```
260
-
261
- ```bash
262
- # .env.development
263
- OLLAMA_MODEL=my-feature
264
- ```
265
-
266
- A `Modelfile.template` is included at `node_modules/@jithin/ai-provider/modelfiles-template/Modelfile.template`.
267
-
268
- ---
269
-
270
- ## Security
271
-
272
- This package reads API keys from environment variables and passes them directly to the provider SDK over HTTPS. Keys are never logged, stored, or transmitted by this package.
273
-
274
- Your responsibilities as a consumer:
275
-
276
- - Never commit `.env` or `.env.local` — add both to `.gitignore`
277
- - Never log `process.env` in application code
278
- - Use `.env.example` with placeholder values for documentation
279
- - Use deployment secrets (Vercel dashboard / AWS Secrets Manager) in production
280
- - Rotate keys immediately if accidentally exposed
215
+ - **Response cache** — same `cacheKey` skips the API entirely. Bounded at 500 entries, 5 min TTL.
216
+ - **Token budget guard** — throws before the API call if input exceeds `maxInputTokens`.
217
+ - **Smart retry** — retries only transient errors (429, 500, timeout). Never retries auth or billing failures.
218
+ - **Hard timeout** — 60s for Ollama, 30s for cloud. Override with `AI_TIMEOUT_MS`.
219
+ - **Prompt caching** — automatically enabled for Anthropic in production. Reduces input costs by ~90% on repeat calls.
220
+ - **Usage logging** — formatted terminal output in development showing provider, model, and token counts.
281
221
 
282
222
  ---
283
223
 
284
224
  ## Error handling
285
225
 
286
- The package throws `AIProviderError` with a typed `code` and a clear actionable message. You never see raw SDK errors.
287
-
288
226
  ```typescript
289
- import { generateStructured, AIProviderError } from '@jithin/ai-provider'
227
+ import { generateStructured, AIProviderError } from '@jz92/ai-provider'
290
228
 
291
229
  try {
292
230
  const result = await generateStructured({ ... })
293
231
  } catch (err) {
294
232
  if (err instanceof AIProviderError) {
295
- console.error(err.code) // 'AUTH_ERROR' | 'BILLING_ERROR' | 'RATE_LIMIT' | etc.
296
- console.error(err.message) // tells you exactly what to do
233
+ console.error(err.code) // 'AUTH_ERROR' | 'RATE_LIMIT' | 'TIMEOUT' | etc.
234
+ console.error(err.message) // actionable message with exact steps to fix
297
235
  }
298
236
  }
299
237
  ```
@@ -342,6 +280,49 @@ try {
342
280
 
343
281
  ---
344
282
 
283
+ ## Architecture
284
+
285
+ ```mermaid
286
+ flowchart TD
287
+ A["Your feature code\ngenerateStructured() · generatePlainText()"]
288
+ B["Gateway\ncache · token guard · retry · timeout"]
289
+ C["Provider resolver\nreads NODE_ENV + overrides"]
290
+
291
+ D["development\nNODE_ENV=development"]
292
+ E["test / CI\nNODE_ENV=test"]
293
+ F["production\nNODE_ENV=production"]
294
+
295
+ G["Ollama\nlocalhost:11434 · free"]
296
+ H["Anthropic · OpenAI\nGoogle · Groq · Mistral"]
297
+
298
+ A --> B --> C
299
+ C --> D --> G
300
+ C --> E --> H
301
+ C --> F --> H
302
+
303
+ style A fill:#F1EFE8,stroke:#5F5E5A
304
+ style B fill:#EEEDFE,stroke:#534AB7
305
+ style C fill:#EEEDFE,stroke:#534AB7
306
+ style D fill:#E1F5EE,stroke:#0F6E56
307
+ style E fill:#FAEEDA,stroke:#854F0B
308
+ style F fill:#FAECE7,stroke:#993C1D
309
+ style G fill:#E1F5EE,stroke:#0F6E56
310
+ style H fill:#FAECE7,stroke:#993C1D
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Security
316
+
317
+ This package reads API keys from environment variables and passes them directly to the provider SDK over HTTPS. Keys are never logged, stored, or transmitted by this package.
318
+
319
+ - Never commit `.env` or `.env.local` — add both to `.gitignore`
320
+ - Never log `process.env` in application code
321
+ - Use deployment secrets (Vercel / AWS Secrets Manager) in production
322
+ - Rotate keys immediately if accidentally exposed
323
+
324
+ ---
325
+
345
326
  ## Running tests
346
327
 
347
328
  ```bash
@@ -349,19 +330,16 @@ try {
349
330
  npm test
350
331
  ```
351
332
 
352
- Expected: 23 passed.
333
+ Expected: 27 passed.
353
334
 
354
335
  ---
355
336
 
356
- ## Publishing
337
+ ## Reference implementation
357
338
 
358
- ```bash
359
- npm run build
360
- npm publish --access public
361
- ```
339
+ See [portfolio-lab](https://github.com/jithinjohnzachariah92/portfolio-lab) for a working Next.js project using this package across multiple AI-powered features.
362
340
 
363
341
  ---
364
342
 
365
343
  ## Repo
366
344
 
367
- [github.com/jithinjohnzachariah92/ai-provider](https://github.com/jithinjohnzachariah92/ai-provider)
345
+ [github.com/jithinjohnzachariah92/ai-provider](https://github.com/jithinjohnzachariah92/ai-provider) · [npmjs.com/package/@jz92/ai-provider](https://www.npmjs.com/package/@jz92/ai-provider)
package/dist/index.cjs CHANGED
@@ -227,13 +227,17 @@ async function buildModel(config) {
227
227
  async function buildOllamaModel(config) {
228
228
  await assertOllamaReachable(config.baseURL ?? "http://localhost:11434");
229
229
  try {
230
- const { createOllama } = await import("ollama-ai-provider");
231
- const ollama = createOllama({ baseURL: `${config.baseURL}/api` });
230
+ const { createOpenAI } = await import("@ai-sdk/openai");
231
+ const ollama = createOpenAI({
232
+ baseURL: `${config.baseURL}/v1`,
233
+ apiKey: "ollama"
234
+ // required by the client but not validated by Ollama
235
+ });
232
236
  return ollama(config.model);
233
237
  } catch (err) {
234
238
  if (isModuleNotFound(err)) {
235
239
  throw new AIProviderError(
236
- "[ai-provider] ollama-ai-provider is not installed.\nRun: npm install ollama-ai-provider",
240
+ "[ai-provider] @ai-sdk/openai is not installed.\nRun: npm install @ai-sdk/openai",
237
241
  "UNKNOWN",
238
242
  "ollama"
239
243
  );
@@ -413,12 +417,13 @@ async function generateStructured(options) {
413
417
  }
414
418
  guardTokenBudget(options.systemPrompt + options.prompt, options.maxInputTokens ?? 8e3);
415
419
  const model = await buildModel(config);
416
- const messages = buildMessages(config, options.systemPrompt, options.prompt);
420
+ const { system, messages } = buildMessages(config, options.systemPrompt, options.prompt);
417
421
  const timeout = TIMEOUT_MS[config.provider];
418
422
  const result = await withRetry(
419
423
  () => withTimeout(
420
424
  () => (0, import_ai.generateText)({
421
425
  model,
426
+ system,
422
427
  messages,
423
428
  output: import_ai.Output.object({ schema: options.schema }),
424
429
  maxOutputTokens: config.maxTokens
@@ -437,11 +442,11 @@ async function generatePlainText(options) {
437
442
  }
438
443
  guardTokenBudget(options.systemPrompt + options.prompt, options.maxInputTokens ?? 8e3);
439
444
  const model = await buildModel(config);
440
- const messages = buildMessages(config, options.systemPrompt, options.prompt);
445
+ const { system, messages } = buildMessages(config, options.systemPrompt, options.prompt);
441
446
  const timeout = TIMEOUT_MS[config.provider];
442
447
  const result = await withRetry(
443
448
  () => withTimeout(
444
- () => (0, import_ai.generateText)({ model, messages, maxOutputTokens: config.maxTokens }),
449
+ () => (0, import_ai.generateText)({ model, system, messages, maxOutputTokens: config.maxTokens }),
445
450
  timeout
446
451
  ),
447
452
  config.provider
@@ -450,26 +455,28 @@ async function generatePlainText(options) {
450
455
  }
451
456
  function buildMessages(config, systemPrompt, userPrompt) {
452
457
  if (config.usePromptCache) {
453
- return [
454
- { role: "system", content: systemPrompt },
455
- {
456
- role: "user",
457
- content: [
458
- {
459
- type: "text",
460
- text: userPrompt,
461
- providerOptions: {
462
- anthropic: { cacheControl: { type: "ephemeral" } }
458
+ return {
459
+ system: systemPrompt,
460
+ messages: [
461
+ {
462
+ role: "user",
463
+ content: [
464
+ {
465
+ type: "text",
466
+ text: userPrompt,
467
+ providerOptions: {
468
+ anthropic: { cacheControl: { type: "ephemeral" } }
469
+ }
463
470
  }
464
- }
465
- ]
466
- }
467
- ];
471
+ ]
472
+ }
473
+ ]
474
+ };
468
475
  }
469
- return [
470
- { role: "system", content: systemPrompt },
471
- { role: "user", content: userPrompt }
472
- ];
476
+ return {
477
+ system: systemPrompt,
478
+ messages: [{ role: "user", content: userPrompt }]
479
+ };
473
480
  }
474
481
  function buildResponse(data, usage, config, cacheKey) {
475
482
  if (cacheKey) responseCache.set(cacheKey, data);
package/dist/index.js CHANGED
@@ -187,13 +187,17 @@ async function buildModel(config) {
187
187
  async function buildOllamaModel(config) {
188
188
  await assertOllamaReachable(config.baseURL ?? "http://localhost:11434");
189
189
  try {
190
- const { createOllama } = await import("ollama-ai-provider");
191
- const ollama = createOllama({ baseURL: `${config.baseURL}/api` });
190
+ const { createOpenAI } = await import("@ai-sdk/openai");
191
+ const ollama = createOpenAI({
192
+ baseURL: `${config.baseURL}/v1`,
193
+ apiKey: "ollama"
194
+ // required by the client but not validated by Ollama
195
+ });
192
196
  return ollama(config.model);
193
197
  } catch (err) {
194
198
  if (isModuleNotFound(err)) {
195
199
  throw new AIProviderError(
196
- "[ai-provider] ollama-ai-provider is not installed.\nRun: npm install ollama-ai-provider",
200
+ "[ai-provider] @ai-sdk/openai is not installed.\nRun: npm install @ai-sdk/openai",
197
201
  "UNKNOWN",
198
202
  "ollama"
199
203
  );
@@ -373,12 +377,13 @@ async function generateStructured(options) {
373
377
  }
374
378
  guardTokenBudget(options.systemPrompt + options.prompt, options.maxInputTokens ?? 8e3);
375
379
  const model = await buildModel(config);
376
- const messages = buildMessages(config, options.systemPrompt, options.prompt);
380
+ const { system, messages } = buildMessages(config, options.systemPrompt, options.prompt);
377
381
  const timeout = TIMEOUT_MS[config.provider];
378
382
  const result = await withRetry(
379
383
  () => withTimeout(
380
384
  () => generateText({
381
385
  model,
386
+ system,
382
387
  messages,
383
388
  output: Output.object({ schema: options.schema }),
384
389
  maxOutputTokens: config.maxTokens
@@ -397,11 +402,11 @@ async function generatePlainText(options) {
397
402
  }
398
403
  guardTokenBudget(options.systemPrompt + options.prompt, options.maxInputTokens ?? 8e3);
399
404
  const model = await buildModel(config);
400
- const messages = buildMessages(config, options.systemPrompt, options.prompt);
405
+ const { system, messages } = buildMessages(config, options.systemPrompt, options.prompt);
401
406
  const timeout = TIMEOUT_MS[config.provider];
402
407
  const result = await withRetry(
403
408
  () => withTimeout(
404
- () => generateText({ model, messages, maxOutputTokens: config.maxTokens }),
409
+ () => generateText({ model, system, messages, maxOutputTokens: config.maxTokens }),
405
410
  timeout
406
411
  ),
407
412
  config.provider
@@ -410,26 +415,28 @@ async function generatePlainText(options) {
410
415
  }
411
416
  function buildMessages(config, systemPrompt, userPrompt) {
412
417
  if (config.usePromptCache) {
413
- return [
414
- { role: "system", content: systemPrompt },
415
- {
416
- role: "user",
417
- content: [
418
- {
419
- type: "text",
420
- text: userPrompt,
421
- providerOptions: {
422
- anthropic: { cacheControl: { type: "ephemeral" } }
418
+ return {
419
+ system: systemPrompt,
420
+ messages: [
421
+ {
422
+ role: "user",
423
+ content: [
424
+ {
425
+ type: "text",
426
+ text: userPrompt,
427
+ providerOptions: {
428
+ anthropic: { cacheControl: { type: "ephemeral" } }
429
+ }
423
430
  }
424
- }
425
- ]
426
- }
427
- ];
431
+ ]
432
+ }
433
+ ]
434
+ };
428
435
  }
429
- return [
430
- { role: "system", content: systemPrompt },
431
- { role: "user", content: userPrompt }
432
- ];
436
+ return {
437
+ system: systemPrompt,
438
+ messages: [{ role: "user", content: userPrompt }]
439
+ };
433
440
  }
434
441
  function buildResponse(data, usage, config, cacheKey) {
435
442
  if (cacheKey) responseCache.set(cacheKey, data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jz92/ai-provider",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Environment-aware AI provider for portfolio projects. Local dev → Ollama, deployed → any cloud provider.",
5
5
  "author": "jz92",
6
6
  "license": "MIT",
@@ -37,8 +37,7 @@
37
37
  "@ai-sdk/openai": ">=3.0.0",
38
38
  "@ai-sdk/google": ">=3.0.0",
39
39
  "@ai-sdk/groq": ">=3.0.0",
40
- "@ai-sdk/mistral": ">=3.0.0",
41
- "ollama-ai-provider": ">=1.0.0"
40
+ "@ai-sdk/mistral": ">=3.0.0"
42
41
  },
43
42
  "peerDependenciesMeta": {
44
43
  "@ai-sdk/anthropic": {
@@ -55,9 +54,6 @@
55
54
  },
56
55
  "@ai-sdk/mistral": {
57
56
  "optional": true
58
- },
59
- "ollama-ai-provider": {
60
- "optional": true
61
57
  }
62
58
  },
63
59
  "devDependencies": {
@@ -6,7 +6,6 @@ const cyan = '\x1b[36m'
6
6
  const yellow = '\x1b[33m'
7
7
  const green = '\x1b[32m'
8
8
  const dim = '\x1b[2m'
9
- const red = '\x1b[31m'
10
9
 
11
10
  console.log(`
12
11
  ${bold}@jz92/ai-provider${reset} installed successfully.
@@ -23,13 +22,10 @@ ${bold}Cloud providers — install only what you use:${reset}
23
22
  ${cyan}npm install @ai-sdk/groq${reset} ${dim}→ GROQ_API_KEY https://console.groq.com/keys${reset}
24
23
  ${cyan}npm install @ai-sdk/mistral${reset} ${dim}→ MISTRAL_API_KEY https://console.mistral.ai${reset}
25
24
 
26
- ${bold}Local dev with Ollama (devDependency — not for production):${reset}
27
- ${cyan}npm install ollama-ai-provider --save-dev --legacy-peer-deps${reset}
28
- ${dim}Then: brew install ollama && ollama pull qwen2.5-coder:14b${reset}
29
-
30
- ${red}Note:${reset} ollama-ai-provider@1.2.0 conflicts with zod@4.
31
- ${dim}--legacy-peer-deps is required until ollama-ai-provider updates.${reset}
32
- ${dim}This does not affect production builds — devDependencies are excluded on Vercel/AWS.${reset}
25
+ ${bold}Local dev with Ollama:${reset}
26
+ ${dim}Ollama uses @ai-sdk/openai pointed at localhost:11434/v1${reset}
27
+ ${dim}No extra package needed — just install Ollama and pull a model:${reset}
28
+ ${cyan}brew install ollama && ollama pull qwen2.5-coder:14b${reset}
33
29
 
34
30
  ${bold}Set NODE_ENV in your .env:${reset}
35
31
  ${dim}development${reset} → Ollama (free, local)