@stacksjs/ai 0.70.55 → 0.70.57
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
package/src/image.ts
DELETED
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Image Module
|
|
3
|
-
*
|
|
4
|
-
* Unified image generation and vision/analysis across multiple providers.
|
|
5
|
-
* Supports OpenAI DALL-E, Anthropic Claude Vision, and Ollama multimodal models.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { AIMessage, AIResult, ChatCompletionOptions } from './types'
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
export interface ImageGenerationOptions {
|
|
15
|
-
provider?: 'openai' | 'ollama'
|
|
16
|
-
model?: string
|
|
17
|
-
size?: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'
|
|
18
|
-
quality?: 'standard' | 'hd'
|
|
19
|
-
n?: number
|
|
20
|
-
responseFormat?: 'url' | 'b64_json'
|
|
21
|
-
style?: 'vivid' | 'natural'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ImageGenerationResult {
|
|
25
|
-
images: Array<{
|
|
26
|
-
url?: string
|
|
27
|
-
b64_json?: string
|
|
28
|
-
revisedPrompt?: string
|
|
29
|
-
}>
|
|
30
|
-
provider: string
|
|
31
|
-
model: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface VisionOptions {
|
|
35
|
-
provider?: 'anthropic' | 'openai' | 'ollama'
|
|
36
|
-
model?: string
|
|
37
|
-
maxTokens?: number
|
|
38
|
-
temperature?: number
|
|
39
|
-
detail?: 'auto' | 'low' | 'high'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface VisionResult extends AIResult {
|
|
43
|
-
provider: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export type ImageInput =
|
|
47
|
-
| { type: 'url'; url: string }
|
|
48
|
-
| { type: 'base64'; data: string; mediaType: string }
|
|
49
|
-
| { type: 'file'; path: string }
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// Image Generation
|
|
53
|
-
// ============================================================================
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Generate images from a text prompt.
|
|
57
|
-
* Currently supports OpenAI DALL-E models.
|
|
58
|
-
*/
|
|
59
|
-
export async function generateImage(
|
|
60
|
-
prompt: string,
|
|
61
|
-
options: ImageGenerationOptions = {},
|
|
62
|
-
): Promise<ImageGenerationResult> {
|
|
63
|
-
const {
|
|
64
|
-
provider = 'openai',
|
|
65
|
-
model = 'dall-e-3',
|
|
66
|
-
size = '1024x1024',
|
|
67
|
-
quality = 'standard',
|
|
68
|
-
n = 1,
|
|
69
|
-
responseFormat = 'url',
|
|
70
|
-
style = 'vivid',
|
|
71
|
-
} = options
|
|
72
|
-
|
|
73
|
-
if (provider === 'openai') {
|
|
74
|
-
return generateImageOpenAI(prompt, { model, size, quality, n, responseFormat, style })
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
throw new Error(`Image generation not supported for provider: ${provider}. Use 'openai'.`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function generateImageOpenAI(
|
|
81
|
-
prompt: string,
|
|
82
|
-
options: {
|
|
83
|
-
model: string
|
|
84
|
-
size: string
|
|
85
|
-
quality: string
|
|
86
|
-
n: number
|
|
87
|
-
responseFormat: string
|
|
88
|
-
style: string
|
|
89
|
-
},
|
|
90
|
-
): Promise<ImageGenerationResult> {
|
|
91
|
-
const apiKey = process.env.OPENAI_API_KEY
|
|
92
|
-
if (!apiKey) {
|
|
93
|
-
throw new Error('OPENAI_API_KEY environment variable is required for image generation.')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const response = await fetch('https://api.openai.com/v1/images/generations', {
|
|
97
|
-
method: 'POST',
|
|
98
|
-
headers: {
|
|
99
|
-
'Content-Type': 'application/json',
|
|
100
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
101
|
-
},
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
model: options.model,
|
|
104
|
-
prompt,
|
|
105
|
-
size: options.size,
|
|
106
|
-
quality: options.quality,
|
|
107
|
-
n: options.n,
|
|
108
|
-
response_format: options.responseFormat,
|
|
109
|
-
style: options.style,
|
|
110
|
-
}),
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
if (!response.ok) {
|
|
114
|
-
const error = await response.text()
|
|
115
|
-
throw new Error(`OpenAI Image API error: ${error}`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const data = (await response.json()) as {
|
|
119
|
-
data: Array<{ url?: string; b64_json?: string; revised_prompt?: string }>
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
images: data.data.map(img => ({
|
|
124
|
-
url: img.url,
|
|
125
|
-
b64_json: img.b64_json,
|
|
126
|
-
revisedPrompt: img.revised_prompt,
|
|
127
|
-
})),
|
|
128
|
-
provider: 'openai',
|
|
129
|
-
model: options.model,
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ============================================================================
|
|
134
|
-
// Image Editing
|
|
135
|
-
// ============================================================================
|
|
136
|
-
|
|
137
|
-
export interface ImageEditOptions {
|
|
138
|
-
mask?: Blob | File
|
|
139
|
-
model?: 'dall-e-2'
|
|
140
|
-
n?: number
|
|
141
|
-
size?: '256x256' | '512x512' | '1024x1024'
|
|
142
|
-
responseFormat?: 'url' | 'b64_json'
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Edit an existing image with a text prompt (OpenAI DALL-E 2).
|
|
147
|
-
*/
|
|
148
|
-
export async function editImage(
|
|
149
|
-
image: Blob | File,
|
|
150
|
-
prompt: string,
|
|
151
|
-
options: ImageEditOptions = {},
|
|
152
|
-
): Promise<ImageGenerationResult> {
|
|
153
|
-
const apiKey = process.env.OPENAI_API_KEY
|
|
154
|
-
if (!apiKey) {
|
|
155
|
-
throw new Error('OPENAI_API_KEY environment variable is required for image editing.')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const formData = new FormData()
|
|
159
|
-
formData.append('image', image)
|
|
160
|
-
formData.append('prompt', prompt)
|
|
161
|
-
formData.append('model', options.model || 'dall-e-2')
|
|
162
|
-
if (options.mask) formData.append('mask', options.mask)
|
|
163
|
-
if (options.n) formData.append('n', String(options.n))
|
|
164
|
-
if (options.size) formData.append('size', options.size)
|
|
165
|
-
if (options.responseFormat) formData.append('response_format', options.responseFormat)
|
|
166
|
-
|
|
167
|
-
const response = await fetch('https://api.openai.com/v1/images/edits', {
|
|
168
|
-
method: 'POST',
|
|
169
|
-
headers: {
|
|
170
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
171
|
-
},
|
|
172
|
-
body: formData,
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
const error = await response.text()
|
|
177
|
-
throw new Error(`OpenAI Image Edit API error: ${error}`)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const data = (await response.json()) as {
|
|
181
|
-
data: Array<{ url?: string; b64_json?: string }>
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
images: data.data.map(img => ({
|
|
186
|
-
url: img.url,
|
|
187
|
-
b64_json: img.b64_json,
|
|
188
|
-
})),
|
|
189
|
-
provider: 'openai',
|
|
190
|
-
model: options.model || 'dall-e-2',
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ============================================================================
|
|
195
|
-
// Image Variations
|
|
196
|
-
// ============================================================================
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Create variations of an existing image (OpenAI DALL-E 2).
|
|
200
|
-
*/
|
|
201
|
-
export async function createImageVariation(
|
|
202
|
-
image: Blob | File,
|
|
203
|
-
options: {
|
|
204
|
-
model?: 'dall-e-2'
|
|
205
|
-
n?: number
|
|
206
|
-
size?: '256x256' | '512x512' | '1024x1024'
|
|
207
|
-
responseFormat?: 'url' | 'b64_json'
|
|
208
|
-
} = {},
|
|
209
|
-
): Promise<ImageGenerationResult> {
|
|
210
|
-
const apiKey = process.env.OPENAI_API_KEY
|
|
211
|
-
if (!apiKey) {
|
|
212
|
-
throw new Error('OPENAI_API_KEY environment variable is required for image variations.')
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const formData = new FormData()
|
|
216
|
-
formData.append('image', image)
|
|
217
|
-
formData.append('model', options.model || 'dall-e-2')
|
|
218
|
-
if (options.n) formData.append('n', String(options.n))
|
|
219
|
-
if (options.size) formData.append('size', options.size)
|
|
220
|
-
if (options.responseFormat) formData.append('response_format', options.responseFormat)
|
|
221
|
-
|
|
222
|
-
const response = await fetch('https://api.openai.com/v1/images/variations', {
|
|
223
|
-
method: 'POST',
|
|
224
|
-
headers: {
|
|
225
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
226
|
-
},
|
|
227
|
-
body: formData,
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
if (!response.ok) {
|
|
231
|
-
const error = await response.text()
|
|
232
|
-
throw new Error(`OpenAI Image Variations API error: ${error}`)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const data = (await response.json()) as {
|
|
236
|
-
data: Array<{ url?: string; b64_json?: string }>
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
images: data.data.map(img => ({
|
|
241
|
-
url: img.url,
|
|
242
|
-
b64_json: img.b64_json,
|
|
243
|
-
})),
|
|
244
|
-
provider: 'openai',
|
|
245
|
-
model: options.model || 'dall-e-2',
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ============================================================================
|
|
250
|
-
// Vision / Image Analysis
|
|
251
|
-
// ============================================================================
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Analyze an image using AI vision capabilities.
|
|
255
|
-
* Supports Anthropic Claude, OpenAI GPT-4V, and Ollama multimodal models.
|
|
256
|
-
*/
|
|
257
|
-
export async function analyzeImage(
|
|
258
|
-
imageInput: ImageInput,
|
|
259
|
-
prompt: string,
|
|
260
|
-
options: VisionOptions = {},
|
|
261
|
-
): Promise<VisionResult> {
|
|
262
|
-
const { provider = 'anthropic' } = options
|
|
263
|
-
|
|
264
|
-
switch (provider) {
|
|
265
|
-
case 'anthropic':
|
|
266
|
-
return analyzeImageAnthropic(imageInput, prompt, options)
|
|
267
|
-
case 'openai':
|
|
268
|
-
return analyzeImageOpenAI(imageInput, prompt, options)
|
|
269
|
-
case 'ollama':
|
|
270
|
-
return analyzeImageOllama(imageInput, prompt, options)
|
|
271
|
-
default:
|
|
272
|
-
throw new Error(`Vision not supported for provider: ${provider}`)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async function resolveImageToBase64(input: ImageInput): Promise<{ data: string; mediaType: string }> {
|
|
277
|
-
if (input.type === 'base64') {
|
|
278
|
-
return { data: input.data, mediaType: input.mediaType }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (input.type === 'url') {
|
|
282
|
-
const response = await fetch(input.url)
|
|
283
|
-
if (!response.ok) throw new Error(`Failed to fetch image from URL: ${input.url}`)
|
|
284
|
-
const buffer = await response.arrayBuffer()
|
|
285
|
-
const base64 = Buffer.from(buffer).toString('base64')
|
|
286
|
-
const contentType = response.headers.get('content-type') || 'image/png'
|
|
287
|
-
return { data: base64, mediaType: contentType }
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (input.type === 'file') {
|
|
291
|
-
const { readFile } = await import('node:fs/promises')
|
|
292
|
-
const buffer = await readFile(input.path)
|
|
293
|
-
const base64 = buffer.toString('base64')
|
|
294
|
-
const ext = input.path.split('.').pop()?.toLowerCase() || 'png'
|
|
295
|
-
const mediaTypes: Record<string, string> = {
|
|
296
|
-
png: 'image/png',
|
|
297
|
-
jpg: 'image/jpeg',
|
|
298
|
-
jpeg: 'image/jpeg',
|
|
299
|
-
gif: 'image/gif',
|
|
300
|
-
webp: 'image/webp',
|
|
301
|
-
}
|
|
302
|
-
return { data: base64, mediaType: mediaTypes[ext] || 'image/png' }
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
throw new Error('Invalid image input type')
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function analyzeImageAnthropic(
|
|
309
|
-
imageInput: ImageInput,
|
|
310
|
-
prompt: string,
|
|
311
|
-
options: VisionOptions,
|
|
312
|
-
): Promise<VisionResult> {
|
|
313
|
-
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
314
|
-
if (!apiKey) {
|
|
315
|
-
throw new Error('ANTHROPIC_API_KEY environment variable is required for Claude vision.')
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const { data, mediaType } = await resolveImageToBase64(imageInput)
|
|
319
|
-
const model = options.model || 'claude-sonnet-4-20250514'
|
|
320
|
-
const maxTokens = options.maxTokens || 4096
|
|
321
|
-
|
|
322
|
-
const messages: AIMessage[] = [{
|
|
323
|
-
role: 'user',
|
|
324
|
-
content: [
|
|
325
|
-
{
|
|
326
|
-
type: 'image',
|
|
327
|
-
source: { type: 'base64', media_type: mediaType, data },
|
|
328
|
-
},
|
|
329
|
-
{ type: 'text', text: prompt },
|
|
330
|
-
],
|
|
331
|
-
}]
|
|
332
|
-
|
|
333
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
334
|
-
method: 'POST',
|
|
335
|
-
headers: {
|
|
336
|
-
'Content-Type': 'application/json',
|
|
337
|
-
'x-api-key': apiKey,
|
|
338
|
-
'anthropic-version': '2023-06-01',
|
|
339
|
-
},
|
|
340
|
-
body: JSON.stringify({
|
|
341
|
-
model,
|
|
342
|
-
max_tokens: maxTokens,
|
|
343
|
-
temperature: options.temperature,
|
|
344
|
-
messages,
|
|
345
|
-
}),
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
if (!response.ok) {
|
|
349
|
-
const error = await response.text()
|
|
350
|
-
throw new Error(`Claude Vision API error: ${error}`)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const responseData = (await response.json()) as any
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
content: responseData.content[0].text,
|
|
357
|
-
model: responseData.model,
|
|
358
|
-
provider: 'anthropic',
|
|
359
|
-
usage: {
|
|
360
|
-
promptTokens: responseData.usage?.input_tokens || 0,
|
|
361
|
-
completionTokens: responseData.usage?.output_tokens || 0,
|
|
362
|
-
totalTokens: (responseData.usage?.input_tokens || 0) + (responseData.usage?.output_tokens || 0),
|
|
363
|
-
},
|
|
364
|
-
finishReason: responseData.stop_reason,
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function analyzeImageOpenAI(
|
|
369
|
-
imageInput: ImageInput,
|
|
370
|
-
prompt: string,
|
|
371
|
-
options: VisionOptions,
|
|
372
|
-
): Promise<VisionResult> {
|
|
373
|
-
const apiKey = process.env.OPENAI_API_KEY
|
|
374
|
-
if (!apiKey) {
|
|
375
|
-
throw new Error('OPENAI_API_KEY environment variable is required for GPT-4 vision.')
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const model = options.model || 'gpt-4o'
|
|
379
|
-
const maxTokens = options.maxTokens || 4096
|
|
380
|
-
const detail = options.detail || 'auto'
|
|
381
|
-
|
|
382
|
-
let imageContent: any
|
|
383
|
-
if (imageInput.type === 'url') {
|
|
384
|
-
imageContent = { type: 'image_url', image_url: { url: imageInput.url, detail } }
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
const { data, mediaType } = await resolveImageToBase64(imageInput)
|
|
388
|
-
imageContent = {
|
|
389
|
-
type: 'image_url',
|
|
390
|
-
image_url: { url: `data:${mediaType};base64,${data}`, detail },
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
395
|
-
method: 'POST',
|
|
396
|
-
headers: {
|
|
397
|
-
'Content-Type': 'application/json',
|
|
398
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
399
|
-
},
|
|
400
|
-
body: JSON.stringify({
|
|
401
|
-
model,
|
|
402
|
-
max_tokens: maxTokens,
|
|
403
|
-
temperature: options.temperature,
|
|
404
|
-
messages: [{
|
|
405
|
-
role: 'user',
|
|
406
|
-
content: [
|
|
407
|
-
imageContent,
|
|
408
|
-
{ type: 'text', text: prompt },
|
|
409
|
-
],
|
|
410
|
-
}],
|
|
411
|
-
}),
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
if (!response.ok) {
|
|
415
|
-
const error = await response.text()
|
|
416
|
-
throw new Error(`OpenAI Vision API error: ${error}`)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const responseData = (await response.json()) as any
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
content: responseData.choices[0].message.content,
|
|
423
|
-
model: responseData.model,
|
|
424
|
-
provider: 'openai',
|
|
425
|
-
usage: {
|
|
426
|
-
promptTokens: responseData.usage?.prompt_tokens || 0,
|
|
427
|
-
completionTokens: responseData.usage?.completion_tokens || 0,
|
|
428
|
-
totalTokens: responseData.usage?.total_tokens || 0,
|
|
429
|
-
},
|
|
430
|
-
finishReason: responseData.choices[0].finish_reason,
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async function analyzeImageOllama(
|
|
435
|
-
imageInput: ImageInput,
|
|
436
|
-
prompt: string,
|
|
437
|
-
options: VisionOptions,
|
|
438
|
-
): Promise<VisionResult> {
|
|
439
|
-
const host = process.env.OLLAMA_HOST || 'http://localhost:11434'
|
|
440
|
-
const model = options.model || 'llava'
|
|
441
|
-
|
|
442
|
-
const { data } = await resolveImageToBase64(imageInput)
|
|
443
|
-
|
|
444
|
-
const response = await fetch(`${host}/api/generate`, {
|
|
445
|
-
method: 'POST',
|
|
446
|
-
headers: { 'Content-Type': 'application/json' },
|
|
447
|
-
body: JSON.stringify({
|
|
448
|
-
model,
|
|
449
|
-
prompt,
|
|
450
|
-
images: [data],
|
|
451
|
-
stream: false,
|
|
452
|
-
}),
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
if (!response.ok) {
|
|
456
|
-
const error = await response.text()
|
|
457
|
-
throw new Error(`Ollama Vision API error: ${error}`)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const responseData = (await response.json()) as any
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
content: responseData.response,
|
|
464
|
-
model: responseData.model,
|
|
465
|
-
provider: 'ollama',
|
|
466
|
-
usage: {
|
|
467
|
-
promptTokens: responseData.prompt_eval_count || 0,
|
|
468
|
-
completionTokens: responseData.eval_count || 0,
|
|
469
|
-
totalTokens: (responseData.prompt_eval_count || 0) + (responseData.eval_count || 0),
|
|
470
|
-
},
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// ============================================================================
|
|
475
|
-
// Multi-image Analysis
|
|
476
|
-
// ============================================================================
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Analyze multiple images together with a prompt.
|
|
480
|
-
* Useful for comparison, batch analysis, etc.
|
|
481
|
-
*/
|
|
482
|
-
export async function analyzeImages(
|
|
483
|
-
images: ImageInput[],
|
|
484
|
-
prompt: string,
|
|
485
|
-
options: VisionOptions = {},
|
|
486
|
-
): Promise<VisionResult> {
|
|
487
|
-
const { provider = 'anthropic' } = options
|
|
488
|
-
|
|
489
|
-
if (provider === 'anthropic') {
|
|
490
|
-
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
491
|
-
if (!apiKey) throw new Error('ANTHROPIC_API_KEY required for multi-image analysis.')
|
|
492
|
-
|
|
493
|
-
const model = options.model || 'claude-sonnet-4-20250514'
|
|
494
|
-
const contentBlocks: any[] = []
|
|
495
|
-
|
|
496
|
-
for (const img of images) {
|
|
497
|
-
const { data, mediaType } = await resolveImageToBase64(img)
|
|
498
|
-
contentBlocks.push({
|
|
499
|
-
type: 'image',
|
|
500
|
-
source: { type: 'base64', media_type: mediaType, data },
|
|
501
|
-
})
|
|
502
|
-
}
|
|
503
|
-
contentBlocks.push({ type: 'text', text: prompt })
|
|
504
|
-
|
|
505
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
506
|
-
method: 'POST',
|
|
507
|
-
headers: {
|
|
508
|
-
'Content-Type': 'application/json',
|
|
509
|
-
'x-api-key': apiKey,
|
|
510
|
-
'anthropic-version': '2023-06-01',
|
|
511
|
-
},
|
|
512
|
-
body: JSON.stringify({
|
|
513
|
-
model,
|
|
514
|
-
max_tokens: options.maxTokens || 4096,
|
|
515
|
-
temperature: options.temperature,
|
|
516
|
-
messages: [{ role: 'user', content: contentBlocks }],
|
|
517
|
-
}),
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
if (!response.ok) {
|
|
521
|
-
const error = await response.text()
|
|
522
|
-
throw new Error(`Claude Vision API error: ${error}`)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const responseData = (await response.json()) as any
|
|
526
|
-
return {
|
|
527
|
-
content: responseData.content[0].text,
|
|
528
|
-
model: responseData.model,
|
|
529
|
-
provider: 'anthropic',
|
|
530
|
-
usage: {
|
|
531
|
-
promptTokens: responseData.usage?.input_tokens || 0,
|
|
532
|
-
completionTokens: responseData.usage?.output_tokens || 0,
|
|
533
|
-
totalTokens: (responseData.usage?.input_tokens || 0) + (responseData.usage?.output_tokens || 0),
|
|
534
|
-
},
|
|
535
|
-
finishReason: responseData.stop_reason,
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (provider === 'openai') {
|
|
540
|
-
const apiKey = process.env.OPENAI_API_KEY
|
|
541
|
-
if (!apiKey) throw new Error('OPENAI_API_KEY required for multi-image analysis.')
|
|
542
|
-
|
|
543
|
-
const model = options.model || 'gpt-4o'
|
|
544
|
-
const detail = options.detail || 'auto'
|
|
545
|
-
const contentBlocks: any[] = []
|
|
546
|
-
|
|
547
|
-
for (const img of images) {
|
|
548
|
-
if (img.type === 'url') {
|
|
549
|
-
contentBlocks.push({ type: 'image_url', image_url: { url: img.url, detail } })
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
const { data, mediaType } = await resolveImageToBase64(img)
|
|
553
|
-
contentBlocks.push({
|
|
554
|
-
type: 'image_url',
|
|
555
|
-
image_url: { url: `data:${mediaType};base64,${data}`, detail },
|
|
556
|
-
})
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
contentBlocks.push({ type: 'text', text: prompt })
|
|
560
|
-
|
|
561
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
562
|
-
method: 'POST',
|
|
563
|
-
headers: {
|
|
564
|
-
'Content-Type': 'application/json',
|
|
565
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
566
|
-
},
|
|
567
|
-
body: JSON.stringify({
|
|
568
|
-
model,
|
|
569
|
-
max_tokens: options.maxTokens || 4096,
|
|
570
|
-
temperature: options.temperature,
|
|
571
|
-
messages: [{ role: 'user', content: contentBlocks }],
|
|
572
|
-
}),
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
if (!response.ok) {
|
|
576
|
-
const error = await response.text()
|
|
577
|
-
throw new Error(`OpenAI Vision API error: ${error}`)
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const responseData = (await response.json()) as any
|
|
581
|
-
return {
|
|
582
|
-
content: responseData.choices[0].message.content,
|
|
583
|
-
model: responseData.model,
|
|
584
|
-
provider: 'openai',
|
|
585
|
-
usage: {
|
|
586
|
-
promptTokens: responseData.usage?.prompt_tokens || 0,
|
|
587
|
-
completionTokens: responseData.usage?.completion_tokens || 0,
|
|
588
|
-
totalTokens: responseData.usage?.total_tokens || 0,
|
|
589
|
-
},
|
|
590
|
-
finishReason: responseData.choices[0].finish_reason,
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
throw new Error(`Multi-image analysis not supported for provider: ${provider}`)
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// ============================================================================
|
|
598
|
-
// Exports
|
|
599
|
-
// ============================================================================
|
|
600
|
-
|
|
601
|
-
export const image = {
|
|
602
|
-
generate: generateImage,
|
|
603
|
-
edit: editImage,
|
|
604
|
-
variation: createImageVariation,
|
|
605
|
-
analyze: analyzeImage,
|
|
606
|
-
analyzeMultiple: analyzeImages,
|
|
607
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// Types
|
|
2
|
-
export * from './types'
|
|
3
|
-
|
|
4
|
-
// Drivers
|
|
5
|
-
export * from './drivers'
|
|
6
|
-
|
|
7
|
-
// Agents
|
|
8
|
-
export * from './agents'
|
|
9
|
-
|
|
10
|
-
// Buddy - Voice AI Code Assistant
|
|
11
|
-
export * from './buddy'
|
|
12
|
-
|
|
13
|
-
// Text utilities
|
|
14
|
-
export * from './text'
|
|
15
|
-
|
|
16
|
-
// Image generation & vision
|
|
17
|
-
export * from './image'
|
|
18
|
-
|
|
19
|
-
// Semantic search, embeddings & RAG
|
|
20
|
-
export * from './search'
|
|
21
|
-
|
|
22
|
-
// Personalization, sentiment & classification
|
|
23
|
-
export * from './personalization'
|
|
24
|
-
|
|
25
|
-
// Model Context Protocol (MCP) client
|
|
26
|
-
export * from './mcp'
|
|
27
|
-
|
|
28
|
-
// AWS Bedrock utilities
|
|
29
|
-
export * from './utils/client-bedrock'
|
|
30
|
-
export * from './utils/client-bedrock-runtime'
|
|
31
|
-
|
|
32
|
-
// Cross-driver vision helpers (stacksjs/stacks#1878 A-3).
|
|
33
|
-
// `buildMessageWithImages(command, images)` constructs portable
|
|
34
|
-
// content arrays; `normalizeMessagesForProvider(messages, 'openai' | 'anthropic')`
|
|
35
|
-
// translates between the OpenAI image_url and Anthropic image
|
|
36
|
-
// source formats so apps can switch providers without rewriting.
|
|
37
|
-
export { buildMessageWithImages, normalizeMessagesForProvider } from './utils/vision'
|
|
38
|
-
|
|
39
|
-
// HTTP retry helper for 429/5xx (stacksjs/stacks#1878 A-5).
|
|
40
|
-
export { fetchWithRetry } from './utils/retry'
|
|
41
|
-
export type { RetryConfig } from './utils/retry'
|
|
42
|
-
|
|
43
|
-
// Usage tracking (stacksjs/stacks#1878 A-6). Apps install reporters
|
|
44
|
-
// via `onUsage(fn)`; drivers fire `recordUsage(...)` per completion.
|
|
45
|
-
// Default with no reporters is a no-op.
|
|
46
|
-
export { clearUsageReporters, listUsageReporters, onUsage, recordUsage } from './utils/usage'
|
|
47
|
-
export type { UsageRecord, UsageReporter } from './utils/usage'
|
|
48
|
-
|
|
49
|
-
// Token estimation + prompt-injection heuristics (stacksjs/stacks#1878 A-7).
|
|
50
|
-
export { estimateMessageTokens, estimateTokens, sanitizePrompt } from './utils/tokens'
|
|
51
|
-
export type { SanitizeResult } from './utils/tokens'
|
|
52
|
-
|
|
53
|
-
export * from './utils/model-access'
|