@holokai/holo-provider-openai 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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +771 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +16 -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 +163 -0
  10. package/dist/manifest.js.map +1 -0
  11. package/dist/openai.auditor.d.ts +15 -0
  12. package/dist/openai.auditor.d.ts.map +1 -0
  13. package/dist/openai.auditor.js +164 -0
  14. package/dist/openai.auditor.js.map +1 -0
  15. package/dist/openai.provider.d.ts +24 -0
  16. package/dist/openai.provider.d.ts.map +1 -0
  17. package/dist/openai.provider.js +111 -0
  18. package/dist/openai.provider.js.map +1 -0
  19. package/dist/openai.response.factory.d.ts +8 -0
  20. package/dist/openai.response.factory.d.ts.map +1 -0
  21. package/dist/openai.response.factory.js +15 -0
  22. package/dist/openai.response.factory.js.map +1 -0
  23. package/dist/openai.translator.d.ts +22 -0
  24. package/dist/openai.translator.d.ts.map +1 -0
  25. package/dist/openai.translator.js +74 -0
  26. package/dist/openai.translator.js.map +1 -0
  27. package/dist/openai.wire.adapter.d.ts +12 -0
  28. package/dist/openai.wire.adapter.d.ts.map +1 -0
  29. package/dist/openai.wire.adapter.js +17 -0
  30. package/dist/openai.wire.adapter.js.map +1 -0
  31. package/dist/plugin.d.ts +14 -0
  32. package/dist/plugin.d.ts.map +1 -0
  33. package/dist/plugin.js +59 -0
  34. package/dist/plugin.js.map +1 -0
  35. package/dist/services/index.d.ts +2 -0
  36. package/dist/services/index.d.ts.map +1 -0
  37. package/dist/services/index.js +2 -0
  38. package/dist/services/index.js.map +1 -0
  39. package/dist/services/openai.chatcompletions.service.d.ts +12 -0
  40. package/dist/services/openai.chatcompletions.service.d.ts.map +1 -0
  41. package/dist/services/openai.chatcompletions.service.js +66 -0
  42. package/dist/services/openai.chatcompletions.service.js.map +1 -0
  43. package/dist/translators/index.d.ts +10 -0
  44. package/dist/translators/index.d.ts.map +1 -0
  45. package/dist/translators/index.js +10 -0
  46. package/dist/translators/index.js.map +1 -0
  47. package/dist/translators/openai.chatcompletion.request.translators.d.ts +23 -0
  48. package/dist/translators/openai.chatcompletion.request.translators.d.ts.map +1 -0
  49. package/dist/translators/openai.chatcompletion.request.translators.js +201 -0
  50. package/dist/translators/openai.chatcompletion.request.translators.js.map +1 -0
  51. package/dist/translators/openai.chatcompletion.response.translators.d.ts +18 -0
  52. package/dist/translators/openai.chatcompletion.response.translators.d.ts.map +1 -0
  53. package/dist/translators/openai.chatcompletion.response.translators.js +117 -0
  54. package/dist/translators/openai.chatcompletion.response.translators.js.map +1 -0
  55. package/dist/translators/openai.content.translators.d.ts +28 -0
  56. package/dist/translators/openai.content.translators.d.ts.map +1 -0
  57. package/dist/translators/openai.content.translators.js +100 -0
  58. package/dist/translators/openai.content.translators.js.map +1 -0
  59. package/dist/translators/openai.message.translators.d.ts +15 -0
  60. package/dist/translators/openai.message.translators.d.ts.map +1 -0
  61. package/dist/translators/openai.message.translators.js +144 -0
  62. package/dist/translators/openai.message.translators.js.map +1 -0
  63. package/dist/translators/openai.response.message.translators.d.ts +12 -0
  64. package/dist/translators/openai.response.message.translators.d.ts.map +1 -0
  65. package/dist/translators/openai.response.message.translators.js +100 -0
  66. package/dist/translators/openai.response.message.translators.js.map +1 -0
  67. package/dist/translators/openai.responses.request.translators.d.ts +10 -0
  68. package/dist/translators/openai.responses.request.translators.d.ts.map +1 -0
  69. package/dist/translators/openai.responses.request.translators.js +172 -0
  70. package/dist/translators/openai.responses.request.translators.js.map +1 -0
  71. package/dist/translators/openai.tool.translators.d.ts +19 -0
  72. package/dist/translators/openai.tool.translators.d.ts.map +1 -0
  73. package/dist/translators/openai.tool.translators.js +80 -0
  74. package/dist/translators/openai.tool.translators.js.map +1 -0
  75. package/dist/translators/openai.usage.translators.d.ts +12 -0
  76. package/dist/translators/openai.usage.translators.d.ts.map +1 -0
  77. package/dist/translators/openai.usage.translators.js +42 -0
  78. package/dist/translators/openai.usage.translators.js.map +1 -0
  79. package/dist/translators/streaming/index.d.ts +6 -0
  80. package/dist/translators/streaming/index.d.ts.map +1 -0
  81. package/dist/translators/streaming/index.js +6 -0
  82. package/dist/translators/streaming/index.js.map +1 -0
  83. package/dist/translators/streaming/openai.content.delta.translator.d.ts +12 -0
  84. package/dist/translators/streaming/openai.content.delta.translator.d.ts.map +1 -0
  85. package/dist/translators/streaming/openai.content.delta.translator.js +82 -0
  86. package/dist/translators/streaming/openai.content.delta.translator.js.map +1 -0
  87. package/dist/translators/streaming/openai.message.delta.translator.d.ts +12 -0
  88. package/dist/translators/streaming/openai.message.delta.translator.d.ts.map +1 -0
  89. package/dist/translators/streaming/openai.message.delta.translator.js +152 -0
  90. package/dist/translators/streaming/openai.message.delta.translator.js.map +1 -0
  91. package/dist/translators/streaming/openai.message.start.translator.d.ts +12 -0
  92. package/dist/translators/streaming/openai.message.start.translator.d.ts.map +1 -0
  93. package/dist/translators/streaming/openai.message.start.translator.js +80 -0
  94. package/dist/translators/streaming/openai.message.start.translator.js.map +1 -0
  95. package/dist/translators/streaming/openai.message.stop.translator.d.ts +14 -0
  96. package/dist/translators/streaming/openai.message.stop.translator.d.ts.map +1 -0
  97. package/dist/translators/streaming/openai.message.stop.translator.js +112 -0
  98. package/dist/translators/streaming/openai.message.stop.translator.js.map +1 -0
  99. package/dist/translators/streaming/openai.stream.translator.d.ts +20 -0
  100. package/dist/translators/streaming/openai.stream.translator.d.ts.map +1 -0
  101. package/dist/translators/streaming/openai.stream.translator.js +86 -0
  102. package/dist/translators/streaming/openai.stream.translator.js.map +1 -0
  103. package/package.json +72 -0
