@aeye/openai 0.3.10 → 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
@@ -4,14 +4,18 @@ OpenAI provider for the @aeye (AI TypeScript) framework. This package provides a
4
4
 
5
5
  ## Features
6
6
 
7
- - **Chat Completions**: Support for GPT-4, GPT-4 Turbo, GPT-3.5 Turbo, and other chat models
8
- - **Vision**: GPT-4 Vision (GPT-4V) for image understanding
9
- - **Reasoning Models**: o1, o1-mini, o3-mini for advanced reasoning tasks
10
- - **Image Generation**: DALL-E 2 and DALL-E 3 for creating images from text
11
- - **Speech-to-Text**: Whisper models for audio transcription
12
- - **Text-to-Speech**: TTS models for voice synthesis
7
+ - **Chat**: GPT-5.x, GPT-6, GPT-4.1 and other chat models, through Chat Completions or the Responses API
8
+ - **Vision**: Image and file (PDF) understanding
9
+ - **Reasoning**: Efforts `none` through `max`, reasoning summaries, and encrypted reasoning carried across turns
10
+ - **Hosted Tools**: Web search, file search, code interpreter, image generation, and remote MCP servers run by OpenAI
11
+ - **Image Generation**: GPT Image (`gpt-image-2` by default) for generating and editing images
12
+ - **Speech-to-Text**: `gpt-transcribe`, diarization with known speakers, and Whisper
13
+ - **Text-to-Speech**: TTS models with built-in or custom voices
14
+ - **Audio Output**: Spoken answers from audio-capable chat models
13
15
  - **Embeddings**: Text embeddings for semantic search and similarity
14
- - **Function Calling**: Tool/function calling with automatic schema conversion
16
+ - **Decisions**: Probabilities, choices, and scores from `/v1/decisions` (`gpt-6-luna`)
17
+ - **Batches**: Chat requests as one batch at about half the price
18
+ - **Function Calling**: Tool/function calling with automatic schema conversion, free-text and grammar tools, tool search, async tools, and allowed tools
15
19
  - **Structured Outputs**: JSON mode and schema-based structured outputs
16
20
  - **Streaming**: Real-time streaming responses for chat completions
17
21
  - **Multi-modal**: Support for text, images, audio, and files in conversations
@@ -23,6 +27,8 @@ OpenAI provider for the @aeye (AI TypeScript) framework. This package provides a
23
27
  npm install @aeye/openai @aeye/ai @aeye/core openai zod
24
28
  ```
25
29
 
30
+ Requires Node >= 22 and the `openai` SDK v7.
31
+
26
32
  ## Quick Start
27
33
 
28
34
  ```typescript
@@ -44,7 +50,7 @@ const response = await executor(
44
50
  ]
45
51
  },
46
52
  {},
47
- { model: 'gpt-4' }
53
+ { model: 'gpt-5.4-mini' }
48
54
  );
49
55
 
50
56
  console.log(response.content);
@@ -65,6 +71,17 @@ const config: OpenAIConfig = {
65
71
  const provider = new OpenAIProvider(config);
66
72
  ```
67
73
 
74
+ ### Chat Completions or Responses API
75
+
76
+ ```typescript
77
+ const provider = new OpenAIProvider({
78
+ apiKey: process.env.OPENAI_API_KEY!,
79
+ api: 'auto', // 'auto' (default) | 'chat' | 'responses'
80
+ });
81
+ ```
82
+
83
+ With `'auto'`, chat uses Chat Completions unless the model is Responses-only (codex, pre-5.6 pro models, deep-research, computer-use), the request has `hostedTools`, a tool sets `format`, `defer`, or `async`, `reason` sets `mode`, `summary`, or `context`, or the request has tools and a reasoning `effort` other than `none`. Through Responses, reasoning comes back encrypted (unless `store` is on) and is replayed on later turns.
84
+
68
85
  ### Custom Base URL (for Azure OpenAI or compatible APIs)
69
86
 
70
87
  ```typescript
@@ -93,7 +110,7 @@ const response = await executor(
93
110
  maxTokens: 150,
94
111
  },
95
112
  {},
96
- { model: 'gpt-4' }
113
+ { model: 'gpt-5.4-mini' }
97
114
  );
98
115
 
99
116
  console.log(response.content);
@@ -112,7 +129,7 @@ for await (const chunk of streamer(
112
129
  ]
113
130
  },
114
131
  {},
