@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.
@@ -12252,7 +12252,7 @@ function zodTextFormat(zodObject, name, props) {
12252
12252
  }, (content) => zodObject.parse(JSON.parse(content)));
12253
12253
  }
12254
12254
 
12255
- const DEFAULT_MODEL = 'gpt-5-mini';
12255
+ const DEFAULT_MODEL = 'gpt-5.4-mini';
12256
12256
  const GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS = 272_000;
12257
12257
  const GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
12258
12258
  const GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
@@ -12890,6 +12890,7 @@ async function parseResponse(content, responseFormat) {
12890
12890
  }
12891
12891
 
12892
12892
  // llm-openai.ts
12893
+ const DEFAULT_IMAGE_MIME_TYPE = 'image/webp';
12893
12894
  /**
12894
12895
  * Checks if the given direct OpenAI model supports the temperature parameter.
12895
12896
  * @param model The model to check.
@@ -12924,6 +12925,135 @@ function buildJsonSchemaFormat(schema, schemaName, schemaDescription, schemaStri
12924
12925
  ...(schemaStrict !== undefined ? { strict: schemaStrict } : {}),
12925
12926
  };
12926
12927
  }
12928
+ function resolveOpenAIApiKey(apiKey) {
12929
+ const resolvedApiKey = apiKey || process.env.OPENAI_API_KEY;
12930
+ if (!resolvedApiKey) {
12931
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
12932
+ }
12933
+ return resolvedApiKey;
12934
+ }
12935
+ function createOpenAIClient(apiKey) {
12936
+ return new OpenAI({
12937
+ apiKey,
12938
+ });
12939
+ }
12940
+ function normalizeImageDataUrl(imageData, mimeType = DEFAULT_IMAGE_MIME_TYPE) {
12941
+ return imageData.startsWith('data:') ? imageData : `data:${mimeType};base64,${imageData}`;
12942
+ }
12943
+ function getFilenameFromPath(filePath) {
12944
+ const normalizedPath = filePath.replace(/\\/g, '/');
12945
+ const pathSegments = normalizedPath.split('/');
12946
+ const filename = pathSegments[pathSegments.length - 1];
12947
+ if (!filename) {
12948
+ throw new Error(`Could not determine a filename from file path: ${filePath}`);
12949
+ }
12950
+ return filename;
12951
+ }
12952
+ function inferImageMimeType(filePath) {
12953
+ const normalizedPath = filePath.toLowerCase();
12954
+ if (normalizedPath.endsWith('.png')) {
12955
+ return 'image/png';
12956
+ }
12957
+ if (normalizedPath.endsWith('.jpg') || normalizedPath.endsWith('.jpeg')) {
12958
+ return 'image/jpeg';
12959
+ }
12960
+ if (normalizedPath.endsWith('.webp')) {
12961
+ return 'image/webp';
12962
+ }
12963
+ if (normalizedPath.endsWith('.gif')) {
12964
+ return 'image/gif';
12965
+ }
12966
+ return undefined;
12967
+ }
12968
+ async function readLocalFileBytes(filePath) {
12969
+ try {
12970
+ const dynamicImport = new Function('specifier', 'return import(specifier);');
12971
+ const { readFile } = await dynamicImport('node:fs/promises');
12972
+ return await readFile(filePath);
12973
+ }
12974
+ catch {
12975
+ throw new Error('Local image file uploads require a Node.js runtime with access to node:fs/promises');
12976
+ }
12977
+ }
12978
+ async function uploadVisionFileFromPath(openai, filePath, filename, mimeType) {
12979
+ const fileBytes = await readLocalFileBytes(filePath);
12980
+ const resolvedFilename = filename || getFilenameFromPath(filePath);
12981
+ const resolvedMimeType = mimeType || inferImageMimeType(filePath);
12982
+ const uploadableFile = await OpenAI.toFile(fileBytes, resolvedFilename, resolvedMimeType ? { type: resolvedMimeType } : undefined);
12983
+ const uploadedFile = await openai.files.create({
12984
+ file: uploadableFile,
12985
+ purpose: 'vision',
12986
+ });
12987
+ return uploadedFile.id;
12988
+ }
12989
+ function collectImageInputs(image, images, imageBase64) {
12990
+ const collectedImages = [];
12991
+ if (image) {
12992
+ collectedImages.push(image);
12993
+ }
12994
+ if (images && images.length > 0) {
12995
+ collectedImages.push(...images);
12996
+ }
12997
+ if (imageBase64) {
12998
+ collectedImages.push({ base64: imageBase64 });
12999
+ }
13000
+ return collectedImages;
13001
+ }
13002
+ function hasLocalFileImage(images) {
13003
+ return images.some((image) => 'filePath' in image);
13004
+ }
13005
+ async function buildImageContentPart(image, defaultDetail, openai) {
13006
+ const detail = image.detail ?? defaultDetail;
13007
+ if ('url' in image) {
13008
+ return {
13009
+ type: 'input_image',
13010
+ detail,
13011
+ image_url: image.url,
13012
+ };
13013
+ }
13014
+ if ('dataUrl' in image) {
13015
+ return {
13016
+ type: 'input_image',
13017
+ detail,
13018
+ image_url: image.dataUrl,
13019
+ };
13020
+ }
13021
+ if ('base64' in image) {
13022
+ return {
13023
+ type: 'input_image',
13024
+ detail,
13025
+ image_url: normalizeImageDataUrl(image.base64, image.mimeType),
13026
+ };
13027
+ }
13028
+ if ('fileId' in image) {
13029
+ return {
13030
+ type: 'input_image',
13031
+ detail,
13032
+ file_id: image.fileId,
13033
+ };
13034
+ }
13035
+ if (!openai) {
13036
+ throw new Error('A local image file path was provided, but no OpenAI client is available to upload it');
13037
+ }
13038
+ const fileId = await uploadVisionFileFromPath(openai, image.filePath, image.filename, image.mimeType);
13039
+ return {
13040
+ type: 'input_image',
13041
+ detail,
13042
+ file_id: fileId,
13043
+ };
13044
+ }
13045
+ async function buildPromptContent(input, imageInputs, imageDetail, openai) {
13046
+ if (imageInputs.length === 0) {
13047
+ return input;
13048
+ }
13049
+ const imageContent = await Promise.all(imageInputs.map((image) => buildImageContentPart(image, imageDetail, openai)));
13050
+ return [{ type: 'input_text', text: input }, ...imageContent];
13051
+ }
13052
+ async function uploadVisionFile(filePath, options = {}) {
13053
+ const resolvedApiKey = resolveOpenAIApiKey(options.apiKey);
13054
+ const openai = createOpenAIClient(resolvedApiKey);
13055
+ return await uploadVisionFileFromPath(openai, filePath, options.filename, options.mimeType);
13056
+ }
12927
13057
  /**
12928
13058
  * Makes a call to OpenAI's Responses API for more advanced use cases with built-in tools.
12929
13059
  *
@@ -12955,13 +13085,8 @@ const makeResponsesAPICall = async (input, options = {}) => {
12955
13085
  if (!isOpenAIModel(normalizedModel)) {
12956
13086
  throw new Error(`Unsupported OpenAI model: ${normalizedModel}. Please use a supported GPT-5 model.`);
12957
13087
  }
12958
- const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
12959
- if (!apiKey) {
12960
- throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
12961
- }
12962
- const openai = new OpenAI({
12963
- apiKey: apiKey,
12964
- });
13088
+ const apiKey = resolveOpenAIApiKey(options.apiKey);
13089
+ const openai = createOpenAIClient(apiKey);
12965
13090
  // Remove apiKey from options before creating request body
12966
13091
  const { apiKey: _, model: __, ...cleanOptions } = options;
12967
13092
  const requestBody = {
@@ -13072,12 +13197,16 @@ const makeResponsesAPICall = async (input, options = {}) => {
13072
13197
  * });
13073
13198
  */
