@stacksjs/ai 0.70.55 → 0.70.56

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 (46) hide show
  1. package/package.json +10 -7
  2. package/src/agents/claude/index.ts +0 -310
  3. package/src/agents/index.ts +0 -7
  4. package/src/buddy.ts +0 -619
  5. package/src/drivers/anthropic/index.ts +0 -430
  6. package/src/drivers/claude-agent-sdk/index.ts +0 -370
  7. package/src/drivers/index.ts +0 -14
  8. package/src/drivers/ollama/index.ts +0 -514
  9. package/src/drivers/openai/index.ts +0 -529
  10. package/src/image.ts +0 -607
  11. package/src/index.ts +0 -53
  12. package/src/mcp.ts +0 -658
  13. package/src/personalization.ts +0 -490
  14. package/src/search.ts +0 -555
  15. package/src/text.ts +0 -79
  16. package/src/types.ts +0 -229
  17. package/src/utils/client-bedrock-runtime.ts +0 -51
  18. package/src/utils/client-bedrock.ts +0 -75
  19. package/src/utils/model-access.ts +0 -32
  20. package/src/utils/retry.ts +0 -124
  21. package/src/utils/tokens.ts +0 -159
  22. package/src/utils/usage.ts +0 -98
  23. package/src/utils/vision.ts +0 -119
  24. /package/dist/{src/agents → agents}/claude/index.d.ts +0 -0
  25. /package/dist/{src/agents → agents}/index.d.ts +0 -0
  26. /package/dist/{src/buddy.d.ts → buddy.d.ts} +0 -0
  27. /package/dist/{src/drivers → drivers}/anthropic/index.d.ts +0 -0
  28. /package/dist/{src/drivers → drivers}/claude-agent-sdk/index.d.ts +0 -0
  29. /package/dist/{src/drivers → drivers}/index.d.ts +0 -0
  30. /package/dist/{src/drivers → drivers}/ollama/index.d.ts +0 -0
  31. /package/dist/{src/drivers → drivers}/openai/index.d.ts +0 -0
  32. /package/dist/{src/image.d.ts → image.d.ts} +0 -0
  33. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  34. /package/dist/{src/index.js → index.js} +0 -0
  35. /package/dist/{src/mcp.d.ts → mcp.d.ts} +0 -0
  36. /package/dist/{src/personalization.d.ts → personalization.d.ts} +0 -0
  37. /package/dist/{src/search.d.ts → search.d.ts} +0 -0
  38. /package/dist/{src/text.d.ts → text.d.ts} +0 -0
  39. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  40. /package/dist/{src/utils → utils}/client-bedrock-runtime.d.ts +0 -0
  41. /package/dist/{src/utils → utils}/client-bedrock.d.ts +0 -0
  42. /package/dist/{src/utils → utils}/model-access.d.ts +0 -0
  43. /package/dist/{src/utils → utils}/retry.d.ts +0 -0
  44. /package/dist/{src/utils → utils}/tokens.d.ts +0 -0
  45. /package/dist/{src/utils → utils}/usage.d.ts +0 -0
  46. /package/dist/{src/utils → utils}/vision.d.ts +0 -0