115
- { model: 'gpt-4' }
132
+ { model: 'gpt-5.4-mini' }
116
133
  )) {
117
134
  if (chunk.content) {
118
135
  process.stdout.write(chunk.content);
@@ -132,7 +149,7 @@ const conversation = [
132
149
  let response = await executor(
133
150
  { messages: conversation },
134
151
  {},
135
- { model: 'gpt-4' }
152
+ { model: 'gpt-5.4-mini' }
136
153
  );
137
154
 
138
155
  conversation.push({ role: 'assistant', content: response.content });
@@ -142,11 +159,11 @@ conversation.push({ role: 'user', content: 'Can you give me an example?' });
142
159
  response = await executor(
143
160
  { messages: conversation },
144
161
  {},
145
- { model: 'gpt-4' }
162
+ { model: 'gpt-5.4-mini' }
146
163
  );
147
164
  ```
148
165
 
149
- ### Vision (GPT-4V)
166
+ ### Vision
150
167
 
151
168
  #### Analyze an Image
152
169
 
@@ -168,7 +185,7 @@ const response = await executor(
168
185
  ]
169
186
  },
170
187
  {},
171
- { model: 'gpt-4-vision-preview' }
188
+ { model: 'gpt-5.4-mini' }
172
189
  );
173
190
  ```
174
191
 
@@ -189,7 +206,7 @@ const response = await executor(
189
206
  ]
190
207
  },
191
208
  {},
192
- { model: 'gpt-4-vision-preview' }
209
+ { model: 'gpt-5.4-mini' }
193
210
  );
194
211
  ```
195
212
 
@@ -215,10 +232,10 @@ const response = await executor(
215
232
  }),
216
233
  }
217
234
  ],
218
- toolChoice: 'auto', // Can be 'auto', 'required', 'none', or { tool: 'tool_name' }
235
+ toolChoice: 'auto', // 'auto', 'required', 'none', { tool: 'tool_name' }, or { allowed: ['tool_name'], required? }
219
236
  },
220
237
  {},
221
- { model: 'gpt-4' }
238
+ { model: 'gpt-5.4-mini' }
222
239
  );
223
240
 
224
241
  if (response.toolCalls) {
@@ -242,7 +259,7 @@ if (response.toolCalls) {
242
259
  ]
243
260
  },
244
261
  {},
245
- { model: 'gpt-4' }
262
+ { model: 'gpt-5.4-mini' }
246
263
  );
247
264
  }
248
265
  }
@@ -268,7 +285,7 @@ const response = await executor(
268
285
  toolChoice: { tool: 'get_weather' }, // Force this specific tool
269
286
  },
270
287
  {},
271
- { model: 'gpt-4' }
288
+ { model: 'gpt-5.4-mini' }
272
289
  );
273
290
  ```
274
291
 
@@ -288,7 +305,7 @@ const response = await executor(
288
305
  responseFormat: 'json', // Ensures valid JSON output
289
306
  },
290
307
  {},
291
- { model: 'gpt-4' }
308
+ { model: 'gpt-5.4-mini' }
292
309
  );
293
310
 
294
311
  const scientists = JSON.parse(response.content);
@@ -317,7 +334,7 @@ const response = await executor(
317
334
  responseFormat: PersonSchema, // Strict schema enforcement
318
335
  },
319
336
  {},
320
- { model: 'gpt-4' }
337
+ { model: 'gpt-5.4-mini' }
321
338
  );
322
339
 
323
340
  // Response is guaranteed to match the schema
@@ -325,60 +342,44 @@ const person = JSON.parse(response.content);
325
342
  console.log(person.name, person.age); // Type-safe
326
343
  ```
327
344
 
328
- ### Image Generation (DALL-E)
345
+ ### Image Generation (GPT Image)
329
346
 
330
- #### Generate with DALL-E 3
347
+ #### Generate an Image
331
348
 
332
349
  ```typescript
333
350
  const imageResponse = await provider.generateImage(
334
351
  {
335
352
  prompt: 'A serene landscape with mountains and a lake at sunset',
336
- model: 'dall-e-3',
337
- size: '1024x1024',
338
- quality: 'hd', // 'standard' or 'hd'
339
- style: 'vivid', // 'vivid' or 'natural'
353
+ model: 'gpt-image-2', // the default
354
+ size: '1024x1024', // or 'auto'; other sizes snap to the nearest the model supports
355
+ quality: 'high', // 'auto' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' (per model)
356
+ outputFormat: 'webp', // 'png' | 'jpeg' | 'webp'
357
+ outputCompression: 80, // 0-100, for jpeg and webp
358
+ background: 'transparent',
340
359
  n: 1,
341
360
  },
342
361
  {}
343
362
  );
344
363
 
345
- console.log('Generated image URL:', imageResponse.images[0].url);
346
- console.log('Revised prompt:', imageResponse.images[0].revisedPrompt);
364
+ const base64Image = imageResponse.images[0].b64_json;
347
365
  ```
348
366
 
349
- #### Generate Multiple Images with DALL-E 2
367
+ #### Edit with Reference Images
350
368
 
351
369
  ```typescript