13074
13199
  async function makeLLMCall(input, options = {}) {
13075
- const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', schema, schemaName = 'response', schemaDescription, schemaStrict, tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high', context, } = options;
13200
+ const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', schema, schemaName = 'response', schemaDescription, schemaStrict, tools, useCodeInterpreter = false, useWebSearch = false, image, images, imageBase64, imageDetail = 'high', context, } = options;
13076
13201
  // Validate model
13077
13202
  const normalizedModel = normalizeModelName(model);
13078
13203
  if (!isOpenAIModel(normalizedModel)) {
13079
13204
  throw new Error(`Unsupported OpenAI model: ${normalizedModel}. Please use a supported GPT-5 model.`);
13080
13205
  }
13206
+ const resolvedApiKey = resolveOpenAIApiKey(apiKey);
13207
+ const imageInputs = collectImageInputs(image, images, imageBase64);
13208
+ const openai = hasLocalFileImage(imageInputs) ? createOpenAIClient(resolvedApiKey) : undefined;
13209
+ const promptContent = await buildPromptContent(input, imageInputs, imageDetail, openai);
13081
13210
  // Process input for conversation context and image analysis
13082
13211
  let processedInput;
13083
13212
  if (context && context.length > 0) {
@@ -13092,18 +13221,11 @@ async function makeLLMCall(input, options = {}) {
13092
13221
  });
