@holokai/holo-provider-ollama 0.1.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +750 -0
  3. package/dist/index.d.ts +17 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +17 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/manifest.d.ts +3 -0
  8. package/dist/manifest.d.ts.map +1 -0
  9. package/dist/manifest.js +136 -0
  10. package/dist/manifest.js.map +1 -0
  11. package/dist/ollama.auditor.d.ts +22 -0
  12. package/dist/ollama.auditor.d.ts.map +1 -0
  13. package/dist/ollama.auditor.js +151 -0
  14. package/dist/ollama.auditor.js.map +1 -0
  15. package/dist/ollama.provider.d.ts +14 -0
  16. package/dist/ollama.provider.d.ts.map +1 -0
  17. package/dist/ollama.provider.js +88 -0
  18. package/dist/ollama.provider.js.map +1 -0
  19. package/dist/ollama.response.factory.d.ts +7 -0
  20. package/dist/ollama.response.factory.d.ts.map +1 -0
  21. package/dist/ollama.response.factory.js +9 -0
  22. package/dist/ollama.response.factory.js.map +1 -0
  23. package/dist/ollama.translator.d.ts +22 -0
  24. package/dist/ollama.translator.d.ts.map +1 -0
  25. package/dist/ollama.translator.js +90 -0
  26. package/dist/ollama.translator.js.map +1 -0
  27. package/dist/ollama.wire.adapter.d.ts +6 -0
  28. package/dist/ollama.wire.adapter.d.ts.map +1 -0
  29. package/dist/ollama.wire.adapter.js +11 -0
  30. package/dist/ollama.wire.adapter.js.map +1 -0
  31. package/dist/plugin.d.ts +19 -0
  32. package/dist/plugin.d.ts.map +1 -0
  33. package/dist/plugin.js +57 -0
  34. package/dist/plugin.js.map +1 -0
  35. package/dist/translators/index.d.ts +9 -0
  36. package/dist/translators/index.d.ts.map +1 -0
  37. package/dist/translators/index.js +9 -0
  38. package/dist/translators/index.js.map +1 -0
  39. package/dist/translators/ollama.chat.request.translator.d.ts +20 -0
  40. package/dist/translators/ollama.chat.request.translator.d.ts.map +1 -0
  41. package/dist/translators/ollama.chat.request.translator.js +100 -0
  42. package/dist/translators/ollama.chat.request.translator.js.map +1 -0
  43. package/dist/translators/ollama.chat.response.translator.d.ts +22 -0
  44. package/dist/translators/ollama.chat.response.translator.d.ts.map +1 -0
  45. package/dist/translators/ollama.chat.response.translator.js +127 -0
  46. package/dist/translators/ollama.chat.response.translator.js.map +1 -0
  47. package/dist/translators/ollama.generate.request.translator.d.ts +13 -0
  48. package/dist/translators/ollama.generate.request.translator.d.ts.map +1 -0
  49. package/dist/translators/ollama.generate.request.translator.js +90 -0
  50. package/dist/translators/ollama.generate.request.translator.js.map +1 -0
  51. package/dist/translators/ollama.generate.response.translator.d.ts +20 -0
  52. package/dist/translators/ollama.generate.response.translator.d.ts.map +1 -0
  53. package/dist/translators/ollama.generate.response.translator.js +104 -0
  54. package/dist/translators/ollama.generate.response.translator.js.map +1 -0
  55. package/dist/translators/ollama.message.translators.d.ts +14 -0
  56. package/dist/translators/ollama.message.translators.d.ts.map +1 -0
  57. package/dist/translators/ollama.message.translators.js +117 -0
  58. package/dist/translators/ollama.message.translators.js.map +1 -0
  59. package/dist/translators/ollama.options.translators.d.ts +11 -0
  60. package/dist/translators/ollama.options.translators.d.ts.map +1 -0
  61. package/dist/translators/ollama.options.translators.js +37 -0
  62. package/dist/translators/ollama.options.translators.js.map +1 -0
  63. package/dist/translators/ollama.tool.translators.d.ts +13 -0
  64. package/dist/translators/ollama.tool.translators.d.ts.map +1 -0
  65. package/dist/translators/ollama.tool.translators.js +62 -0
  66. package/dist/translators/ollama.tool.translators.js.map +1 -0
  67. package/dist/translators/streaming/index.d.ts +5 -0
  68. package/dist/translators/streaming/index.d.ts.map +1 -0
  69. package/dist/translators/streaming/index.js +5 -0
  70. package/dist/translators/streaming/index.js.map +1 -0
  71. package/dist/translators/streaming/ollama.content.delta.translator.d.ts +14 -0
  72. package/dist/translators/streaming/ollama.content.delta.translator.d.ts.map +1 -0
  73. package/dist/translators/streaming/ollama.content.delta.translator.js +76 -0
  74. package/dist/translators/streaming/ollama.content.delta.translator.js.map +1 -0
  75. package/dist/translators/streaming/ollama.message.delta.translator.d.ts +15 -0
  76. package/dist/translators/streaming/ollama.message.delta.translator.d.ts.map +1 -0
  77. package/dist/translators/streaming/ollama.message.delta.translator.js +116 -0
  78. package/dist/translators/streaming/ollama.message.delta.translator.js.map +1 -0
  79. package/dist/translators/streaming/ollama.message.stop.translator.d.ts +16 -0
  80. package/dist/translators/streaming/ollama.message.stop.translator.d.ts.map +1 -0
  81. package/dist/translators/streaming/ollama.message.stop.translator.js +87 -0
  82. package/dist/translators/streaming/ollama.message.stop.translator.js.map +1 -0
  83. package/dist/translators/streaming/ollama.stream.translator.d.ts +20 -0
  84. package/dist/translators/streaming/ollama.stream.translator.d.ts.map +1 -0
  85. package/dist/translators/streaming/ollama.stream.translator.js +82 -0
  86. package/dist/translators/streaming/ollama.stream.translator.js.map +1 -0
  87. package/dist/types/index.d.ts +6 -0
  88. package/dist/types/index.d.ts.map +1 -0
  89. package/dist/types/index.js +11 -0
  90. package/dist/types/index.js.map +1 -0
  91. package/dist/types/request.types.d.ts +13 -0
  92. package/dist/types/request.types.d.ts.map +1 -0
  93. package/dist/types/request.types.js +8 -0
  94. package/dist/types/request.types.js.map +1 -0
  95. package/dist/types/response.types.d.ts +40 -0
  96. package/dist/types/response.types.d.ts.map +1 -0
  97. package/dist/types/response.types.js +9 -0
  98. package/dist/types/response.types.js.map +1 -0
  99. package/package.json +70 -0
