@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.
- package/package.json +10 -7
- package/src/agents/claude/index.ts +0 -310
- package/src/agents/index.ts +0 -7
- package/src/buddy.ts +0 -619
- package/src/drivers/anthropic/index.ts +0 -430
- package/src/drivers/claude-agent-sdk/index.ts +0 -370
- package/src/drivers/index.ts +0 -14
- package/src/drivers/ollama/index.ts +0 -514
- package/src/drivers/openai/index.ts +0 -529
- package/src/image.ts +0 -607
- package/src/index.ts +0 -53
- package/src/mcp.ts +0 -658
- package/src/personalization.ts +0 -490
- package/src/search.ts +0 -555
- package/src/text.ts +0 -79
- package/src/types.ts +0 -229
- package/src/utils/client-bedrock-runtime.ts +0 -51
- package/src/utils/client-bedrock.ts +0 -75
- package/src/utils/model-access.ts +0 -32
- package/src/utils/retry.ts +0 -124
- package/src/utils/tokens.ts +0 -159
- package/src/utils/usage.ts +0 -98
- package/src/utils/vision.ts +0 -119
- /package/dist/{src/agents → agents}/claude/index.d.ts +0 -0
- /package/dist/{src/agents → agents}/index.d.ts +0 -0
- /package/dist/{src/buddy.d.ts → buddy.d.ts} +0 -0
- /package/dist/{src/drivers → drivers}/anthropic/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/claude-agent-sdk/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/ollama/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/openai/index.d.ts +0 -0
- /package/dist/{src/image.d.ts → image.d.ts} +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/index.js → index.js} +0 -0
- /package/dist/{src/mcp.d.ts → mcp.d.ts} +0 -0
- /package/dist/{src/personalization.d.ts → personalization.d.ts} +0 -0
- /package/dist/{src/search.d.ts → search.d.ts} +0 -0
- /package/dist/{src/text.d.ts → text.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{src/utils → utils}/client-bedrock-runtime.d.ts +0 -0
- /package/dist/{src/utils → utils}/client-bedrock.d.ts +0 -0
- /package/dist/{src/utils → utils}/model-access.d.ts +0 -0
- /package/dist/{src/utils → utils}/retry.d.ts +0 -0
- /package/dist/{src/utils → utils}/tokens.d.ts +0 -0
- /package/dist/{src/utils → utils}/usage.d.ts +0 -0
- /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
|