@discomedia/utils 1.0.71 → 1.0.72
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/dist/index-frontend.cjs +147 -30
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +147 -31
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/index.cjs +147 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +147 -31
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +1 -1
- package/dist/test.js +4564 -5188
- package/dist/test.js.map +1 -1
- package/dist/types/index-frontend.d.ts +2 -1
- package/dist/types/index-frontend.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/llm-config.d.ts +2 -2
- package/dist/types/llm-config.d.ts.map +1 -1
- package/dist/types/llm-openai.d.ts +9 -2
- package/dist/types/llm-openai.d.ts.map +1 -1
- package/dist/types/types/llm-types.d.ts +48 -1
- package/dist/types/types/llm-types.d.ts.map +1 -1
- package/dist/types-frontend/index-frontend.d.ts +2 -1
- package/dist/types-frontend/index-frontend.d.ts.map +1 -1
- package/dist/types-frontend/index.d.ts +2 -1
- package/dist/types-frontend/index.d.ts.map +1 -1
- package/dist/types-frontend/llm-config.d.ts +2 -2
- package/dist/types-frontend/llm-config.d.ts.map +1 -1
- package/dist/types-frontend/llm-openai.d.ts +9 -2
- package/dist/types-frontend/llm-openai.d.ts.map +1 -1
- package/dist/types-frontend/types/llm-types.d.ts +48 -1
- package/dist/types-frontend/types/llm-types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -13608,7 +13608,7 @@ function zodTextFormat(zodObject, name, props) {
|
|
|
13608
13608
|
}, (content) => zodObject.parse(JSON.parse(content)));
|
|
13609
13609
|
}
|
|
13610
13610
|
|
|
13611
|
-
const DEFAULT_MODEL = 'gpt-5-mini';
|
|
13611
|
+
const DEFAULT_MODEL = 'gpt-5.4-mini';
|
|
13612
13612
|
const GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS = 272_000;
|
|
13613
13613
|
const GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
|
|
13614
13614
|
const GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
|
|
@@ -14246,6 +14246,7 @@ async function parseResponse(content, responseFormat) {
|
|
|
14246
14246
|
}
|
|
14247
14247
|
|
|
14248
14248
|
// llm-openai.ts
|
|
14249
|
+
const DEFAULT_IMAGE_MIME_TYPE = 'image/webp';
|
|
14249
14250
|
/**
|
|
14250
14251
|
* Checks if the given direct OpenAI model supports the temperature parameter.
|
|
14251
14252
|
* @param model The model to check.
|
|
@@ -14280,6 +14281,135 @@ function buildJsonSchemaFormat(schema, schemaName, schemaDescription, schemaStri
|
|
|
14280
14281
|
...(schemaStrict !== undefined ? { strict: schemaStrict } : {}),
|
|
14281
14282
|
};
|
|
14282
14283
|
}
|
|
14284
|
+
function resolveOpenAIApiKey(apiKey) {
|
|
14285
|
+
const resolvedApiKey = apiKey || process.env.OPENAI_API_KEY;
|
|
14286
|
+
if (!resolvedApiKey) {
|
|
14287
|
+
throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
|
|
14288
|
+
}
|
|
14289
|
+
return resolvedApiKey;
|
|
14290
|
+
}
|
|
14291
|
+
function createOpenAIClient(apiKey) {
|
|
14292
|
+
return new OpenAI({
|
|
14293
|
+
apiKey,
|
|
14294
|
+
});
|
|
14295
|
+
}
|
|
14296
|
+
function normalizeImageDataUrl(imageData, mimeType = DEFAULT_IMAGE_MIME_TYPE) {
|
|
14297
|
+
return imageData.startsWith('data:') ? imageData : `data:${mimeType};base64,${imageData}`;
|
|
14298
|
+
}
|
|
14299
|
+
function getFilenameFromPath(filePath) {
|
|
14300
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
14301
|
+
const pathSegments = normalizedPath.split('/');
|
|
14302
|
+
const filename = pathSegments[pathSegments.length - 1];
|
|
14303
|
+
if (!filename) {
|
|
14304
|
+
throw new Error(`Could not determine a filename from file path: ${filePath}`);
|
|
14305
|
+
}
|
|
14306
|
+
return filename;
|
|
14307
|
+
}
|
|
14308
|
+
function inferImageMimeType(filePath) {
|
|
14309
|
+
const normalizedPath = filePath.toLowerCase();
|
|
14310
|
+
if (normalizedPath.endsWith('.png')) {
|
|
14311
|
+
return 'image/png';
|
|
14312
|
+
}
|
|
14313
|
+
if (normalizedPath.endsWith('.jpg') || normalizedPath.endsWith('.jpeg')) {
|
|
14314
|
+
return 'image/jpeg';
|
|
14315
|
+
}
|
|
14316
|
+
if (normalizedPath.endsWith('.webp')) {
|
|
14317
|
+
return 'image/webp';
|
|
14318
|
+
}
|
|
14319
|
+
if (normalizedPath.endsWith('.gif')) {
|
|
14320
|
+
return 'image/gif';
|
|
14321
|
+
}
|
|
14322
|
+
return undefined;
|
|
14323
|
+
}
|
|
14324
|
+
async function readLocalFileBytes(filePath) {
|
|
14325
|
+
try {
|
|
14326
|
+
const dynamicImport = new Function('specifier', 'return import(specifier);');
|
|
14327
|
+
const { readFile } = await dynamicImport('node:fs/promises');
|
|
14328
|
+
return await readFile(filePath);
|
|
14329
|
+
}
|
|
14330
|
+
catch {
|
|
14331
|
+
throw new Error('Local image file uploads require a Node.js runtime with access to node:fs/promises');
|
|
14332
|
+
}
|
|
14333
|
+
}
|
|
14334
|
+
async function uploadVisionFileFromPath(openai, filePath, filename, mimeType) {
|
|
14335
|
+
const fileBytes = await readLocalFileBytes(filePath);
|
|
14336
|
+
const resolvedFilename = filename || getFilenameFromPath(filePath);
|
|
14337
|
+
const resolvedMimeType = mimeType || inferImageMimeType(filePath);
|
|
14338
|
+
const uploadableFile = await OpenAI.toFile(fileBytes, resolvedFilename, resolvedMimeType ? { type: resolvedMimeType } : undefined);
|
|
14339
|
+
const uploadedFile = await openai.files.create({
|
|
14340
|
+
file: uploadableFile,
|
|
14341
|
+
purpose: 'vision',
|
|
14342
|
+
});
|
|
14343
|
+
return uploadedFile.id;
|
|
14344
|
+
}
|
|
14345
|
+
function collectImageInputs(image, images, imageBase64) {
|
|
14346
|
+
const collectedImages = [];
|
|
14347
|
+
if (image) {
|
|
14348
|
+
collectedImages.push(image);
|
|
14349
|
+
}
|
|
14350
|
+
if (images && images.length > 0) {
|
|
14351
|
+
collectedImages.push(...images);
|
|
14352
|
+
}
|
|
14353
|
+
if (imageBase64) {
|
|
14354
|
+
collectedImages.push({ base64: imageBase64 });
|
|
14355
|
+
}
|
|
14356
|
+
return collectedImages;
|
|
14357
|
+
}
|
|
14358
|
+
function hasLocalFileImage(images) {
|
|
14359
|
+
return images.some((image) => 'filePath' in image);
|
|
14360
|
+
}
|
|
14361
|
+
async function buildImageContentPart(image, defaultDetail, openai) {
|
|
14362
|
+
const detail = image.detail ?? defaultDetail;
|
|
14363
|
+
if ('url' in image) {
|
|
14364
|
+
return {
|
|
14365
|
+
type: 'input_image',
|
|
14366
|
+
detail,
|
|
14367
|
+
image_url: image.url,
|
|
14368
|
+
};
|
|
14369
|
+
}
|
|
14370
|
+
if ('dataUrl' in image) {
|
|
14371
|
+
return {
|
|
14372
|
+
type: 'input_image',
|
|
14373
|
+
detail,
|
|
14374
|
+
image_url: image.dataUrl,
|
|
14375
|
+
};
|
|
14376
|
+
}
|
|
14377
|
+
if ('base64' in image) {
|
|
14378
|
+
return {
|
|
14379
|
+
type: 'input_image',
|
|
14380
|
+
detail,
|
|
14381
|
+
image_url: normalizeImageDataUrl(image.base64, image.mimeType),
|
|
14382
|
+
};
|
|
14383
|
+
}
|
|
14384
|
+
if ('fileId' in image) {
|
|
14385
|
+
return {
|
|
14386
|
+
type: 'input_image',
|
|
14387
|
+
detail,
|
|
14388
|
+
file_id: image.fileId,
|
|
14389
|
+
};
|
|
14390
|
+
}
|
|
14391
|
+
if (!openai) {
|
|
14392
|
+
throw new Error('A local image file path was provided, but no OpenAI client is available to upload it');
|
|
14393
|
+
}
|
|
14394
|
+
const fileId = await uploadVisionFileFromPath(openai, image.filePath, image.filename, image.mimeType);
|
|
14395
|
+
return {
|
|
14396
|
+
type: 'input_image',
|
|
14397
|
+
detail,
|
|
14398
|
+
file_id: fileId,
|
|
14399
|
+
};
|
|
14400
|
+
}
|
|
14401
|
+
async function buildPromptContent(input, imageInputs, imageDetail, openai) {
|
|
14402
|
+
if (imageInputs.length === 0) {
|
|
14403
|
+
return input;
|
|
14404
|
+
}
|
|
14405
|
+
const imageContent = await Promise.all(imageInputs.map((image) => buildImageContentPart(image, imageDetail, openai)));
|
|
14406
|
+
return [{ type: 'input_text', text: input }, ...imageContent];
|
|
14407
|
+
}
|
|
14408
|
+
async function uploadVisionFile(filePath, options = {}) {
|
|
14409
|
+
const resolvedApiKey = resolveOpenAIApiKey(options.apiKey);
|
|
14410
|
+
const openai = createOpenAIClient(resolvedApiKey);
|
|
14411
|
+
return await uploadVisionFileFromPath(openai, filePath, options.filename, options.mimeType);
|
|
14412
|
+
}
|
|
14283
14413
|
/**
|
|
14284
14414
|
* Makes a call to OpenAI's Responses API for more advanced use cases with built-in tools.
|
|
14285
14415
|
*
|
|
@@ -14311,13 +14441,8 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
14311
14441
|
if (!isOpenAIModel(normalizedModel)) {
|
|
14312
14442
|
throw new Error(`Unsupported OpenAI model: ${normalizedModel}. Please use a supported GPT-5 model.`);
|
|
14313
14443
|
}
|
|
14314
|
-
const apiKey = options.apiKey
|
|
14315
|
-
|
|
14316
|
-
throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
|
|
14317
|
-
}
|
|
14318
|
-
const openai = new OpenAI({
|
|
14319
|
-
apiKey: apiKey,
|
|
14320
|
-
});
|
|
14444
|
+
const apiKey = resolveOpenAIApiKey(options.apiKey);
|
|
14445
|
+
const openai = createOpenAIClient(apiKey);
|
|
14321
14446
|
// Remove apiKey from options before creating request body
|
|
14322
14447
|
const { apiKey: _, model: __, ...cleanOptions } = options;
|
|
14323
14448
|
const requestBody = {
|
|
@@ -14428,12 +14553,16 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
14428
14553
|
* });
|
|
14429
14554
|
*/
|
|
14430
14555
|
async function makeLLMCall(input, options = {}) {
|
|
14431
|
-
const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', schema, schemaName = 'response', schemaDescription, schemaStrict, tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high', context, } = options;
|
|
14556
|
+
const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', schema, schemaName = 'response', schemaDescription, schemaStrict, tools, useCodeInterpreter = false, useWebSearch = false, image, images, imageBase64, imageDetail = 'high', context, } = options;
|
|
14432
14557
|
// Validate model
|
|
14433
14558
|
const normalizedModel = normalizeModelName(model);
|
|
14434
14559
|
if (!isOpenAIModel(normalizedModel)) {
|
|
14435
14560
|
throw new Error(`Unsupported OpenAI model: ${normalizedModel}. Please use a supported GPT-5 model.`);
|
|
14436
14561
|
}
|
|
14562
|
+
const resolvedApiKey = resolveOpenAIApiKey(apiKey);
|
|
14563
|
+
const imageInputs = collectImageInputs(image, images, imageBase64);
|
|
14564
|
+
const openai = hasLocalFileImage(imageInputs) ? createOpenAIClient(resolvedApiKey) : undefined;
|
|
14565
|
+
const promptContent = await buildPromptContent(input, imageInputs, imageDetail, openai);
|
|
14437
14566
|
// Process input for conversation context and image analysis
|
|
14438
14567
|
let processedInput;
|
|
14439
14568
|
if (context && context.length > 0) {
|
|
@@ -14448,18 +14577,11 @@ async function makeLLMCall(input, options = {}) {
|
|
|
14448
14577
|
});
|
|
14449
14578
|
}
|
|
14450
14579
|
// Add current input message
|
|
14451
|
-
if (
|
|
14452
|
-
// Current message includes
|
|
14580
|
+
if (imageInputs.length > 0) {
|
|
14581
|
+
// Current message includes text and one or more images
|
|
14453
14582
|
conversationMessages.push({
|
|
14454
14583
|
role: 'user',
|
|
14455
|
-
content:
|
|
14456
|
-
{ type: 'input_text', text: input },
|
|
14457
|
-
{
|
|
14458
|
-
type: 'input_image',
|
|
14459
|
-
detail: imageDetail,
|
|
14460
|
-
image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
|
|
14461
|
-
},
|
|
14462
|
-
],
|
|
14584
|
+
content: promptContent,
|
|
14463
14585
|
type: 'message',
|
|
14464
14586
|
});
|
|
14465
14587
|
}
|
|
@@ -14473,19 +14595,12 @@ async function makeLLMCall(input, options = {}) {
|
|
|
14473
14595
|
}
|
|
14474
14596
|
processedInput = conversationMessages;
|
|
14475
14597
|
}
|
|
14476
|
-
else if (
|
|
14477
|
-
// No context, but has image
|
|
14598
|
+
else if (imageInputs.length > 0) {
|
|
14599
|
+
// No context, but has image(s)
|
|
14478
14600
|
processedInput = [
|
|
14479
14601
|
{
|
|
14480
14602
|
role: 'user',
|
|
14481
|
-
content:
|
|
14482
|
-
{ type: 'input_text', text: input },
|
|
14483
|
-
{
|
|
14484
|
-
type: 'input_image',
|
|
14485
|
-
detail: imageDetail,
|
|
14486
|
-
image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
|
|
14487
|
-
},
|
|
14488
|
-
],
|
|
14603
|
+
content: promptContent,
|
|
14489
14604
|
type: 'message',
|
|
14490
14605
|
},
|
|
14491
14606
|
];
|
|
@@ -14496,7 +14611,7 @@ async function makeLLMCall(input, options = {}) {
|
|
|
14496
14611
|
}
|
|
14497
14612
|
// Build the options object for makeResponsesAPICall
|
|
14498
14613
|
let responsesOptions = {
|
|
14499
|
-
apiKey,
|
|
14614
|
+
apiKey: resolvedApiKey,
|
|
14500
14615
|
model: normalizedModel,
|
|
14501
14616
|
parallel_tool_calls: false,
|
|
14502
14617
|
tools,
|
|
@@ -23638,6 +23753,7 @@ const disco = {
|
|
|
23638
23753
|
},
|
|
23639
23754
|
llm: {
|
|
23640
23755
|
call: makeLLMCall,
|
|
23756
|
+
uploadVisionFile: uploadVisionFile,
|
|
23641
23757
|
seek: makeDeepseekCall,
|
|
23642
23758
|
images: makeImagesCall,
|
|
23643
23759
|
open: makeOpenRouterCall,
|
|
@@ -23678,4 +23794,5 @@ exports.isOpenAIImageModel = isOpenAIImageModel;
|
|
|
23678
23794
|
exports.isOpenAIModel = isOpenAIModel;
|
|
23679
23795
|
exports.isOpenRouterModel = isOpenRouterModel;
|
|
23680
23796
|
exports.supportsTemperature = supportsTemperature;
|
|
23797
|
+
exports.uploadVisionFile = uploadVisionFile;
|
|
23681
23798
|
//# sourceMappingURL=index.cjs.map
|