352
- const imageResponse = await provider.generateImage(
370
+ const edited = await provider.editImage(
353
371
  {
354
- prompt: 'A cute robot playing with a puppy',
355
- model: 'dall-e-2',
356
- size: '512x512',
357
- n: 3, // Generate 3 variations
372
+ prompt: 'Put the product from the second image on the table in the first',
373
+ image: fs.readFileSync('room.png'), // source image
374
+ references: [fs.readFileSync('product.png')], // more input images
375
+ mask: fs.readFileSync('mask.png'), // optional, over the source image
376
+ inputFidelity: 'high', // keep input details closely
358
377
  },
359
378
  {}
360
379
  );
361
-
362
- imageResponse.images.forEach((img, i) => {
363
- console.log(`Image ${i + 1}:`, img.url);
364
- });
365
380
  ```
366
381
 
367
- #### Get Base64 Image Data
368
-
369
- ```typescript
370
- const imageResponse = await provider.generateImage(
371
- {
372
- prompt: 'A futuristic city skyline',
373
- model: 'dall-e-3',
374
- responseFormat: 'b64_json', // Get base64 instead of URL
375
- },
376
- {}
377
- );
378
-
379
- const base64Image = imageResponse.images[0].b64_json;
380
- // Save or process the base64 data
381
- ```
382
+ Usage reports the quality and size actually produced, so per-image pricing applies; GPT Image 2 and later report output image tokens in `usage.image.tokens`.
382
383
 
383
384
  ### Speech Synthesis (TTS)
384
385
 
@@ -386,12 +387,14 @@ const base64Image = imageResponse.images[0].b64_json;
386
387
 
387
388
  ```typescript
388
389
  import fs from 'fs';
390
+ import { Readable } from 'stream';
389
391
 
390
392
  const speechResponse = await provider.generateSpeech(
391
393
  {
392
394
  text: 'Hello! This is a text-to-speech example using OpenAI.',
393
- model: 'tts-1', // or 'tts-1-hd' for higher quality
394
- voice: 'alloy', // alloy, echo, fable, onyx, nova, shimmer
395
+ model: 'gpt-4o-mini-tts', // the default
396
+ voice: 'alloy', // any built-in voice name or a custom voice id
397
+ instructions: 'Speak warmly and a little slowly.',
395
398
  speed: 1.0, // 0.25 to 4.0
396
399
  responseFormat: 'mp3', // mp3, opus, aac, flac, wav, pcm
397
400
  },
@@ -399,7 +402,7 @@ const speechResponse = await provider.generateSpeech(
399
402
  );
400
403
 
401
404
  // Save to file
402
- fs.writeFileSync('output.mp3', speechResponse.audioBuffer);
405
+ Readable.fromWeb(speechResponse.audio).pipe(fs.createWriteStream('output.mp3'));
403
406
  console.log('Audio saved to output.mp3');
404
407
  ```
405
408
 
@@ -413,16 +416,16 @@ for (const voice of voices) {
413
416
  {
414
417
  text: `This is the ${voice} voice speaking.`,
415
418
  voice,
416
- model: 'tts-1',
419
+ model: 'gpt-4o-mini-tts',
417
420
  },
418
421
  {}
419
422
  );
420
423
 
421
- fs.writeFileSync(`voice_${voice}.mp3`, response.audioBuffer);
424
+ Readable.fromWeb(response.audio).pipe(fs.createWriteStream(`voice_${voice}.mp3`));
422
425
  }
423
426
  ```
424
427
 
425
- ### Transcription (Whisper)
428
+ ### Transcription
426
429
 
427
430
  #### Transcribe Audio File
428
431
 
@@ -434,7 +437,7 @@ const audioBuffer = fs.readFileSync('audio.mp3');
434
437
  const transcription = await provider.transcribe(
435
438
  {
436
439
  audio: audioBuffer,
437
- model: 'whisper-1',
440
+ model: 'gpt-transcribe', // the default
438
441
  language: 'en', // Optional: ISO-639-1 language code
439
442
  prompt: 'This is a podcast about AI and technology', // Optional: context
440
443
  },
@@ -458,13 +461,6 @@ const transcription = await provider.transcribe(
458
461
  );
459
462
 
460
463
  console.log('Full text:', transcription.text);
461
- console.log('Language:', transcription.language);
462
- console.log('Duration:', transcription.duration);
463
-
464
- // Word-level timestamps
465
- transcription.words?.forEach(word => {
466
- console.log(`${word.word}: ${word.start}s - ${word.end}s`);
467
- });
468
464
 
469
465
  // Segment-level timestamps
470
466
  transcription.segments?.forEach(segment => {
@@ -472,6 +468,27 @@ transcription.segments?.forEach(segment => {
472
468
  });
473
469
  ```
474
470
 
471
+ #### Speaker Diarization
472
+
473
+ ```typescript
474
+ const transcription = await provider.transcribe(
475
+ {
476
+ audio: fs.readFileSync('meeting.mp3'),
477
+ model: 'gpt-4o-transcribe-diarize',
478
+ responseFormat: 'diarized_json',
479
+ knownSpeakers: [
480
+ { name: 'Alice', audio: fs.readFileSync('alice.wav') },
481
+ { name: 'Bob', audio: fs.readFileSync('bob.wav') },
482
+ ],
483
+ },
484
+ {}
485
+ );
486
+
487
+ transcription.segments?.forEach(segment => {
488
+ console.log(`${segment.speaker}: ${segment.text}`);
489
+ });
490
+ ```
491
+
475
492
  #### Different Output Formats
476
493
 
477
494
  ```typescript
@@ -559,11 +576,7 @@ console.log('Similarity 1-2:', cosineSimilarity(emb1, emb2)); // High similarity
559
576
  console.log('Similarity 1-3:', cosineSimilarity(emb1, emb3)); // Low similarity
560
577
  ```
561
578
 
562
- ### Reasoning Models (o1, o3-mini)
563
-
564
- #### Using o1 Models
565
-
566
- The o1 and o3-mini models use extended thinking to solve complex problems.
579
+ ### Reasoning
567
580
 
568
581
  ```typescript
569
582
  const response = await executor(
@@ -574,21 +587,132 @@ const response = await executor(
574
587
  content: 'Solve this logic puzzle: There are 100 doors, all closed. You make 100 passes. On pass 1, toggle all doors. On pass 2, toggle every 2nd door. On pass 3, toggle every 3rd door, etc. Which doors are open after 100 passes?'
575
588
  }
576
589
  ],
577
- // Note: o1 models don't support temperature or system messages
590
+ reason: {
591
+ effort: 'high', // 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
592
+ summary: 'auto', // a readable summary (uses the Responses API)
593
+ },
578
594
  },
579
595
  {},
580
- { model: 'o1-preview' } // or 'o1-mini', 'o3-mini'
596
+ { model: 'gpt-5.5' }
581
597
  );
582
598
 
583
- console.log('Reasoning:', response.reasoning); // Internal reasoning process
599
+ console.log('Reasoning:', response.reasoning?.content);
584
600
  console.log('Answer:', response.content);
585
601
  ```
586
602
 
587
- **Important**: o1 models have different capabilities:
588
- - No system messages support (use user messages instead)
589
- - No temperature control (model uses internal reasoning)
590
- - No streaming support
591
- - Extended processing time for complex problems
603
+ - The effort is clamped to the model's accepted efforts when its `ModelInfo.reasoningEfforts` lists them.
604
+ - `mode`, `summary`, or `context`, or an effort other than `none` with tools, send the request to the Responses API. There, reasoning comes back encrypted (`openai-responses-v1` details) unless `store` is on; keep the assistant message's `reasoning` in the conversation and it is replayed on the next turn.
605
+ - `reason.maxTokens` and `reason.exclude` are not used by OpenAI.
606
+
607
+ ### Hosted Tools
608
+
609
+ Tools OpenAI runs itself go in `hostedTools` and use the Responses API:
610
+
611
+ ```typescript
612
+ const response = await executor(
613
+ {
614
+ messages: [{ role: 'user', content: 'Summarize this week\'s TypeScript news.' }],
615
+ hostedTools: [
616
+ { type: 'web_search', allowedDomains: ['devblogs.microsoft.com'] },
617
+ { type: 'code_interpreter' },
618
+ { type: 'file_search', vectorStoreIds: ['vs_123'], maxResults: 5 },
619
+ { type: 'mcp', label: 'docs', url: 'https://example.com/mcp', allowedTools: ['search'] },
620
+ { type: 'image_generation' },
621
+ ],
622
+ },
623
+ {},
624
+ { model: 'gpt-5.4-mini' }
625
+ );
626
+
627
+ console.log(response.citations); // [{ url, title, start, end }]
628
+ console.log(response.usage?.tools); // { web_search: 1, ... }
629
+ ```
630
+
631
+ `web_fetch` is not available on OpenAI (web search fetches pages itself), and web search can only allow domains, not block them (`blockedDomains` is rejected). Images from `image_generation` come back on `response.images`.
632
+
633
+ ### Audio Output
634
+
635
+ ```typescript
636
+ const response = await executor(
637
+ {
638
+ messages: [{ role: 'user', content: 'Say hello in French.' }],
639
+ modalities: ['text', 'audio'],
640
+ audio: { voice: 'alloy', format: 'mp3' }, // streaming defaults to pcm
641
+ },
642
+ {},
643
+ { model: 'gpt-audio-1.5' }
644
+ );
645
+
646
+ console.log(response.audio?.transcript); // response.audio.data is base64
647
+ ```
648
+
649
+ ### Other Request Options
650
+
651
+ - `seed`, `verbosity` (`'low' | 'medium' | 'high'`), `serviceTier` (`'auto' | 'default' | 'flex' | 'fast' | 'scale'`), and `metadata` are passed through. Chat Completions sends `metadata` only when `store` is on.
652
+ - `moderate: true` runs inline moderation (`omni-moderation-latest`); verdicts come back on `response.moderation`.
653
+ - Content parts take `cacheBreakpoint` (a prompt cache breakpoint), `detail` for images, and `name` for files.
654
+ - `response.serviceTier` reports the tier used (`priority` reads as `fast`).
655
+
656
+ ### Tool Features
657
+
658
+ - `toolChoice: { allowed: [...], required? }` is sent as `allowed_tools` on both APIs: every tool is still sent (keeping the prompt cacheable), but only the allowed ones may be called.
659
+ - A tool with `format: { type: 'text' }` or `format: { type: 'grammar', syntax: 'lark' | 'regex', definition }` is a Responses custom tool: the model writes free text, constrained by the grammar, and it arrives as `{ input }` arguments (`TEXT_TOOL_SCHEMA` from `@aeye/core`). With `api: 'chat'` it is a one-field function whose description carries the grammar.
660
+ - `defer: true` sends the tool with `defer_loading` and adds the hosted `tool_search` tool, so it is loaded only when the model searches for it.
661
+ - `async: true` marks an async tool; the model keeps working while it runs.
662
+
663
+ ```typescript
664
+ import { TEXT_TOOL_SCHEMA } from '@aeye/core';
665
+
666
+ const response = await executor(
667
+ {
668
+ messages: [{ role: 'user', content: 'How many orders did customer 42 place?' }],
669
+ tools: [{
670
+ name: 'sql',
671
+ description: 'Run a read-only SQL query',
672
+ parameters: TEXT_TOOL_SCHEMA,
673
+ format: { type: 'grammar', syntax: 'regex', definition: 'SELECT [^;]+;' },
674
+ }],
675
+ },
676
+ {},
677
+ { model: 'gpt-5.4-mini' }
678
+ );
679
+
680
+ console.log(response.toolCalls?.[0]?.arguments); // {"input":"SELECT COUNT(*) FROM orders WHERE customer_id = 42;"}
681
+ ```
682
+
683
+ ### Decisions
684
+
685
+ Typed answers from `/v1/decisions` (default model `gpt-6-luna`, or `defaultModels.decision`), through `ai.decide`:
686
+
687
+ ```typescript
688
+ import { AI } from '@aeye/ai';
689
+
690
+ const ai = AI.with().providers({ openai: provider }).create();
691
+
692
+ const { answers } = await ai.decide.get({
693
+ input: 'I was charged twice for my order.',
694
+ questions: {
695
+ billing: { type: 'predicate', instructions: 'Is this a billing problem?' },
696
+ urgency: { type: 'score', instructions: 'How urgent is it?', levels: ['Can wait', 'This week', 'Now'] },
697
+ },
698
+ });
699
+ ```
700
+
701
+ Decisions report through the `decide` hooks.
702
+
703
+ ### Batches
704
+
705
+ `ai.batch` uploads chat requests as a JSONL file and runs them as one batch with a 24 hour window, at about half the price. Results come back in request order; a failed request has `error`.
706
+
707
+ ```typescript
708
+ const job = await ai.batch.submit(
709
+ questions.map(question => ({ messages: [{ role: 'user', content: question }] })),
710
+ { metadata: { model: 'gpt-5.4-mini' } },
711
+ );
712
+
713
+ const done = await ai.batch.wait(job, {});
714
+ done.results?.forEach(result => console.log(result.response?.content ?? result.error));
715
+ ```
592
716
 
593
717
  ### Advanced Features
594
718
 
@@ -604,7 +728,7 @@ try {
604
728
  const response = await executor(
605
729
  { messages: [{ role: 'user', content: 'Tell me a long story' }] },
606
730
  {},
607
- { model: 'gpt-4' },
731
+ { model: 'gpt-5.4-mini' },
608
732
  controller.signal
609
733
  );
610
734
  } catch (error) {
@@ -638,7 +762,7 @@ console.log('OpenAI API is', isHealthy ? 'accessible' : 'not accessible');
638
762
  ```typescript
639
763
  // Set default metadata for all requests
640
764
  provider.defaultMetadata = {
641
- model: 'gpt-4',
765
+ model: 'gpt-5.4-mini',
642
766
  user: 'user-123',
643
767
  };
644
768
 
@@ -646,7 +770,7 @@ provider.defaultMetadata = {
646
770
  const response = await executor(
647
771
  { messages: [{ role: 'user', content: 'Hello' }] },
648
772
  {}
649
- // No need to pass { model: 'gpt-4' }
773
+ // No need to pass { model: 'gpt-5.4-mini' }
650
774
  );
651
775
  ```
652
776
 
@@ -657,6 +781,7 @@ The `OpenAIProvider` class can be extended to support OpenAI-compatible APIs lik
657
781
  ### Example: Custom Provider
658
782
 
659
783
  ```typescript
784
+ import type { Request } from '@aeye/core';
660
785
  import { OpenAIProvider, OpenAIConfig } from '@aeye/openai';
661
786
  import OpenAI from 'openai';
662
787
 
@@ -677,12 +802,13 @@ class CustomProvider extends OpenAIProvider<CustomConfig> {
677
802
  });
678
803
  }
679
804
 
680
- protected customizeChatParams(params: any, config: CustomConfig, request: any) {
681
- // Add custom parameters
682
- return {
683
- ...params,
684
- custom_param: config.customOption,
685
- };
805
+ protected augmentChatRequest<TParams extends OpenAI.Chat.ChatCompletionCreateParams>(
806
+ params: TParams,
807
+ request: Request,
808
+ config: CustomConfig,
809
+ ) {
810
+ // Add custom parameters (mutate in place)
811
+ Object.assign(params, { custom_param: config.customOption });
686
812
  }
687
813
  }
688
814
 
@@ -723,6 +849,12 @@ const azureProvider = new AzureOpenAIProvider({
723
849
  | `apiKey` | `string` | Yes | OpenAI API key |
724
850
  | `baseURL` | `string` | No | Custom base URL for API endpoint |
725
851
  | `organization` | `string` | No | OpenAI organization ID |
852
+ | `project` | `string` | No | OpenAI project ID |
853
+ | `store` | `boolean` | No | Let OpenAI store requests (default `false`) |
854
+ | `api` | `'auto' \| 'chat' \| 'responses'` | No | Which API chat uses (default `'auto'`) |
855
+ | `retry` / `retryEvents` | `RetryConfig` / `RetryEvents` | No | Retry behavior and events |
856
+ | `hooks` | `OpenAIHooks` | No | `beforeRequest` / `afterRequest` / `onError` for `chat`, `responses`, `imageGenerate`, `imageEdit`, `transcribe`, `speech`, `embed` |
857
+ | `defaultModels` | `object` | No | Per-operation defaults; built in: `gpt-image-2` (generate, edit), `gpt-5.4-mini` (image analysis), `gpt-transcribe`, `gpt-4o-mini-tts`, `text-embedding-3-small`, `gpt-6-luna` (decision); `rerank` has no OpenAI default |
726
858
 
727
859
  ### Request Parameters
728
860
 
@@ -736,30 +868,40 @@ const azureProvider = new AzureOpenAIProvider({
736
868
  | `maxTokens` | `number` | Maximum tokens to generate |
737
869
  | `stop` | `string \| string[]` | Stop sequences |
738
870
  | `tools` | `Tool[]` | Function definitions for tool calling |
739
- | `toolChoice` | `'auto' \| 'required' \| 'none' \| { tool: string }` | Tool selection mode |
871
+ | `toolChoice` | `'auto' \| 'required' \| 'none' \| { tool: string } \| { allowed: string[]; required?: boolean }` | Tool selection mode |
740
872
  | `responseFormat` | `'text' \| 'json' \| ZodSchema` | Response format constraint |
873
+ | `reason` | `Reason` | `{ effort, summary, mode, context }` |
874
+ | `seed` / `verbosity` / `serviceTier` / `metadata` | | Passed through |
875
+ | `modalities` / `audio` | | Audio output |
876
+ | `hostedTools` | `HostedTool[]` | Tools OpenAI runs (Responses API) |
877
+ | `moderate` | `boolean` | Inline moderation |
741
878
 
742
879
  #### Image Generation Request
743
880
 
744
881
  | Parameter | Type | Description |
745
882
  |-----------|------|-------------|
746
883
  | `prompt` | `string` | Text description of desired image |
747
- | `model` | `string` | Model to use (dall-e-2, dall-e-3) |
748
- | `size` | `string` | Image size (512x512, 1024x1024, etc.) |
749
- | `quality` | `'standard' \| 'hd'` | Image quality (DALL-E 3 only) |
750
- | `style` | `'vivid' \| 'natural'` | Image style (DALL-E 3 only) |
884
+ | `model` | `string` | GPT Image model (default gpt-image-2) |
885
+ | `size` | `string` | `WIDTHxHEIGHT` or `'auto'`; snaps to the model's sizes |
886
+ | `quality` | `'auto' \| 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'` | Image quality (models accept subsets) |
887
+ | `background` | `'transparent' \| 'opaque' \| 'auto'` | Background |
888
+ | `outputFormat` | `'png' \| 'jpeg' \| 'webp'` | Encoding |
889
+ | `outputCompression` | `number` | 0-100, for jpeg and webp |
890
+ | `moderation` | `'auto' \| 'low'` | Moderation strictness (generate) |
891
+ | `references` / `mask` / `inputFidelity` | | Extra input images, mask, and detail preservation (edit) |
751
892
  | `n` | `number` | Number of images to generate |
752
- | `responseFormat` | `'url' \| 'b64_json'` | Response format |
893
+ | `streamCount` | `number` | Partial images to stream |
753
894
 
754
895
  #### Transcription Request
755
896
 
756
897
  | Parameter | Type | Description |
757
898
  |-----------|------|-------------|
758
899
  | `audio` | `Buffer \| string \| Blob` | Audio file to transcribe |
759
- | `model` | `string` | Model to use (whisper-1) |
900
+ | `model` | `string` | Model to use (default gpt-transcribe) |
760
901
  | `language` | `string` | ISO-639-1 language code |
761
902
  | `prompt` | `string` | Context for better accuracy |
762
- | `responseFormat` | `'json' \| 'text' \| 'srt' \| 'vtt' \| 'verbose_json'` | Output format |
903
+ | `responseFormat` | `'json' \| 'text' \| 'srt' \| 'vtt' \| 'verbose_json' \| 'diarized_json'` | Output format |
904
+ | `knownSpeakers` | `{ name, audio }[]` | Reference clips for diarization |
763
905
  | `timestampGranularities` | `('word' \| 'segment')[]` | Timestamp detail level |
764
906
  | `temperature` | `number` | Sampling temperature |
765
907
 
@@ -768,8 +910,9 @@ const azureProvider = new AzureOpenAIProvider({
768
910
  | Parameter | Type | Description |
769
911
  |-----------|------|-------------|
770
912
  | `text` | `string` | Text to convert to speech |
771
- | `model` | `string` | Model to use (tts-1, tts-1-hd) |
772
- | `voice` | `string` | Voice ID (alloy, echo, fable, onyx, nova, shimmer) |
913
+ | `model` | `string` | Model to use (default gpt-4o-mini-tts) |
914
+ | `voice` | `string` | Built-in voice name or custom voice id (default alloy) |
915
+ | `instructions` | `string` | Style instructions |
773
916
  | `speed` | `number` | Playback speed (0.25-4.0) |
774
917
  | `responseFormat` | `'mp3' \| 'opus' \| 'aac' \| 'flac' \| 'wav' \| 'pcm'` | Audio format |
775
918
 
@@ -793,7 +936,7 @@ try {
793
936
  const response = await executor(
794
937
  { messages: [{ role: 'user', content: 'Hello' }] },
795
938
  {},
796
- { model: 'gpt-4' }
939
+ { model: 'gpt-5.4-mini' }
797
940
  );
798
941
  } catch (error) {
799
942
  if (error instanceof RateLimitError) {
@@ -832,9 +975,12 @@ Main provider class implementing the @aeye Provider interface.
832
975
  - `createExecutor<TContext, TMetadata>(config?: TConfig): Executor` - Create chat executor
833
976
  - `createStreamer<TContext, TMetadata>(config?: TConfig): Streamer` - Create streaming executor
834
977
  - `generateImage<TContext>(request, ctx, config?): Promise<ImageGenerationResponse>` - Generate images
978
+ - `editImage<TContext>(request, ctx, config?): Promise<ImageGenerationResponse>` - Edit images
835
979
  - `transcribe<TContext>(request, ctx, config?): Promise<TranscriptionResponse>` - Transcribe audio
836
- - `generateSpeech<TContext>(request, ctx, config?): Promise<SpeechResponse>` - Generate speech
980
+ - `speech(request, ctx, config?): Promise<SpeechResponse>` - Generate speech
837
981
  - `embed<TContext>(request, ctx, config?): Promise<EmbeddingResponse>` - Generate embeddings
982
+ - `decide(request, ctx, config?): Promise<DecisionResponse>` - Answer decision questions
983
+ - `submitBatch(requests, ctx, config?): Promise<BatchJob>` / `getBatch(job, ctx, config?): Promise<BatchJob>` - Run and read batches
838
984
 
839
985
  **Protected Methods** (for extending):
840
986
  - `createClient(config): OpenAI` - Create OpenAI client
@@ -843,31 +989,33 @@ Main provider class implementing the @aeye Provider interface.
843
989
  - `convertTools(request): OpenAI.ChatCompletionTool[]` - Convert tools
844
990
  - `convertToolChoice(request): OpenAI.ChatCompletionToolChoiceOption` - Convert tool choice
845
991
  - `convertResponseFormat(request): ResponseFormat` - Convert response format
846
- - `customizeChatParams(params, config, request)` - Customize chat params
847
- - `customizeImageParams(params, config)` - Customize image params
848
- - `customizeTranscriptionParams(params, config)` - Customize transcription params
849
- - `customizeSpeechParams(params, config)` - Customize speech params
850
- - `customizeEmbeddingParams(params, config)` - Customize embedding params
992
+ - `augmentChatRequest(params, request, config)` - Add to Chat Completions params (mutate in place)
993
+ - `augmentChatMessage(chatMessage, message, config)` / `augmentChatContent(...)` - Add to converted messages and content parts
994
+ - `augmentChatResponse(completion, response, config)` / `augmentChatChunk(chunk, ...)` - Read extra fields from responses and chunks
995
+ - `augmentImageGenerateRequest?` / `augmentImageEditRequest?` / `augmentTranscriptionRequest?` / `augmentSpeechRequest?` / `augmentEmbeddingRequest?` `(params, request, config)` - Optional hooks to add to those params
996
+ - `usesResponses(request, modelId, config, model?)` - Whether a chat request goes to the Responses API
997
+ - `createBatch(batch, run)` / `retrieveBatch(job, run)` - Submit and read batches (OpenRouter overrides these for inline batches)
851
998
 
852
999
  ## Model Support
853
1000
 
854
1001
  ### Chat Models
855
1002
 
856
- - **GPT-4 Turbo**: `gpt-4-turbo`, `gpt-4-turbo-preview`
857
- - **GPT-4**: `gpt-4`, `gpt-4-0613`, `gpt-4-32k`
858
- - **GPT-4 Vision**: `gpt-4-vision-preview`, `gpt-4-turbo-2024-04-09`
859
- - **GPT-3.5 Turbo**: `gpt-3.5-turbo`, `gpt-3.5-turbo-16k`
860
- - **Reasoning**: `o1-preview`, `o1-mini`, `o3-mini`
1003
+ - **GPT-6**: `gpt-6-luna`, `gpt-6-sol`, `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6.1-terra`
1004
+ - **GPT-5.x**: `gpt-5.6-*`, `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `chat-latest`
1005
+ - **Pro and codex** (Responses API): `gpt-5.5-pro`, `gpt-5.4-pro`, `gpt-5.3-codex`
1006
+ - **GPT-4.1 / GPT-4o** (retiring): `gpt-4.1`, `gpt-4.1-mini`, `gpt-4o`, `gpt-4o-mini`
1007
+ - **Audio**: `gpt-audio-1.5`
861
1008
 
862
1009
  ### Image Models
863
1010
 
864
- - **DALL-E 3**: `dall-e-3`
865
- - **DALL-E 2**: `dall-e-2`
1011
+ - **GPT Image**: `gpt-image-2`, `gpt-image-2.5-*`
866
1012
 
867
1013
  ### Audio Models
868
1014
 
869
- - **Whisper**: `whisper-1`
870
- - **TTS**: `tts-1`, `tts-1-hd`
1015
+ - **Transcription**: `gpt-transcribe`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, `whisper-1`
1016
+ - **TTS**: `gpt-4o-mini-tts`, `tts-1`, `tts-1-hd`
1017
+
1018
+ The `@aeye/models` registry records each model's `retiresAt` date; retired models are never selected.
871
1019
 
872
1020
  ### Embedding Models
873
1021
 
@@ -885,9 +1033,9 @@ Main provider class implementing the @aeye Provider interface.
885
1033
  4. **Token Management**: Monitor token usage to control costs. Use `maxTokens` to limit response length.
886
1034
 
887
1035
  5. **Model Selection**: Choose the appropriate model for your use case:
888
- - GPT-4 for complex tasks requiring high accuracy
889
- - GPT-3.5 Turbo for simpler tasks and faster responses
890
- - o1/o3-mini for reasoning-heavy problems
1036
+ - GPT-6 or GPT-5.5 for complex tasks requiring high accuracy
1037
+ - `gpt-5.4-mini` / `gpt-5.4-nano` for simpler tasks and faster responses
1038
+ - A higher `reason.effort` for reasoning-heavy problems
891
1039
 
892
1040
  6. **Function Calling**: Use structured schemas with Zod for type-safe function definitions.
893
1041