package/README.md ADDED
@@ -0,0 +1,750 @@
1
+ # @holokai/holo-provider-ollama
2
+
3
+ > **Official Ollama provider plugin for Holo LLM Gateway**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@holokai/holo-provider-ollama.svg)](https://www.npmjs.com/package/@holokai/holo-provider-ollama)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ The Ollama provider plugin enables Holo to communicate with locally-hosted Ollama models through the universal Holo format. This plugin is part of the migration from the monolithic provider architecture to a plugin-based system, providing complete bidirectional translation between Ollama's native API and the portable Holo format.
13
+
14
+ ### Key Features
15
+
16
+ - ✅ **Full Holo SDK Integration** - Uses `@holokai/sdk` types for strict type safety
17
+ - ✅ **Bidirectional Translation** - Ollama ↔ Holo format with lossless core fields
18
+ - ✅ **Dual Mode Support** - Both Chat and Generate endpoints
19
+ - ✅ **Streaming Support** - Frame-based streaming with proper orchestration
20
+ - ✅ **Tool Calling** - Function calling support (Chat mode only)
21
+ - ✅ **Vision/Multimodal** - Image support via URLs and base64
22
+ - ✅ **Local Deployment** - No external API dependencies
23
+ - ✅ **Plugin Architecture** - Auto-discovered, hot-reloadable, independently versioned
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install @holokai/holo-provider-ollama
31
+ ```
32
+
33
+ ### Peer Dependencies
34
+
35
+ This plugin requires:
36
+ - `@holokai/sdk` ^0.1.0 - Holo universal format types and plugin contracts
37
+ - `ollama` ^0.6.3 - Official Ollama JavaScript SDK
38
+
39
+ ### Prerequisites
40
+
41
+ - Ollama must be installed and running locally: https://ollama.com/download
42
+ - Default endpoint: `http://localhost:11434`
43
+
44
+ ---
45
+
46
+ ## Quick Start
47
+
48
+ ### Automatic Discovery
49
+
50
+ When installed in a Holo worker environment, this plugin is automatically discovered and loaded by the plugin system. No manual registration required.
51
+
52
+ ### Configuration
53
+
54
+ Add a provider configuration to your Holo deployment:
55
+
56
+ ```json
57
+ {
58
+ "id": "ollama-local",
59
+ "provider_type": "ollama",
60
+ "plugin_id": "@holokai/holo-provider-ollama",
61
+ "base_url": "http://localhost:11434",
62
+ "model": "llama2",
63
+ "config": {
64
+ "defaultModel": "llama2",
65
+ "timeoutMs": 60000
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Usage in Code
71
+
72
+ ```typescript
73
+ import { HoloRequest, HoloResponse } from '@holokai/sdk';
74
+
75
+ const request: HoloRequest = {
76
+ model: 'llama2',
77
+ messages: [
78
+ { role: 'user', content: 'Explain quantum entanglement briefly.' }
79
+ ],
80
+ max_tokens: 500,
81
+ temperature: 0.7
82
+ };
83
+
84
+ // Plugin handles translation automatically
85
+ const response: HoloResponse = await holoClient.chat(request);
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Migration from Monolithic Architecture
91
+
92
+ ### What Changed
93
+
94
+ This plugin represents the extraction of Ollama provider logic from the monolithic `src/providers/ollama/` codebase into a standalone, independently versioned package.
95
+
96
+ **Before** (Monolithic):
97
+ ```
98
+ src/providers/ollama/
99
+ ├── ollama.translator.ts
100
+ ├── translators/
101
+ │ ├── chat/
102
+ │ └── generate/
103
+ ├── streaming/
104
+ └── types/
105
+ ```
106
+
107
+ **After** (Plugin):
108
+ ```
109
+ @holokai/holo-provider-ollama
110
+ ├── src/
111
+ │ ├── plugin.ts # Plugin entrypoint
112
+ │ ├── manifest.ts # Plugin metadata
113
+ │ ├── ollama.provider.ts # Provider implementation
114
+ │ └── translators/ # Translation logic (preserved)
115
+ └── package.json
116
+ ```
117
+
118
+ ### Migration Benefits
119
+
120
+ 1. **Independent Versioning** - Update Ollama support without core releases
121
+ 2. **Hot Reload** - Deploy new Ollama versions without downtime
122
+ 3. **Type Safety** - Strict SDK types eliminate `Record<string, unknown>`
123
+ 4. **Reduced Coupling** - Plugin contracts enforce clean boundaries
124
+ 5. **Local First** - No external API keys or dependencies
125
+
126
+ ### Breaking Changes
127
+
128
+ - **Import paths changed**: Use `@holokai/sdk` for types instead of `../../types`
129
+ - **Configuration schema**: Now validated via plugin manifest
130
+ - **Dependency injection**: Uses plugin container instead of core DI
131
+
132
+ ---
133
+
134
+ ## Architecture
135
+
136
+ ### Plugin Structure
137
+
138
+ ```
139
+ @holokai/holo-provider-ollama/
140
+ ├── src/
141
+ │ ├── plugin.ts # ProviderPlugin implementation
142
+ │ ├── manifest.ts # Plugin metadata & config schema
143
+ │ ├── ollama.provider.ts # Core provider logic
144
+ │ ├── ollama.translator.ts # Main translator facade
145
+ │ ├── translators/
146
+ │ │ ├── ollama.chat.request.translator.ts
147
+ │ │ ├── ollama.chat.response.translator.ts
148
+ │ │ ├── ollama.generate.request.translator.ts
149
+ │ │ ├── ollama.generate.response.translator.ts
150
+ │ │ ├── ollama.message.translator.ts
151
+ │ │ └── streaming/
152
+ │ │ ├── ollama.stream.translator.ts # Orchestrator
153
+ │ │ ├── ollama.content.delta.translator.ts
154
+ │ │ ├── ollama.message.delta.translator.ts
155
+ │ │ └── ollama.message.stop.translator.ts
156
+ │ ├── types/
157
+ │ │ └── (Re-exports from ollama SDK)
158
+ │ └── utils/
159
+ │ └── (Helper functions)
160
+ └── package.json
161
+ ```
162
+
163
+ ### Translation Flow
164
+
165
+ ```
166
+ ┌─────────────────┐
167
+ │ Holo Request │
168
+ │ (SDK types) │
169
+ └────────┬────────┘
170
+ │
171
+ ↓
172
+ ┌─────────────────────────┐
173
+ │ OllamaRequestTranslator │
174
+ │ - Detects mode │
175
+ │ - Maps to Chat/Gen │
176
+ │ - Nests in options │
177
+ └────────┬────────────────┘
178
+ │
179
+ ↓
180
+ ┌─────────────────┐
181
+ │ Ollama API │
182
+ │ (local/remote) │
183
+ └────────┬────────┘
184
+ │
185
+ ↓
186
+ ┌──────────────────────────┐
187
+ │ OllamaResponseTranslator │
188
+ │ - Detects mode │
189
+ │ - Synthesizes ID │
190
+ │ - Converts timestamp │
191
+ └────────┬─────────────────┘
192
+ │
193
+ ↓
194
+ ┌─────────────────┐
195
+ │ Holo Response │
196
+ │ (SDK types) │
197
+ └─────────────────┘
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Holo Format Mapping
203
+
204
+ This plugin implements the official Holo format mappings as documented in the SDK.
205
+
206
+ ### Request Mapping: Holo → Ollama
207
+
208
+ | Holo Field | Ollama Field | Transformation | Notes |
209
+ |------------|-------------|----------------|-------|
210
+ | **Direct 1:1** ||||
211
+ | `model` | `model` | Direct | Required |
212
+ | `stream` | `stream` | Direct | Optional |
213
+ | **Structure Transforms** ||||
214
+ | `system` (Chat) | First message with `role:'system'` | Inject as message | Optional |
215
+ | `system` (Generate) | `system` | Top-level field | Optional |
216
+ | `messages` (Chat) | `messages` | Flatten to text + extract images | Required |
217
+ | `messages` (Generate) | `prompt` | Extract from single user message | Converted to string |
218
+ | `temperature` | `options.temperature` | Nest in options | Optional |
219
+ | `top_p` | `options.top_p` | Nest in options | Optional |
220
+ | `top_k` | `options.top_k` | Nest in options | Optional |
221
+ | `max_tokens` | `options.num_predict` | Nest + rename | Optional |
222
+ | `stop_sequences` | `options.stop` | Nest in options | Array format |
223
+ | `frequency_penalty` | `options.frequency_penalty` | Nest in options | Optional |
224
+ | `presence_penalty` | `options.presence_penalty` | Nest in options | Optional |
225
+ | `seed` | `options.seed` | Nest in options | Optional |
226
+ | `response_format.type: 'json_object'` | `format: "json"` | Map to string | Optional |
227
+ | `response_format.schema` | `format: {...}` | Pass schema object | Optional |
228
+ | `tools` (Chat) | `tools` | Direct | Chat mode only |
229
+
230
+ **Dropped Fields** (Holo → Ollama):
231
+ - `tool_choice` - Ollama doesn't support explicit tool selection (log warning)
232
+ - `service_tier` - Not applicable to local models
233
+ - `metadata` - Provider-specific field
234
+
235
+ **Ollama-Specific Fields** (not in Holo):
236
+ - `keep_alive` - Model memory duration (handled via config)
237
+ - `options.*` - Runtime-specific hardware options
238
+ - `raw` - Skip prompt formatting (Generate mode only)
239
+
240
+ ### Response Mapping: Ollama → Holo
241
+
242
+ | Ollama Field | Holo Field | Transformation | Notes |
243
+ |-------------|------------|----------------|-------|
244
+ | **Direct 1:1** ||||
245
+ | `model` | `model` | Direct | Always present |
246
+ | `message.role` (Chat) | `messages[0].role` | Wrap in array | Always 'assistant' |
247
+ | `message.content` (Chat) | `messages[0].content` | Direct | Text content |
248
+ | `response` (Generate) | `messages[0].content` | Wrap in message | Text content |
249
+ | `message.tool_calls` | `messages[0].tool_calls` | Direct | If present |
250
+ | **Structure Transforms** ||||
251
+ | N/A | `id` | Synthesize UUID | Ollama lacks ID |
252
+ | `created_at` | `created` | Parse ISO8601 to ms | `Date.parse(created_at)` |
253
+ | `done_reason: 'stop'` | `finish_reason: 'stop'` | Direct | Optional |
254
+ | `done_reason: 'length'` | `finish_reason: 'length'` | Direct | Optional |
255
+ | `done: true` with no `done_reason` | `finish_reason: 'stop'` | Default | Fallback behavior |
256
+ | `prompt_eval_count` | `usage.input_tokens` | Rename | Optional |
257
+ | `eval_count` | `usage.output_tokens` | Rename | Optional |
258
+ | Computed | `usage.total_tokens` | `input + output` | Derived |
259
+ | `total_duration` (ns) | `usage.timings.total` | Direct | Optional; nanoseconds |
260
+ | `load_duration` (ns) | `usage.timings.load` | Direct | Optional; nanoseconds |
261
+ | `prompt_eval_duration` (ns) | `usage.timings.prompt_eval` | Direct | Optional; nanoseconds |
262
+ | `eval_duration` (ns) | `usage.timings.eval` | Direct | Optional; nanoseconds |
263
+
264
+ **ID Synthesis**:
265
+ - Ollama responses do NOT include `id` fields
266
+ - Translators MUST synthesize UUIDs for all responses and streaming chunks
267
+ - For streaming: Generate once at start, reuse across all chunks
268
+
269
+ **Timestamp Conversion**:
270
+ - Ollama: ISO8601 string (e.g., `"2024-01-01T12:00:00Z"`)
271
+ - Holo: Milliseconds since epoch (`number`)
272
+ - Conversion: `Date.parse(created_at)`
273
+
274
+ **Finish Reason Mapping**:
275
+
276
+ | Ollama `done_reason` | Holo `finish_reason` | Notes |
277
+ |---------------------|---------------------|-------|
278
+ | `'stop'` | `'stop'` | Natural completion |
279
+ | `'length'` | `'length'` | Hit token limit |
280
+ | `null` or missing + `done: true` | `'stop'` | Default fallback |
281
+
282
+ ### Content Mapping
283
+
284
+ #### Text Content
285
+
286
+ ```typescript
287
+ // Holo
288
+ { type: 'text', text: 'Hello' }
289
+
290
+ // Ollama Chat (flattened)
291
+ { role: 'user', content: 'Hello' }
292
+
293
+ // Ollama Generate (string)
294
+ { prompt: 'Hello' }
295
+ ```
296
+
297
+ #### Image Content
298
+
299
+ ```typescript
300
+ // Holo
301
+ {
302
+ role: 'user',
303
+ content: [
304
+ { type: 'text', text: 'What is in this image?' },
305
+ { type: 'image', url: 'https://example.com/image.png' }
306
+ ]
307
+ }
308
+
309
+ // Ollama Chat (extracted to images array)
310
+ {
311
+ role: 'user',
312
+ content: 'What is in this image?',
313
+ images: ['https://example.com/image.png']
314
+ }
315
+
316
+ // Ollama Generate (NOT SUPPORTED)
317
+ // Images cannot be used in Generate mode
318
+ ```
319
+
320
+ #### Tool Calls
321
+
322
+ ```typescript
323
+ // Ollama Response (OpenAI-style)
324
+ {
325
+ message: {
326
+ role: 'assistant',
327
+ content: '',
328
+ tool_calls: [{
329
+ id: 'call_abc',
330
+ type: 'function',
331
+ function: { name: 'get_weather', arguments: { location: 'SF' } }
332
+ }]
333
+ }
334
+ }
335
+
336
+ // Holo Response (direct mapping)
337
+ {
338
+ messages: [{
339
+ role: 'assistant',
340
+ content: '',
341
+ tool_calls: [{
342
+ id: 'call_abc',
343
+ type: 'function',
344
+ function: { name: 'get_weather', arguments: { location: 'SF' } }
345
+ }]
346
+ }]
347
+ }
348
+ ```
349
+
350
+ **Note**: Ollama uses OpenAI-style tool call format, so mapping is direct (no extraction needed).
351
+
352
+ ---
353
+
354
+ ## Dual Mode Support
355
+
356
+ ### Chat Mode vs Generate Mode
357
+
358
+ Ollama provides two distinct endpoints with different capabilities:
359
+
360
+ | Feature | Chat Mode (`/api/chat`) | Generate Mode (`/api/generate`) |
361
+ |---------|------------------------|--------------------------------|
362
+ | **Endpoint** | `/api/chat` | `/api/generate` |
363
+ | **Input** | `messages[]` array | `prompt` string |
364
+ | **Conversation** | ✅ Multi-turn history | ❌ Single prompt only |
365
+ | **System Prompt** | ✅ As first message | ✅ Top-level field |
366
+ | **Tools** | ✅ Function calling | ❌ Not supported |
367
+ | **Images** | ✅ Via `images[]` | ❌ Not supported |
368
+ | **Context Continuation** | ❌ Use message history | ✅ Via `context` array |
369
+ | **Use Case** | Interactive chat | Single completion |
370
+
371
+ ### Mode Selection
372
+
373
+ The plugin automatically selects the appropriate mode based on the request:
374
+
375
+ ```typescript
376
+ // Auto-detected as Chat Mode (has tools)
377
+ const chatRequest: HoloRequest = {
378
+ model: 'llama2',
379
+ messages: [{ role: 'user', content: 'What is the weather?' }],
380
+ tools: [{ name: 'get_weather', parameters: {...} }]
381
+ };
382
+
383
+ // Can be either mode (no tools, single message)
384
+ const simpleRequest: HoloRequest = {
385
+ model: 'llama2',
386
+ messages: [{ role: 'user', content: 'Complete this sentence...' }]
387
+ };
388
+
389
+ // Force Generate Mode via provider config
390
+ const generateRequest: HoloRequest = {
391
+ model: 'llama2',
392
+ messages: [{ role: 'user', content: 'Complete: Once upon a time' }],
393
+ provider_config: {
394
+ mode: 'generate' // Force Generate endpoint
395
+ }
396
+ };
397
+ ```
398
+
399
+ **Default**: Chat mode is preferred unless explicitly configured otherwise.
400
+
401
+ ---
402
+
403
+ ## Streaming
404
+
405
+ ### Frame-Based Streaming
406
+
407
+ Ollama uses a simpler frame-based streaming model compared to Claude's event-based approach:
408
+
409
+ #### Streaming Frames
410
+
411
+ ```typescript
412
+ // Frame 1: First content
413
+ {
414
+ model: 'llama2',
415
+ created_at: '2024-01-01T12:00:00Z',
416
+ message: { role: 'assistant', content: 'Hello' }, // Chat mode
417
+ // response: 'Hello', // Generate mode
418
+ done: false
419
+ }
420
+
421
+ // Frame 2: More content
422
+ {
423
+ model: 'llama2',
424
+ created_at: '2024-01-01T12:00:01Z',
425
+ message: { role: 'assistant', content: ' there' },
426
+ done: false
427
+ }
428
+
429
+ // Frame 3: Final frame with usage
430
+ {
431
+ model: 'llama2',
432
+ created_at: '2024-01-01T12:00:02Z',
433
+ message: { role: 'assistant', content: '!' },
434
+ done: true,
435
+ done_reason: 'stop',
436
+ prompt_eval_count: 10,
437
+ eval_count: 3,
438
+ total_duration: 1500000000 // nanoseconds
439
+ }
440
+ ```
441
+
442
+ #### Holo Mapping
443
+
444
+ The plugin translates Ollama frames to Holo streaming events:
445
+
446
+ | Ollama Frame | Holo Event | Notes |
447
+ |--------------|-----------|-------|
448
+ | First frame (`done: false`) | `message_start` + `content_delta` | Synthesized by orchestrator |
449
+ | Content frames (`done: false`) | `content_delta` | Incremental text |
450
+ | Final frame (`done: true`) | `message_delta` + `message_stop` | Usage + completion |
451
+
452
+ ### Streaming Example
453
+
454
+ ```typescript
455
+ import { HoloStreamChunk } from '@holokai/sdk';
456
+
457
+ const stream = await ollamaProvider.streamChat(request);
458
+
459
+ for await (const chunk: HoloStreamChunk of stream) {
460
+ switch (chunk.delta?.type) {
461
+ case 'message_start':
462
+ console.log('Message started:', chunk.id);
463
+ break;
464
+ case 'content_delta':
465
+ process.stdout.write(chunk.delta.delta.content ?? '');
466
+ break;
467
+ case 'message_delta':
468
+ console.log('Usage:', chunk.usage);
469
+ break;
470
+ case 'message_stop':
471
+ console.log('Complete. Reason:', chunk.finish_reason);
472
+ break;
473
+ }
474
+ }
475
+ ```
476
+
477
+ ---
478
+
479
+ ## Ollama-Specific Features
480
+
481
+ ### Keep Alive
482
+
483
+ Control how long models stay in memory:
484
+
485
+ ```typescript
486
+ const request: HoloRequest = {
487
+ model: 'llama2',
488
+ messages: [{ role: 'user', content: 'Hello' }],
489
+ provider_config: {
490
+ keep_alive: '5m' // Keep model loaded for 5 minutes
491
+ // or: keep_alive: 300 // 300 seconds
492
+ }
493
+ };
494
+ ```
495
+
496
+ ### Hardware Control
497
+
498
+ Configure GPU and NUMA settings:
499
+
500
+ ```typescript
501
+ const request: HoloRequest = {
502
+ model: 'llama2',
503
+ messages: [{ role: 'user', content: 'Hello' }],
504
+ provider_config: {
505
+ options: {
506
+ num_gpu: 1, // Number of GPUs to use
507
+ main_gpu: 0, // Primary GPU index
508
+ numa: true // Enable NUMA optimization
509
+ }
510
+ }
511
+ };
512
+ ```
513
+
514
+ ### Context Window Override
515
+
516
+ Override model's default context size:
517
+
518
+ ```typescript
519
+ const request: HoloRequest = {
520
+ model: 'llama2',
521
+ messages: [{ role: 'user', content: 'Hello' }],
522
+ provider_config: {
523
+ options: {
524
+ num_ctx: 4096 // Override context window
525
+ }
526
+ }
527
+ };
528
+ ```
529
+
530
+ ### Raw Mode (Generate Only)
531
+
532
+ Skip prompt formatting in Generate mode:
533
+
534
+ ```typescript
535
+ const request: HoloRequest = {
536
+ model: 'llama2',
537
+ messages: [{ role: 'user', content: 'Raw prompt text' }],
538
+ provider_config: {
539
+ mode: 'generate',
540
+ raw: true // Skip Ollama's prompt template
541
+ }
542
+ };
543
+ ```
544
+
545
+ ---
546
+
547
+ ## Type Safety
548
+
549
+ ### SDK Integration
550
+
551
+ This plugin uses strict SDK types exclusively:
552
+
553
+ ```typescript
554
+ import type {
555
+ HoloRequest,
556
+ HoloResponse,
557
+ HoloMessage,
558
+ HoloTool,
559
+ HoloJsonSchema
560
+ } from '@holokai/sdk';
561
+
562
+ // ❌ NO: Record<string, unknown>
563
+ // ✅ YES: Proper SDK types
564
+ ```
565
+
566
+ ### Migration from Legacy Types
567
+
568
+ **Before** (Legacy provider):
569
+ ```typescript
570
+ import { HoloTool } from '../../types/holo/requests';
571
+
572
+ interface HoloTool {
573
+ parameters?: Record<string, unknown>; // ❌ Loose typing
574
+ }
575
+ ```
576
+
577
+ **After** (Plugin SDK):
578
+ ```typescript
579
+ import type { HoloTool, HoloJsonSchema } from '@holokai/sdk';
580
+
581
+ interface HoloTool {
582
+ parameters?: HoloJsonSchema; // ✅ Strict JSON Schema Draft 7
583
+ }
584
+ ```
585
+
586
+ ### Type Safety
587
+
588
+ All interfaces use strict TypeScript types from `@holokai/sdk` for compile-time validation.
589
+
590
+ ---
591
+
592
+ ## Configuration Schema
593
+
594
+ The plugin exposes a JSON Schema for configuration validation:
595
+
596
+ ```typescript
597
+ {
598
+ baseUrl?: string; // Ollama endpoint (default: http://localhost:11434)
599
+ defaultModel?: string; // Fallback model (e.g., "llama2")
600
+ allowedModels?: string[]; // Model allowlist
601
+ timeoutMs?: number; // Request timeout (default: 60000)
602
+ maxRetries?: number; // Retry attempts (default: 2)
603
+ defaultKeepAlive?: string; // Default keep_alive ("5m", 300)
604
+ logRequests?: boolean; // Observability (default: false)
605
+ }
606
+ ```
607
+
608
+ See [manifest.ts](./src/manifest.ts) for the complete schema.
609
+
610
+ ---
611
+
612
+ ## Development
613
+
614
+ ### Setup
615
+
616
+ ```bash
617
+ # Install dependencies
618
+ npm install
619
+
620
+ # Build
621
+ npm run build
622
+
623
+ # Type checking
624
+ npm run type-check
625
+
626
+ # Run tests
627
+ npm test
628
+ ```
629
+
630
+ ### Testing
631
+
632
+ ```bash
633
+ # Unit tests
634
+ npm test
635
+
636
+ # Integration tests (requires Ollama running)
637
+ npm run test:integration
638
+
639
+ # Watch mode
640
+ npm run test:watch
641
+ ```
642
+
643
+ ### Building
644
+
645
+ ```bash
646
+ # Production build
647
+ npm run build
648
+
649
+ # Watch mode
650
+ npm run build:watch
651
+
652
+ # Clean
653
+ npm run clean
654
+ ```
655
+
656
+ ---
657
+
658
+ ## Known Issues & Workarounds
659
+
660
+ ### Missing Response IDs
661
+
662
+ **Issue**: Ollama responses don't include `id` fields.
663
+
664
+ **Workaround**: Plugin automatically synthesizes UUIDs for all responses. For streaming, the same ID is used across all chunks in a session.
665
+
666
+ ### Timestamp Format
667
+
668
+ **Issue**: Ollama returns timestamps as ISO8601 strings, not milliseconds.
669
+
670
+ **Workaround**: Plugin automatically converts to milliseconds: `Date.parse(created_at)`.
671
+
672
+ ### Missing Finish Reasons
673
+
674
+ **Issue**: Some models return `done: true` without `done_reason`.
675
+
676
+ **Workaround**: Plugin defaults to `finish_reason: 'stop'` when missing.
677
+
678
+ ### Tool Choice Not Supported
679
+
680
+ **Issue**: Ollama doesn't support explicit `tool_choice` like Claude/OpenAI.
681
+
682
+ **Behavior**: Plugin logs a warning and ignores `tool_choice` field. Model auto-selects tools when provided.
683
+
684
+ ### Empty Streaming Frames
685
+
686
+ **Issue**: Ollama may emit empty frames (`content: ""`) during slow tokenization.
687
+
688
+ **Workaround**: Plugin skips empty frames to avoid emitting no-op events.
689
+
690
+ ---
691
+
692
+ ## Related Documentation
693
+
694
+ ### SDK Documentation
695
+ - [SDK README](../sdk/README.md) - Plugin development guide and templates
696
+
697
+ ### Ollama Documentation
698
+ - [Official API Docs](https://github.com/ollama/ollama/blob/main/docs/api.md)
699
+ - [Model Library](https://ollama.com/library)
700
+ - [Model Files](https://github.com/ollama/ollama/blob/main/docs/modelfile.md)
701
+ - [FAQ](https://github.com/ollama/ollama/blob/main/docs/faq.md)
702
+
703
+ ### Migration Notes
704
+ - This plugin was extracted from the monolithic `src/providers/ollama/` codebase
705
+ - Migration to plugin architecture is complete
706
+
707
+ ---
708
+
709
+ ## Contributing
710
+
711
+ ### Adding Features
712
+
713
+ 1. Update types in `@holokai/sdk` first (if needed)
714
+ 2. Implement translator logic
715
+ 3. Write tests (unit + integration)
716
+ 4. Update this README
717
+
718
+ ### Reporting Issues
719
+
720
+ Found a bug or have a feature request?
721
+ - GitHub Issues: https://github.com/holokai/holo-provider-ollama/issues
722
+ - Include: Holo version, Ollama version, model name, request/response samples
723
+
724
+ ---
725
+
726
+ ## License
727
+
728
+ MIT © Holokai
729
+
730
+ ---
731
+
732
+ ## Changelog
733
+
734
+ ### v0.1.0 (Current)
735
+ - ✅ Initial plugin release
736
+ - ✅ Extracted from monolithic architecture
737
+ - ✅ Migrated to SDK types
738
+ - ✅ Validated against Holo format spec
739
+ - ✅ Dual mode support (Chat + Generate)
740
+ - ✅ Complete streaming orchestration
741
+ - ✅ Tool calling support
742
+ - ✅ Vision/multimodal support
743
+ - ✅ Local model deployment
744
+
745
+ ---
746
+
747
+ **Last Updated**: 2025-12-18
748
+ **Plugin Version**: 0.1.0
749
+ **SDK Version**: ^0.1.0
750
+ **Ollama SDK**: ^0.6.3