package/README.md ADDED
@@ -0,0 +1,771 @@
1
+ # @holokai/holo-provider-openai
2
+
3
+ > **Official OpenAI provider plugin for Holo LLM Gateway**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@holokai/holo-provider-openai.svg)](https://www.npmjs.com/package/@holokai/holo-provider-openai)
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 OpenAI provider plugin enables Holo to communicate with OpenAI's Chat Completions and Responses APIs 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 OpenAI's native APIs 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** - OpenAI ↔ Holo format with lossless core fields
18
+ - ✅ **Dual API Support** - Both Chat Completions and Responses APIs
19
+ - ✅ **Streaming Support** - Delta-based streaming with proper chunk handling
20
+ - ✅ **Tool Calling** - Complete function calling support with direct mapping
21
+ - ✅ **Vision/Multimodal** - Image support via URLs and base64 data URIs
22
+ - ✅ **Structured Outputs** - JSON schema validation and JSON object mode
23
+ - ✅ **Multi-Choice Support** - OpenAI-specific `n` parameter for multiple completions
24
+ - ✅ **Plugin Architecture** - Auto-discovered, hot-reloadable, independently versioned
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @holokai/holo-provider-openai
32
+ ```
33
+
34
+ ### Peer Dependencies
35
+
36
+ This plugin requires:
37
+ - `@holokai/sdk` ^0.1.0 - Holo universal format types and plugin contracts
38
+ - `openai` ^6.9.1 - Official OpenAI SDK
39
+
40
+ ---
41
+
42
+ ## Quick Start
43
+
44
+ ### Automatic Discovery
45
+
46
+ When installed in a Holo worker environment, this plugin is automatically discovered and loaded by the plugin system. No manual registration required.
47
+
48
+ ### Configuration
49
+
50
+ Add a provider configuration to your Holo deployment:
51
+
52
+ ```json
53
+ {
54
+ "id": "openai-primary",
55
+ "provider_type": "openai",
56
+ "plugin_id": "@holokai/holo-provider-openai",
57
+ "api_key": "${OPENAI_API_KEY}",
58
+ "model": "gpt-4o",
59
+ "config": {
60
+ "defaultModel": "gpt-4o",
61
+ "timeoutMs": 60000,
62
+ "maxRetries": 2,
63
+ "enableVision": true
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Usage in Code
69
+
70
+ ```typescript
71
+ import { HoloRequest, HoloResponse } from '@holokai/sdk';
72
+
73
+ const request: HoloRequest = {
74
+ model: 'gpt-4o',
75
+ messages: [
76
+ { role: 'user', content: 'Explain quantum computing briefly.' }
77
+ ],
78
+ max_tokens: 1000,
79
+ temperature: 0.7
80
+ };
81
+
82
+ // Plugin handles translation automatically
83
+ const response: HoloResponse = await holoClient.chat(request);
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Migration from Monolithic Architecture
89
+
90
+ ### What Changed
91
+
92
+ This plugin represents the extraction of OpenAI provider logic from the monolithic `src/providers/openai/` codebase into a standalone, independently versioned package.
93
+
94
+ **Before** (Monolithic):
95
+ ```
96
+ src/providers/openai/
97
+ ├── openai.translator.ts
98
+ ├── translators/
99
+ │ ├── chatcompletion/
100
+ │ └── responses/
101
+ ├── services/
102
+ ├── streaming/
103
+ └── types/
104
+ ```
105
+
106
+ **After** (Plugin):
107
+ ```
108
+ @holokai/holo-provider-openai
109
+ ├── src/
110
+ │ ├── plugin.ts # Plugin entrypoint
111
+ │ ├── manifest.ts # Plugin metadata
112
+ │ ├── openai.provider.ts # Provider implementation
113
+ │ └── translators/ # Translation logic (preserved)
114
+ └── package.json
115
+ ```
116
+
117
+ ### Migration Benefits
118
+
119
+ 1. **Independent Versioning** - Update OpenAI support without core releases
120
+ 2. **Hot Reload** - Deploy new OpenAI versions without downtime
121
+ 3. **Type Safety** - Strict SDK types eliminate `Record<string, unknown>`
122
+ 4. **Reduced Coupling** - Plugin contracts enforce clean boundaries
123
+ 5. **Marketplace Ready** - Can be published to NPM independently
124
+
125
+ ### Breaking Changes
126
+
127
+ - **Import paths changed**: Use `@holokai/sdk` for types instead of `../../types`
128
+ - **Configuration schema**: Now validated via plugin manifest
129
+ - **Dependency injection**: Uses plugin container instead of core DI
130
+
131
+ ---
132
+
133
+ ## Architecture
134
+
135
+ ### Plugin Structure
136
+
137
+ ```
138
+ @holokai/holo-provider-openai/
139
+ ├── src/
140
+ │ ├── plugin.ts # ProviderPlugin implementation
141
+ │ ├── manifest.ts # Plugin metadata & config schema
142
+ │ ├── openai.provider.ts # Core provider logic with routing
143
+ │ ├── openai.translator.ts # Main translator facade
144
+ │ ├── translators/
145
+ │ │ ├── openai.chatcompletion.request.translator.ts
146
+ │ │ ├── openai.chatcompletion.response.translator.ts
147
+ │ │ ├── openai.responses.request.translator.ts
148
+ │ │ ├── openai.message.translator.ts
149
+ │ │ ├── openai.content.translator.ts
150
+ │ │ ├── openai.tool.translator.ts
151
+ │ │ └── openai.usage.translator.ts
152
+ │ ├── streaming/
153
+ │ │ ├── openai.stream.translator.ts # Orchestrator
154
+ │ │ ├── openai.message.start.translator.ts
155
+ │ │ ├── openai.message.delta.translator.ts
156
+ │ │ ├── openai.message.stop.translator.ts
157
+ │ │ └── openai.content.delta.translator.ts
158
+ │ ├── services/
159
+ │ │ ├── openai.chatcompletions.service.ts # Chat Completions logic
160
+ │ │ └── openai.responses.service.ts # Responses API logic
161
+ │ ├── types/
162
+ │ │ ├── chatcompletion.types.ts # 60 types
163
+ │ │ └── responses.ts # 168 types
164
+ │ └── validators/
165
+ │ ├── openai.chatcompletion.validators.ts # 50 validators
166
+ │ └── openai.responses.validators.ts # 150 validators
167
+ └── package.json
168
+ ```
169
+
170
+ ### Translation Flow
171
+
172
+ ```
173
+ ┌─────────────────┐
174
+ │ Holo Request │
175
+ │ (SDK types) │
176
+ └────────┬────────┘
177
+
178
+
179
+ ┌─────────────────────────┐
180
+ │ OpenAIRequestTranslator │
181
+ │ - Maps Holo → OpenAI │
182
+ │ - Wraps tool format │
183
+ │ - Renames fields │
184
+ └────────┬────────────────┘
185
+
186
+
187
+ ┌─────────────────┐
188
+ │ OpenAI API │
189
+ │ (Chat/Response)│
190
+ └────────┬────────┘
191
+
192
+
193
+ ┌──────────────────────────┐
194
+ │ OpenAIResponseTranslator │
195
+ │ - Maps OpenAI → Holo │
196
+ │ - Converts timestamps │
197
+ │ - Extracts from choices │
198
+ └────────┬─────────────────┘
199
+
200
+
201
+ ┌─────────────────┐
202
+ │ Holo Response │
203
+ │ (SDK types) │
204
+ └─────────────────┘
205
+ ```
206
+
207
+ ### Dual API Support
208
+
209
+ The provider intelligently routes requests based on structure:
210
+
211
+ ```typescript
212
+ private isResponsesAPIRequest(payload: ProviderRequest): payload is OpenAIResponseCreateParams {
213
+ return 'input' in payload && !('messages' in payload);
214
+ }
215
+ ```
216
+
217
+ - **Chat Completions API**: Has `messages` field (most common)
218
+ - **Responses API**: Has `input` field instead of `messages`
219
+
220
+ ---
221
+
222
+ ## Holo Format Mapping
223
+
224
+ This plugin implements the official Holo format mappings as documented in the SDK.
225
+
226
+ ### Request Mapping: Holo → OpenAI (Chat Completions)
227
+
228
+ | Holo Field | OpenAI Field | Transformation | Notes |
229
+ |------------|-------------|----------------|-------|
230
+ | **Direct 1:1** ||||
231
+ | `model` | `model` | Direct | Required |
232
+ | `temperature` | `temperature` | Direct | 0-2 for OpenAI |
233
+ | `top_p` | `top_p` | Direct | Optional |
234
+ | `stream` | `stream` | Direct | Optional |
235
+ | `max_tokens` | `max_tokens` | Direct | Optional |
236
+ | `stop_sequences` | `stop` | Rename | Array format |
237
+ | `frequency_penalty` | `frequency_penalty` | Direct | Optional |
238
+ | `presence_penalty` | `presence_penalty` | Direct | Optional |
239
+ | `seed` | `seed` | Direct | Optional |
240
+ | **Structure Transforms** ||||
241
+ | `system` (string) | First message with `role:'system'` | Inject as message | Optional |
242
+ | `messages` | `messages` | Direct | Array of messages |
243
+ | `metadata.user_id` | `user` | Promote to top-level | Optional |
244
+ | `tools[].parameters` | `tools[].function.parameters` | Wrap in function | JSON Schema |
245
+ | `tool_choice.type: 'specific'` | `{type: 'function', function: {name}}` | Wrap with name | Specific tool |
246
+ | `tool_choice.type: 'required'` | `'required'` | Map type | Any tool required |
247
+ | `tool_choice.type: 'auto'` | `'auto'` | Direct | Default |
248
+ | `tool_choice.type: 'none'` | `'none'` | Direct | Disable tools |
249
+ | `response_format.type: 'json_object'` | `{type: 'json_object'}` | Wrap | JSON mode |
250
+ | `response_format.type: 'json_schema'` | `{type: 'json_schema', json_schema: {...}}` | Nest schema | Structured output |
251
+
252
+ **OpenAI-Specific Fields** (not in Holo core):
253
+ - `n` - Number of choices (handled via multi-choice streaming)
254
+ - `logprobs` - Token probabilities (not in Holo spec)
255
+ - `logit_bias` - Token bias (not in Holo spec)
256
+ - `parallel_tool_calls` - Allow parallel execution (not in Holo spec)
257
+ - `service_tier` - Priority tier (optional in Holo)
258
+
259
+ ### Request Mapping: Holo → OpenAI (Responses API)
260
+
261
+ | Holo Field | OpenAI Field | Transformation | Notes |
262
+ |------------|-------------|----------------|-------|
263
+ | **Direct 1:1** ||||
264
+ | `model` | `model` | Direct | Required |
265
+ | `temperature` | `temperature` | Direct | Optional |
266
+ | `top_p` | `top_p` | Direct | Optional |
267
+ | `stream` | `stream` | Direct | Optional |
268
+ | **Structure Transforms** ||||
269
+ | `messages` | `input` | Rename field | Different field name |
270
+ | `system` (string) | `input[0]` with `role: 'system'` | Inject as first item | Optional |
271
+ | `max_tokens` | `max_output_tokens` | Rename | Optional |
272
+ | `tools` | `tools` | Transform structure | See Tool Mapping |
273
+ | `tool_choice` | `tool_choice` | Similar to Chat | Optional |
274
+ | `metadata.user_id` | `metadata.user_id` | Nest in metadata | Optional |
275
+
276
+ **Note**: Responses API uses `input` instead of `messages` and `max_output_tokens` instead of `max_tokens`.
277
+
278
+ ### Response Mapping: OpenAI → Holo
279
+
280
+ | OpenAI Field | Holo Field | Transformation | Notes |
281
+ |-------------|------------|----------------|-------|
282
+ | **Direct 1:1** ||||
283
+ | `id` | `id` | Direct | Always present |
284
+ | `model` | `model` | Direct | Always present |
285
+ | `choices[0].message.role` | `messages[0].role` | Extract from choices | Always 'assistant' |
286
+ | `choices[0].message.content` | `messages[0].content` | Extract from choices | Text content |
287
+ | `choices[0].message.tool_calls` | `messages[0].tool_calls` | Extract from choices | If present |
288
+ | **Structure Transforms** ||||
289
+ | `created` | `created` | Multiply by 1000 | Seconds → milliseconds |
290
+ | `choices[0].finish_reason` | `finish_reason` | Map codes | See table below |
291
+ | `usage.prompt_tokens` | `usage.input_tokens` | Rename | Optional |
292
+ | `usage.completion_tokens` | `usage.output_tokens` | Rename | Optional |
293
+ | `usage.prompt_tokens_details.cached_tokens` | `usage.cache_read_tokens` | Rename | Optional |
294
+ | Computed | `usage.total_tokens` | `input + output` | Derived |
295
+ | `service_tier` | `service_tier` | Direct | Top-level field |
296
+
297
+ **Timestamp Conversion**:
298
+ - OpenAI: Unix timestamp in seconds (`number`)
299
+ - Holo: Milliseconds since epoch (`number`)
300
+ - Conversion: `created * 1000`
301
+
302
+ **Finish Reason Mapping**:
303
+
304
+ | OpenAI `finish_reason` | Holo `finish_reason` | Notes |
305
+ |----------------------|---------------------|-------|
306
+ | `'stop'` | `'stop'` | Natural completion |
307
+ | `'length'` | `'length'` | Hit token limit |
308
+ | `'tool_calls'` | `'tool_calls'` | Model called tools |
309
+ | `'content_filter'` | `'content_filter'` | Content filtered |
310
+ | `'function_call'` | `'tool_calls'` | Legacy function calling |
311
+
312
+ ### Content Mapping
313
+
314
+ #### Text Content
315
+
316
+ ```typescript
317
+ // Holo
318
+ { type: 'text', text: 'Hello' }
319
+
320
+ // OpenAI (direct)
321
+ { type: 'text', text: 'Hello' }
322
+ ```
323
+
324
+ #### Image Content
325
+
326
+ ```typescript
327
+ // Holo
328
+ { type: 'image', url: 'https://example.com/image.png' }
329
+
330
+ // OpenAI
331
+ { type: 'image_url', image_url: { url: 'https://example.com/image.png' } }
332
+
333
+ // Holo (base64)
334
+ { type: 'image', url: 'data:image/png;base64,iVBORw...' }
335
+
336
+ // OpenAI (base64)
337
+ { type: 'image_url', image_url: { url: 'data:image/png;base64,iVBORw...' } }
338
+ ```
339
+
340
+ #### Tool Calls (Direct Mapping)
341
+
342
+ ```typescript
343
+ // OpenAI Response
344
+ {
345
+ choices: [{
346
+ message: {
347
+ role: 'assistant',
348
+ content: '',
349
+ tool_calls: [{
350
+ id: 'call_abc',
351
+ type: 'function',
352
+ function: { name: 'get_weather', arguments: '{"location":"SF"}' }
353
+ }]
354
+ }
355
+ }]
356
+ }
357
+
358
+ // Holo Response (extracted)
359
+ {
360
+ messages: [{
361
+ role: 'assistant',
362
+ content: '',
363
+ tool_calls: [{
364
+ id: 'call_abc',
365
+ type: 'function',
366
+ function: { name: 'get_weather', arguments: { location: 'SF' } }
367
+ }]
368
+ }]
369
+ }
370
+ ```
371
+
372
+ **Note**: OpenAI uses the same tool call format as Holo, so mapping is direct extraction from `choices[0].message`.
373
+
374
+ ---
375
+
376
+ ## Streaming
377
+
378
+ ### Delta-Based Streaming
379
+
380
+ OpenAI uses incremental deltas for streaming:
381
+
382
+ #### Chat Completions Streaming
383
+
384
+ ```typescript
385
+ // Chunk 1: Role initialization
386
+ {
387
+ id: 'chatcmpl-123',
388
+ model: 'gpt-4o',
389
+ created: 1234567890,
390
+ choices: [{
391
+ index: 0,
392
+ delta: { role: 'assistant', content: '' },
393
+ finish_reason: null
394
+ }]
395
+ }
396
+
397
+ // Chunk 2: Content delta
398
+ {
399
+ id: 'chatcmpl-123',
400
+ model: 'gpt-4o',
401
+ created: 1234567890,
402
+ choices: [{
403
+ index: 0,
404
+ delta: { content: 'Hello' },
405
+ finish_reason: null
406
+ }]
407
+ }
408
+
409
+ // Chunk 3: Final chunk with usage
410
+ {
411
+ id: 'chatcmpl-123',
412
+ model: 'gpt-4o',
413
+ created: 1234567890,
414
+ choices: [{
415
+ index: 0,
416
+ delta: {},
417
+ finish_reason: 'stop'
418
+ }],
419
+ usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
420
+ }
421
+ ```
422
+
423
+ #### Holo Mapping
424
+
425
+ The plugin translates OpenAI chunks to Holo streaming events:
426
+
427
+ | OpenAI Chunk | Holo Event | Notes |
428
+ |--------------|-----------|-------|
429
+ | First chunk (`delta.role`) | `message_start` | Initialize message |
430
+ | Content chunks (`delta.content`) | `content_delta` | Incremental text |
431
+ | Tool call chunks (`delta.tool_calls`) | `message_delta` (with tools) | Tool accumulation |
432
+ | Final chunk (`finish_reason`) | `message_stop` | Completion + usage |
433
+
434
+ ### Streaming Example
435
+
436
+ ```typescript
437
+ import { HoloStreamChunk } from '@holokai/sdk';
438
+
439
+ const stream = await openaiProvider.streamChat(request);
440
+
441
+ for await (const chunk: HoloStreamChunk of stream) {
442
+ switch (chunk.delta?.type) {
443
+ case 'message_start':
444
+ console.log('Message started:', chunk.id);
445
+ break;
446
+ case 'content_delta':
447
+ process.stdout.write(chunk.delta.delta.content ?? '');
448
+ break;
449
+ case 'message_delta':
450
+ console.log('Usage:', chunk.usage);
451
+ break;
452
+ case 'message_stop':
453
+ console.log('Complete. Reason:', chunk.finish_reason);
454
+ break;
455
+ }
456
+ }
457
+ ```
458
+
459
+ ### Multi-Choice Streaming
460
+
461
+ OpenAI supports multiple completions via the `n` parameter:
462
+
463
+ ```typescript
464
+ // OpenAI request
465
+ {
466
+ model: 'gpt-4o',
467
+ messages: [...],
468
+ n: 3 // Generate 3 completions
469
+ }
470
+
471
+ // OpenAI response chunks include choice index
472
+ {
473
+ choices: [{
474
+ index: 0, // First completion
475
+ delta: { content: 'Option A' }
476
+ }]
477
+ }
478
+ {
479
+ choices: [{
480
+ index: 1, // Second completion
481
+ delta: { content: 'Option B' }
482
+ }]
483
+ }
484
+ ```
485
+
486
+ **Note**: Multi-choice (`n > 1`) is OpenAI-specific and not part of portable Holo spec. Handled via streaming with `delta.choice` index.
487
+
488
+ ---
489
+
490
+ ## OpenAI-Specific Features
491
+
492
+ ### Structured Outputs
493
+
494
+ Enable JSON schema validation:
495
+
496
+ ```typescript
497
+ const request: HoloRequest = {
498
+ model: 'gpt-4o',
499
+ messages: [{ role: 'user', content: 'Generate a user profile' }],
500
+ response_format: {
501
+ type: 'json_schema',
502
+ schema: {
503
+ type: 'object',
504
+ properties: {
505
+ name: { type: 'string' },
506
+ age: { type: 'number' }
507
+ },
508
+ required: ['name', 'age']
509
+ }
510
+ }
511
+ };
512
+ ```
513
+
514
+ ### JSON Object Mode
515
+
516
+ Force JSON output without schema:
517
+
518
+ ```typescript
519
+ const request: HoloRequest = {
520
+ model: 'gpt-4o',
521
+ messages: [{ role: 'user', content: 'Generate JSON' }],
522
+ response_format: {
523
+ type: 'json_object'
524
+ }
525
+ };
526
+ ```
527
+
528
+ ### Prompt Caching
529
+
530
+ OpenAI caches prompts automatically based on usage patterns. No explicit cache control needed.
531
+
532
+ ### Service Tier
533
+
534
+ Request priority tier:
535
+
536
+ ```typescript
537
+ const request: HoloRequest = {
538
+ model: 'gpt-4o',
539
+ messages: [{ role: 'user', content: 'Urgent request' }],
540
+ service_tier: 'default' // or 'auto'
541
+ };
542
+ ```
543
+
544
+ ### Logprobs
545
+
546
+ **Note**: Not part of Holo spec. Use provider-specific config:
547
+
548
+ ```typescript
549
+ const request: HoloRequest = {
550
+ model: 'gpt-4o',
551
+ messages: [{ role: 'user', content: 'Hello' }],
552
+ provider_config: {
553
+ logprobs: true,
554
+ top_logprobs: 5
555
+ }
556
+ };
557
+ ```
558
+
559
+ ---
560
+
561
+ ## Type Safety
562
+
563
+ ### SDK Integration
564
+
565
+ This plugin uses strict SDK types exclusively:
566
+
567
+ ```typescript
568
+ import type {
569
+ HoloRequest,
570
+ HoloResponse,
571
+ HoloMessage,
572
+ HoloTool,
573
+ HoloJsonSchema // ✅ Proper JSON Schema types
574
+ } from '@holokai/sdk';
575
+
576
+ // ❌ NO: Record<string, unknown>
577
+ // ✅ YES: HoloJsonSchema
578
+ ```
579
+
580
+ ### Migration from Legacy Types
581
+
582
+ **Before** (Legacy provider):
583
+ ```typescript
584
+ import { HoloTool } from '../../types/holo/requests';
585
+
586
+ interface HoloTool {
587
+ parameters?: Record<string, unknown>; // ❌ Loose typing
588
+ }
589
+ ```
590
+
591
+ **After** (Plugin SDK):
592
+ ```typescript
593
+ import type { HoloTool, HoloJsonSchema } from '@holokai/sdk';
594
+
595
+ interface HoloTool {
596
+ parameters?: HoloJsonSchema; // ✅ Strict JSON Schema Draft 7
597
+ }
598
+ ```
599
+
600
+ ### Type Safety
601
+
602
+ All interfaces use strict TypeScript types from `@holokai/sdk` for compile-time validation.
603
+
604
+ ---
605
+
606
+ ## Configuration Schema
607
+
608
+ The plugin exposes a JSON Schema for configuration validation:
609
+
610
+ ```typescript
611
+ {
612
+ apiKey: string; // Required
613
+ organizationId?: string; // Optional organization ID
614
+ baseUrl?: string; // Optional custom endpoint
615
+ defaultModel?: string; // Fallback model
616
+ allowedModels?: string[]; // Model allowlist
617
+ timeoutMs?: number; // Request timeout (default: 60000)
618
+ maxRetries?: number; // Retry attempts (default: 2)
619
+ enableVision?: boolean; // Vision support (default: true)
620
+ logRequests?: boolean; // Observability (default: false)
621
+ telemetrySampleRate?: number;// Sampling rate (default: 1.0)
622
+ }
623
+ ```
624
+
625
+ See [manifest.ts](./src/manifest.ts) for the complete schema.
626
+
627
+ ---
628
+
629
+ ## Development
630
+
631
+ ### Setup
632
+
633
+ ```bash
634
+ # Install dependencies
635
+ npm install
636
+
637
+ # Build
638
+ npm run build
639
+
640
+ # Type checking
641
+ npm run type-check
642
+
643
+ # Run tests
644
+ npm test
645
+ ```
646
+
647
+ ### Testing
648
+
649
+ ```bash
650
+ # Unit tests
651
+ npm test
652
+
653
+ # Integration tests (requires API key)
654
+ OPENAI_API_KEY=sk-... npm run test:integration
655
+
656
+ # Watch mode
657
+ npm run test:watch
658
+ ```
659
+
660
+ ### Building
661
+
662
+ ```bash
663
+ # Production build
664
+ npm run build
665
+
666
+ # Watch mode
667
+ npm run build:watch
668
+
669
+ # Clean
670
+ npm run clean
671
+ ```
672
+
673
+ ---
674
+
675
+ ## Known Issues & Workarounds
676
+
677
+ ### Timestamp Format
678
+
679
+ **Issue**: OpenAI returns timestamps as Unix seconds, not milliseconds.
680
+
681
+ **Workaround**: Plugin automatically converts: `created * 1000`.
682
+
683
+ ### Multi-Choice Non-Portability
684
+
685
+ **Issue**: `n` parameter for multiple completions is OpenAI-specific.
686
+
687
+ **Behavior**: Not part of portable Holo spec. Use streaming with choice index tracking if needed.
688
+
689
+ ### Tool Call Arguments Parsing
690
+
691
+ **Issue**: OpenAI returns `tool_calls[].function.arguments` as JSON string.
692
+
693
+ **Workaround**: Plugin automatically parses to object for Holo format.
694
+
695
+ ### Empty Content in Tool Calls
696
+
697
+ **Issue**: When model calls tools, `content` may be empty string.
698
+
699
+ **Behavior**: Plugin preserves empty content as-is per OpenAI spec.
700
+
701
+ ### Service Tier Availability
702
+
703
+ **Issue**: Service tier is only available for certain models/tiers.
704
+
705
+ **Behavior**: Field is optional; omitted if not supported by model.
706
+
707
+ ---
708
+
709
+ ## Related Documentation
710
+
711
+ ### SDK Documentation
712
+ - [SDK README](../sdk/README.md) - Plugin development guide and templates
713
+
714
+ ### OpenAI Documentation
715
+ - [Official API Reference](https://platform.openai.com/docs/api-reference)
716
+ - [Chat Completions](https://platform.openai.com/docs/api-reference/chat)
717
+ - [Responses API](https://platform.openai.com/docs/api-reference/responses)
718
+ - [Streaming](https://platform.openai.com/docs/api-reference/streaming)
719
+ - [Function Calling](https://platform.openai.com/docs/guides/function-calling)
720
+ - [Vision](https://platform.openai.com/docs/guides/vision)
721
+ - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)
722
+
723
+ ### Migration Notes
724
+ - This plugin was extracted from the monolithic `src/providers/openai/` codebase
725
+ - Migration to plugin architecture is complete
726
+
727
+ ---
728
+
729
+ ## Contributing
730
+
731
+ ### Adding Features
732
+
733
+ 1. Update types in `@holokai/sdk` first (if needed)
734
+ 2. Implement translator logic
735
+ 3. Write tests (unit + integration)
736
+ 4. Update this README
737
+
738
+ ### Reporting Issues
739
+
740
+ Found a bug or have a feature request?
741
+ - GitHub Issues: https://github.com/holokai/holo-provider-openai/issues
742
+ - Include: Holo version, OpenAI model, request/response samples
743
+
744
+ ---
745
+
746
+ ## License
747
+
748
+ MIT © Holokai
749
+
750
+ ---
751
+
752
+ ## Changelog
753
+
754
+ ### v0.1.0 (Current)
755
+ - ✅ Initial plugin release
756
+ - ✅ Extracted from monolithic architecture
757
+ - ✅ Migrated to SDK types
758
+ - ✅ Validated against Holo format spec
759
+ - ✅ Dual API support (Chat Completions + Responses)
760
+ - ✅ Complete streaming orchestration
761
+ - ✅ Tool calling support
762
+ - ✅ Vision/multimodal support
763
+ - ✅ Structured outputs support
764
+ - ✅ 200/228 type validators (88% coverage)
765
+
766
+ ---
767
+
768
+ **Last Updated**: 2025-12-18
769
+ **Plugin Version**: 0.1.0
770
+ **SDK Version**: ^0.1.0
771
+ **OpenAI SDK**: ^6.9.1