13093
13222
  }
13094
13223
  // Add current input message
13095
- if (imageBase64) {
13096
- // Current message includes both text and image
13224
+ if (imageInputs.length > 0) {
13225
+ // Current message includes text and one or more images
13097
13226
  conversationMessages.push({
13098
13227
  role: 'user',
13099
- content: [
13100
- { type: 'input_text', text: input },
13101
- {
13102
- type: 'input_image',
13103
- detail: imageDetail,
13104
- image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
13105
- },
13106
- ],
13228
+ content: promptContent,
13107
13229
  type: 'message',
13108
13230
  });
13109
13231
  }
@@ -13117,19 +13239,12 @@ async function makeLLMCall(input, options = {}) {
13117
13239
  }
13118
13240
  processedInput = conversationMessages;
13119
13241
  }
13120
- else if (imageBase64) {
13121
- // No context, but has image - use the original image logic
13242
+ else if (imageInputs.length > 0) {
13243
+ // No context, but has image(s)
13122
13244
  processedInput = [
13123
13245
  {
13124
13246
  role: 'user',
13125
- content: [
13126
- { type: 'input_text', text: input },
13127
- {
13128
- type: 'input_image',
13129
- detail: imageDetail,
13130
- image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
13131
- },
13132
- ],
13247
+ content: promptContent,
13133
13248
  type: 'message',
13134
13249
  },
13135
13250
  ];
@@ -13140,7 +13255,7 @@ async function makeLLMCall(input, options = {}) {
13140
13255
  }
13141
13256
  // Build the options object for makeResponsesAPICall
13142
13257
  let responsesOptions = {
13143
- apiKey,
13258
+ apiKey: resolvedApiKey,
13144
13259
  model: normalizedModel,
13145
13260
  parallel_tool_calls: false,
13146
13261
  tools,
@@ -14626,11 +14741,12 @@ const disco = {
14626
14741
  types: Types,
14627
14742
  llm: {
14628
14743
  call: makeLLMCall,
14744
+ uploadVisionFile: uploadVisionFile,
14629
14745
  seek: makeDeepseekCall,
14630
14746
  images: makeImagesCall,
14631
14747
  },
14632
14748
  time,
14633
14749
  };
14634
14750
 
14635
- export { OPENAI_IMAGE_MODELS, OPENAI_MODELS, OPENROUTER_MODELS, disco, isOpenAIImageModel, isOpenAIModel, isOpenRouterModel, supportsTemperature };
14751
+ export { OPENAI_IMAGE_MODELS, OPENAI_MODELS, OPENROUTER_MODELS, disco, isOpenAIImageModel, isOpenAIModel, isOpenRouterModel, supportsTemperature, uploadVisionFile };
14636
14752
  //# sourceMappingURL=index-frontend.mjs.map