@@ -1,529 +0,0 @@
1
- /**
2
- * OpenAI API Driver
3
- *
4
- * Direct API integration with OpenAI's GPT models.
5
- * Supports chat completions, streaming, and embeddings.
6
- */
7
-
8
- import type { AIDriver, AIDriverConfig, AIMessage, AIResult, ChatCompletionOptions, EmbeddingsResponse, OpenAIAPIResponse } from '../../types'
9
- import { fetchWithRetry } from '../../utils/retry'
10
- import { recordUsage } from '../../utils/usage'
11
- import { normalizeMessagesForProvider } from '../../utils/vision'
12
-
13
- export interface OpenAIDriverConfig extends AIDriverConfig {
14
- apiKey: string
15
- model?: string
16
- maxTokens?: number
17
- embeddingModel?: string
18
- baseUrl?: string
19
- }
20
-
21
- const DEFAULT_MODEL = 'gpt-4o'
22
- const DEFAULT_MAX_TOKENS = 4096
23
- const DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small'
24
- const DEFAULT_BASE_URL = 'https://api.openai.com/v1'
25
-
26
- let globalConfig: OpenAIDriverConfig | null = null
27
-
28
- /**
29
- * Configure OpenAI globally
30
- */
31
- export function configure(config: OpenAIDriverConfig): void {
32
- globalConfig = config
33
- }
34
-
35
- function getConfig(config?: Partial<OpenAIDriverConfig>): OpenAIDriverConfig {
36
- const merged = { ...globalConfig, ...config }
37
- if (!merged.apiKey) {
38
- merged.apiKey = process.env.OPENAI_API_KEY || ''
39
- }
40
- return merged as OpenAIDriverConfig
41
- }
42
-
43
- export function createOpenAIDriver(config: OpenAIDriverConfig): AIDriver {
44
- const {
45
- apiKey,
46
- model = DEFAULT_MODEL,
47
- maxTokens = DEFAULT_MAX_TOKENS,
48
- baseUrl = DEFAULT_BASE_URL,
49
- embeddingModel = DEFAULT_EMBEDDING_MODEL,
50
- } = config
51
-
52
- return {
53
- name: 'OpenAI',
54
-
55
- async process(command: string, systemPrompt: string, history: AIMessage[]): Promise<string> {
56
- if (!apiKey) {
57
- throw new Error('OpenAI API key not set. Configure your API key in settings.')
58
- }
59
-
60
- // Retry-aware fetch (stacksjs/stacks#1878 A-5). Honors 429
61
- // `Retry-After` and backs off on 5xx; throws to the caller
62
- // only after retries are exhausted or for non-retryable 4xx.
63
- const response = await fetchWithRetry(`${baseUrl}/chat/completions`, {
64
- method: 'POST',
65
- headers: {
66
- 'Content-Type': 'application/json',
67
- 'Authorization': `Bearer ${apiKey}`,
68
- },
69
- body: JSON.stringify({
70
- model,
71
- max_tokens: maxTokens,
72
- messages: [
73
- { role: 'system', content: systemPrompt },
74
- ...history,
75
- { role: 'user', content: command },
76
- ],
77
- }),
78
- })
79
-
80
- if (!response.ok) {
81
- const error = await response.text()
82
- throw new Error(`OpenAI API error: ${error}`)
83
- }
84
-
85
- const data = (await response.json()) as OpenAIAPIResponse
86
- if (!data.choices || data.choices.length === 0) {
87
- throw new Error('OpenAI API returned empty choices')
88
- }
89
- return data.choices[0].message.content
90
- },
91
-
92
- async *stream(command: string, systemPrompt: string, history: AIMessage[]): AsyncGenerator<string> {
93
- if (!apiKey) {
94
- throw new Error('OpenAI API key not set. Configure your API key in settings.')
95
- }
96
-
97
- const response = await fetch(`${baseUrl}/chat/completions`, {
98
- method: 'POST',
99
- headers: {
100
- 'Content-Type': 'application/json',
101
- 'Authorization': `Bearer ${apiKey}`,
102
- },
103
- body: JSON.stringify({
104
- model,
105
- max_tokens: maxTokens,
106
- stream: true,
107
- messages: [
108
- { role: 'system', content: systemPrompt },
109
- ...history,
110
- { role: 'user', content: command },
111
- ],
112
- }),
113
- })
114
-
115
- if (!response.ok) {
116
- const error = await response.text()
117
- throw new Error(`OpenAI API error: ${error}`)
118
- }
119
-
120
- const reader = response.body?.getReader()
121
- if (!reader) throw new Error('No response body')
122
-
123
- const decoder = new TextDecoder()
124
- let buffer = ''
125
-
126
- // Inner helper: parse a single SSE data payload and either
127
- // yield content or throw on a server-side error event
128
- // (stacksjs/stacks#1878 A-2). Returns a one-shot generator
129
- // so the outer loop's yield semantics are preserved.
130
- const handlePayload = function* (data: string) {
131
- if (data === '[DONE]') return
132
- let parsed: any
133
- try {
134
- parsed = JSON.parse(data)
135
- }
136
- catch {
137
- // Genuinely invalid JSON — skip rather than abort the stream.
138
- return
139
- }
140
- // OpenAI surfaces mid-stream errors as `{ error: { message, type, ... } }`.
141
- // Pre-fix this was dropped on the floor (the `choices[0]?.delta`
142
- // lookup returned undefined), so the consumer saw a clean
143
- // end-of-stream and assumed success. Now: throw so the caller
144
- // knows the response was truncated.
145
- if (parsed?.error) {
146
- const msg = parsed.error.message || JSON.stringify(parsed.error)
147
- throw new Error(`[openai/stream] mid-stream error: ${msg}`)
148
- }
149
- const content = parsed.choices?.[0]?.delta?.content
150
- if (content) yield content
151
- }
152
-
153
- while (true) {
154
- const { done, value } = await reader.read()
155
- if (done) break
156
-
157
- buffer += decoder.decode(value, { stream: true })
158
- const lines = buffer.split('\n')
159
- buffer = lines.pop() || ''
160
-
161
- for (const line of lines) {
162
- if (line.startsWith('data: ')) {
163
- yield * handlePayload(line.slice(6))
164
- }
165
- }
166
- }
167
-
168
- // Process any remaining data in the buffer.
169
- if (buffer.startsWith('data: ')) {
170
- yield * handlePayload(buffer.slice(6))
171
- }
172
- },
173
-
174
- async embed(input: string | string[]): Promise<number[] | number[][]> {
175
- if (!apiKey) {
176
- throw new Error('OpenAI API key not set. Configure your API key in settings.')
177
- }
178
-
179
- const response = await fetch(`${baseUrl}/embeddings`, {
180
- method: 'POST',
181
- headers: {
182
- 'Content-Type': 'application/json',
183
- 'Authorization': `Bearer ${apiKey}`,
184
- },
185
- body: JSON.stringify({
186
- model: embeddingModel,
187
- input,
188
- }),
189
- })
190
-
191
- if (!response.ok) {
192
- const error = await response.text()
193
- throw new Error(`OpenAI Embeddings API error: ${error}`)
194
- }
195
-
196
- const data = (await response.json()) as EmbeddingsResponse
197
-
198
- if (Array.isArray(input)) {
199
- return data.data.map(d => d.embedding)
200
- }
201
- return data.data[0].embedding
202
- },
203
- }
204
- }
205
-
206
- /**
207
- * Chat completion with full options
208
- */
209
- export async function chat(
210
- messages: AIMessage[],
211
- options: ChatCompletionOptions = {},
212
- ): Promise<AIResult> {
213
- const config = getConfig()
214
- const {
215
- model = DEFAULT_MODEL,
216
- maxTokens = DEFAULT_MAX_TOKENS,
217
- temperature,
218
- topP,
219
- stop,
220
- } = options
221
-
222
- // Normalize content arrays into OpenAI's wire format
223
- // (stacksjs/stacks#1878 A-3). Apps that authored messages with
224
- // Anthropic-style `{ type: 'image', source: {...} }` blocks
225
- // (or use cross-driver helpers) get the right shape.
226
- const normalizedMessages = normalizeMessagesForProvider(messages, 'openai')
227
-
228
- // Track wall-clock duration for usage reporters (#1878 A-6).
229
- const startedAt = Date.now()
230
-
231
- const response = await fetchWithRetry(`${config.baseUrl || DEFAULT_BASE_URL}/chat/completions`, {
232
- method: 'POST',
233
- headers: {
234
- 'Content-Type': 'application/json',
235
- 'Authorization': `Bearer ${config.apiKey}`,
236
- },
237
- body: JSON.stringify({
238
- model,
239
- max_tokens: maxTokens,
240
- temperature,
241
- top_p: topP,
242
- stop,
243
- messages: normalizedMessages,
244
- }),
245
- })
246
-
247
- if (!response.ok) {
248
- const error = await response.text()
249
- throw new Error(`OpenAI API error: ${error}`)
250
- }
251
-
252
- const data = (await response.json()) as any
253
-
254
- if (!data.choices || data.choices.length === 0) {
255
- throw new Error('OpenAI API returned empty choices')
256
- }
257
-
258
- const result: AIResult = {
259
- content: data.choices[0].message.content,
260
- model: data.model,
261
- usage: {
262
- promptTokens: data.usage?.prompt_tokens || 0,
263
- completionTokens: data.usage?.completion_tokens || 0,
264
- totalTokens: data.usage?.total_tokens || 0,
265
- },
266
- finishReason: data.choices[0].finish_reason,
267
- }
268
-
269
- // Fire registered usage reporters (#1878 A-6). Apps install via
270
- // `onUsage(reporter)`; default is no-op. Errors are swallowed.
271
- recordUsage({
272
- provider: 'openai',
273
- model: data.model,
274
- promptTokens: result.usage!.promptTokens,
275
- completionTokens: result.usage!.completionTokens,
276
- totalTokens: result.usage!.totalTokens,
277
- durationMs: Date.now() - startedAt,
278
- timestamp: Date.now(),
279
- })
280
-
281
- return result
282
- }
283
-
284
- /**
285
- * Stream chat completion
286
- */
287
- export async function* streamChat(
288
- messages: AIMessage[],
289
- options: ChatCompletionOptions = {},
290
- ): AsyncGenerator<string> {
291
- const config = getConfig()
292
- const {
293
- model = DEFAULT_MODEL,
294
- maxTokens = DEFAULT_MAX_TOKENS,
295
- temperature,
296
- topP,
297
- stop,
298
- } = options
299
-
300
- const response = await fetch(`${config.baseUrl || DEFAULT_BASE_URL}/chat/completions`, {
301
- method: 'POST',
302
- headers: {
303
- 'Content-Type': 'application/json',
304
- 'Authorization': `Bearer ${config.apiKey}`,
305
- },
306
- body: JSON.stringify({
307
- model,
308
- max_tokens: maxTokens,
309
- temperature,
310
- top_p: topP,
311
- stop,
312
- stream: true,
313
- messages: normalizeMessagesForProvider(messages, 'openai'),
314
- }),
315
- })
316
-
317
- if (!response.ok) {
318
- const error = await response.text()
319
- throw new Error(`OpenAI API error: ${error}`)
320
- }
321
-
322
- const reader = response.body?.getReader()
323
- if (!reader) throw new Error('No response body')
324
-
325
- const decoder = new TextDecoder()
326
- let buffer = ''
327
-
328
- while (true) {
329
- const { done, value } = await reader.read()
330
- if (done) break
331
-
332
- buffer += decoder.decode(value, { stream: true })
333
- const lines = buffer.split('\n')
334
- buffer = lines.pop() || ''
335
-
336
- for (const line of lines) {
337
- if (line.startsWith('data: ')) {
338
- const data = line.slice(6)
339
- if (data === '[DONE]') continue
340
-
341
- try {
342
- const parsed = JSON.parse(data) as any
343
- const content = parsed.choices[0]?.delta?.content
344
- if (content) yield content
345
- }
346
- catch {
347
- // Skip invalid JSON
348
- }
349
- }
350
- }
351
- }
352
- }
353
-
354
- /**
355
- * Create embeddings
356
- */
357
- export async function embed(
358
- input: string | string[],
359
- model = DEFAULT_EMBEDDING_MODEL,
360
- ): Promise<number[] | number[][]> {
361
- const config = getConfig()
362
-
363
- const response = await fetch(`${config.baseUrl || DEFAULT_BASE_URL}/embeddings`, {
364
- method: 'POST',
365
- headers: {
366
- 'Content-Type': 'application/json',
367
- 'Authorization': `Bearer ${config.apiKey}`,
368
- },
369
- body: JSON.stringify({ model, input }),
370
- })
371
-
372
- if (!response.ok) {
373
- const error = await response.text()
374
- throw new Error(`OpenAI Embeddings API error: ${error}`)
375
- }
376
-
377
- const data = (await response.json()) as EmbeddingsResponse
378
-
379
- if (Array.isArray(input)) {
380
- return data.data.map(d => d.embedding)
381
- }
382
- return data.data[0].embedding
383
- }
384
-
385
- /**
386
- * Generate images using DALL-E
387
- */
388
- export async function generateImage(
389
- prompt: string,
390
- options: {
391
- model?: 'dall-e-2' | 'dall-e-3'
392
- size?: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'
393
- quality?: 'standard' | 'hd'
394
- n?: number
395
- responseFormat?: 'url' | 'b64_json'
396
- } = {},
397
- ): Promise<{ url?: string, b64_json?: string }[]> {
398
- const config = getConfig()
399
- const {
400
- model = 'dall-e-3',
401
- size = '1024x1024',
402
- quality = 'standard',
403
- n = 1,
404
- responseFormat = 'url',
405
- } = options
406
-
407
- const response = await fetch(`${config.baseUrl || DEFAULT_BASE_URL}/images/generations`, {
408
- method: 'POST',
409
- headers: {
410
- 'Content-Type': 'application/json',
411
- 'Authorization': `Bearer ${config.apiKey}`,
412
- },
413
- body: JSON.stringify({
414
- model,
415
- prompt,
416
- size,
417
- quality,
418
- n,
419
- response_format: responseFormat,
420
- }),
421
- })
422
-
423
- if (!response.ok) {
424
- const error = await response.text()
425
- throw new Error(`OpenAI Image API error: ${error}`)
426
- }
427
-
428
- const data = (await response.json()) as { data: { url?: string, b64_json?: string }[] }
429
- return data.data
430
- }
431
-
432
- /**
433
- * Transcribe audio using Whisper
434
- */
435
- export async function transcribe(
436
- audioFile: Blob | File,
437
- options: {
438
- model?: 'whisper-1'
439
- language?: string
440
- prompt?: string
441
- responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'
442
- temperature?: number
443
- } = {},
444
- ): Promise<{ text: string }> {
445
- const config = getConfig()
446
- const formData = new FormData()
447
- formData.append('file', audioFile)
448
- formData.append('model', options.model || 'whisper-1')
449
-
450
- if (options.language) formData.append('language', options.language)
451
- if (options.prompt) formData.append('prompt', options.prompt)
452
- if (options.responseFormat) formData.append('response_format', options.responseFormat)
453
- if (options.temperature !== undefined) formData.append('temperature', String(options.temperature))
454
-
455
- const response = await fetch(`${config.baseUrl || DEFAULT_BASE_URL}/audio/transcriptions`, {
456
- method: 'POST',
457
- headers: {
458
- 'Authorization': `Bearer ${config.apiKey}`,
459
- },
460
- body: formData,
461
- })
462
-
463
- if (!response.ok) {
464
- const error = await response.text()
465
- throw new Error(`OpenAI Whisper API error: ${error}`)
466
- }
467
-
468
- return response.json() as any
469
- }
470
-
471
- /**
472
- * Text-to-speech using OpenAI TTS
473
- */
474
- export async function textToSpeech(
475
- input: string,
476
- options: {
477
- model?: 'tts-1' | 'tts-1-hd'
478
- voice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer'
479
- responseFormat?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'
480
- speed?: number
481
- } = {},
482
- ): Promise<ArrayBuffer> {
483
- const config = getConfig()
484
- const {
485
- model = 'tts-1',
486
- voice = 'alloy',
487
- responseFormat = 'mp3',
488
- speed = 1.0,
489
- } = options
490
-
491
- const response = await fetch(`${config.baseUrl || DEFAULT_BASE_URL}/audio/speech`, {
492
- method: 'POST',
493
- headers: {
494
- 'Content-Type': 'application/json',
495
- 'Authorization': `Bearer ${config.apiKey}`,
496
- },
497
- body: JSON.stringify({
498
- model,
499
- input,
500
- voice,
501
- response_format: responseFormat,
502
- speed,
503
- }),
504
- })
505
-
506
- if (!response.ok) {
507
- const error = await response.text()
508
- throw new Error(`OpenAI TTS API error: ${error}`)
509
- }
510
-
511
- return response.arrayBuffer()
512
- }
513
-
514
- export const openaiDriver: { create: typeof createOpenAIDriver } = {
515
- create: createOpenAIDriver,
516
- }
517
-
518
- export const openai = {
519
- configure,
520
- chat,
521
- streamChat,
522
- embed,
523
- generateImage,
524
- transcribe,
525
- textToSpeech,
526
- createDriver: createOpenAIDriver,
527
- }
528
-
529
- export default openai