@ljoukov/llm 7.0.16 → 7.0.18

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/README.md CHANGED
@@ -209,9 +209,10 @@ console.log(OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS);
209
209
  console.log(images[0]?.mimeType, images[0]?.data.byteLength);
210
210
  ```
211
211
 
212
- `generateImages()` is typed as a discriminated union by `model`: `gpt-image-2` requests use
213
- `imageResolution`, while Gemini image requests use `imageSize` (`"1K" | "2K" | "4K"`). For
214
- `gpt-image-2`, `OPENAI_GPT_IMAGE_2_RESOLUTIONS` exposes the documented popular presets plus
212
+ `generateImages()` is typed as a discriminated union by `model`: `gpt-image-2` and
213
+ `chatgpt-gpt-image-2` requests use `imageResolution`, while Gemini image requests use `imageSize`
214
+ (`"1K" | "2K" | "4K"`). For GPT Image 2, `OPENAI_GPT_IMAGE_2_RESOLUTIONS` exposes the documented
215
+ popular presets plus
215
216
  `"auto"`; custom literal `WIDTHxHEIGHT` resolutions are also accepted when they satisfy
216
217
  `OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS`: each edge must be at most 3840px, each edge must be a
217
218
  multiple of 16px, the long edge must be at most 3:1 relative to the short edge, and total pixels
@@ -226,14 +227,20 @@ const images = await generateImages({
226
227
  model: "chatgpt-gpt-image-2",
227
228
  stylePrompt: "Warm amber desk light, deep blue night, cinematic laboratory mood.",
228
229
  imagePrompts: ["A compact lab bench still life with glassware and an open notebook"],
230
+ imageResolution: "1024x1536",
231
+ imageQuality: "high",
232
+ outputFormat: "jpeg",
233
+ outputCompression: 50,
234
+ action: "generate",
229
235
  numImages: 1,
230
236
  });
231
237
  ```
232
238
 
233
239
  That path reuses the same ChatGPT auth setup as other `chatgpt-*` models and sends the request
234
- through the ChatGPT/Codex Responses `image_generation` built-in tool. It returns PNG images. The
235
- public Images API controls such as `imageResolution`, `imageQuality`, `outputFormat`, and
236
- `outputCompression` are intentionally only on the `gpt-image-2` request type.
240
+ through the ChatGPT/Codex Responses `image_generation` built-in tool. `imageResolution`,
241
+ `imageQuality`, `outputFormat`, `outputCompression`, `background`, `moderation`, and `action` are
242
+ passed as tool options. `numImages` is implemented as repeated one-image tool calls because the
243
+ ChatGPT/Codex tool rejects `n` on `tools[0]`.
237
244
 
238
245
  ### Streaming (response + thoughts + usage)
239
246
 
package/dist/index.cjs CHANGED
@@ -481,6 +481,7 @@ function resolveChatGptServiceTier(model) {
481
481
  // src/openai/pricing.ts
482
482
  var OPENAI_GPT_55_FAST_MODEL_IDS = ["gpt-5.5-fast", "chatgpt-gpt-5.5-fast"];
483
483
  var OPENAI_GPT_55_STANDARD_MODEL_IDS = ["gpt-5.5", "chatgpt-gpt-5.5"];
484
+ var OPENAI_GPT_55_CONCRETE_MODEL_ID_RE = /^(?:chatgpt-)?gpt-5\.5-\d{4}-\d{2}-\d{2}$/u;
484
485
  var OPENAI_GPT_54_FAST_MODEL_IDS = ["gpt-5.4-fast", "chatgpt-gpt-5.4-fast"];
485
486
  var OPENAI_GPT_54_MINI_MODEL_IDS = ["gpt-5.4-mini", "chatgpt-gpt-5.4-mini"];
486
487
  var OPENAI_GPT_54_NANO_MODEL_IDS = ["gpt-5.4-nano"];
@@ -547,7 +548,7 @@ function getOpenAiPricing(modelId) {
547
548
  if (OPENAI_GPT_55_FAST_MODEL_IDS.includes(modelId)) {
548
549
  return OPENAI_GPT_55_PRIORITY_PRICING;
549
550
  }
550
- if (OPENAI_GPT_55_STANDARD_MODEL_IDS.includes(modelId)) {
551
+ if (OPENAI_GPT_55_STANDARD_MODEL_IDS.includes(modelId) || OPENAI_GPT_55_CONCRETE_MODEL_ID_RE.test(modelId)) {
551
552
  return OPENAI_GPT_55_PRICING;
552
553
  }
553
554
  if (OPENAI_GPT_54_FAST_MODEL_IDS.includes(modelId)) {
@@ -580,12 +581,14 @@ function resolveUsageNumber(value) {
580
581
  }
581
582
  function estimateCallCostUsd({
582
583
  modelId,
584
+ pricingModelId,
583
585
  tokens,
584
586
  responseImages,
585
587
  imageSize,
586
588
  imageQuality
587
589
  }) {
588
- const openAiImagePricing = getOpenAiImagePricing(modelId);
590
+ const pricingModelIds = resolvePricingModelIds(modelId, pricingModelId);
591
+ const openAiImagePricing = resolvePricing(pricingModelIds, getOpenAiImagePricing);
589
592
  if (openAiImagePricing) {
590
593
  return estimateOpenAiImageCostUsd({
591
594
  pricing: openAiImagePricing,
@@ -605,7 +608,7 @@ function estimateCallCostUsd({
605
608
  const toolUsePromptTokens = resolveUsageNumber(tokens.toolUsePromptTokens);
606
609
  const promptTokenTotal = promptTokens + toolUsePromptTokens;
607
610
  const nonCachedPrompt = Math.max(0, promptTokenTotal - cachedTokens);
608
- const imagePreviewPricing = getGeminiImagePricing(modelId);
611
+ const imagePreviewPricing = resolvePricing(pricingModelIds, getGeminiImagePricing);
609
612
  if (imagePreviewPricing) {
610
613
  const resolvedImageSize = imageSize && imagePreviewPricing.imagePrices[imageSize] ? imageSize : "2K";
611
614
  const imageRate = imagePreviewPricing.imagePrices[resolvedImageSize] ?? 0;
@@ -625,7 +628,7 @@ function estimateCallCostUsd({
625
628
  const imageOutputCost = imageTokensForPricing * imagePreviewPricing.outputImageRate;
626
629
  return inputCost + cachedCost + textOutputCost + imageOutputCost;
627
630
  }
628
- const geminiPricing = getGeminiProPricing(modelId);
631
+ const geminiPricing = resolvePricing(pricingModelIds, getGeminiProPricing);
629
632
  if (geminiPricing) {
630
633
  const useHighTier = promptTokenTotal > geminiPricing.threshold;
631
634
  const inputRate = useHighTier ? geminiPricing.inputRateHigh : geminiPricing.inputRateLow;
@@ -637,7 +640,7 @@ function estimateCallCostUsd({
637
640
  const outputCost = outputTokens * outputRate;
638
641
  return inputCost + cachedCost + outputCost;
639
642
  }
640
- const fireworksPricing = getFireworksPricing(modelId);
643
+ const fireworksPricing = resolvePricing(pricingModelIds, getFireworksPricing);
641
644
  if (fireworksPricing) {
642
645
  const inputCost = nonCachedPrompt * fireworksPricing.inputRate;
643
646
  const cachedCost = cachedTokens * fireworksPricing.cachedRate;
@@ -645,7 +648,7 @@ function estimateCallCostUsd({
645
648
  const outputCost = outputTokens * fireworksPricing.outputRate;
646
649
  return inputCost + cachedCost + outputCost;
647
650
  }
648
- const openAiPricing = getOpenAiPricing(modelId);
651
+ const openAiPricing = resolvePricing(pricingModelIds, getOpenAiPricing);
649
652
  if (openAiPricing) {
650
653
  const inputCost = nonCachedPrompt * openAiPricing.inputRate;
651
654
  const cachedCost = cachedTokens * openAiPricing.cachedRate;
@@ -655,6 +658,21 @@ function estimateCallCostUsd({
655
658
  }
656
659
  return 0;
657
660
  }
661
+ function resolvePricingModelIds(modelId, pricingModelId) {
662
+ if (pricingModelId && pricingModelId !== modelId) {
663
+ return [pricingModelId, modelId];
664
+ }
665
+ return [modelId];
666
+ }
667
+ function resolvePricing(modelIds, resolve) {
668
+ for (const modelId of modelIds) {
669
+ const pricing = resolve(modelId);
670
+ if (pricing) {
671
+ return pricing;
672
+ }
673
+ }
674
+ return void 0;
675
+ }
658
676
  function estimateOpenAiImageCostUsd({
659
677
  pricing,
660
678
  responseImages,
@@ -8466,6 +8484,7 @@ async function runTextCall(params) {
8466
8484
  const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
8467
8485
  const costUsd = estimateCallCostUsd({
8468
8486
  modelId: modelVersion,
8487
+ pricingModelId: request.model,
8469
8488
  tokens: latestUsage,
8470
8489
  responseImages,
8471
8490
  imageSize: request.imageSize
@@ -9209,6 +9228,7 @@ async function runToolLoop(request) {
9209
9228
  const modelCompletedAtMs = Date.now();
9210
9229
  const stepCostUsd = estimateCallCostUsd({
9211
9230
  modelId: modelVersion,
9231
+ pricingModelId: request.model,
9212
9232
  tokens: usageTokens,
9213
9233
  responseImages: 0
9214
9234
  });
@@ -9540,6 +9560,7 @@ async function runToolLoop(request) {
9540
9560
  usageTokens = extractChatGptUsageTokens(response.usage);
9541
9561
  const stepCostUsd = estimateCallCostUsd({
9542
9562
  modelId: modelVersion,
9563
+ pricingModelId: request.model,
9543
9564
  tokens: usageTokens,
9544
9565
  responseImages: 0
9545
9566
  });
@@ -9868,6 +9889,7 @@ async function runToolLoop(request) {
9868
9889
  usageTokens = extractFireworksUsageTokens(response.usage);
9869
9890
  const stepCostUsd = estimateCallCostUsd({
9870
9891
  modelId: modelVersion,
9892
+ pricingModelId: request.model,
9871
9893
  tokens: usageTokens,
9872
9894
  responseImages: 0
9873
9895
  });
@@ -10230,6 +10252,7 @@ async function runToolLoop(request) {
10230
10252
  );
10231
10253
  const stepCostUsd = estimateCallCostUsd({
10232
10254
  modelId: modelVersion,
10255
+ pricingModelId: request.model,
10233
10256
  tokens: usageTokens,
10234
10257
  responseImages: 0
10235
10258
  });
@@ -10584,7 +10607,7 @@ function buildOpenAiImagePrompt(params) {
10584
10607
  params.imagePrompt.trim()
10585
10608
  ].filter((line) => line.length > 0).join("\n");
10586
10609
  }
10587
- function resolveOpenAiImageRequestParams(request) {
10610
+ function resolveGptImage2RequestParams(request) {
10588
10611
  if (request.partialImages !== void 0) {
10589
10612
  throw new Error("partialImages is only supported for streaming image generation.");
10590
10613
  }
@@ -10598,7 +10621,7 @@ function resolveOpenAiImageRequestParams(request) {
10598
10621
  const sizeValidation = validateOpenAiGptImage2Resolution(size);
10599
10622
  if (!sizeValidation.valid) {
10600
10623
  throw new Error(
10601
- `imageResolution ${JSON.stringify(size)} is not supported by gpt-image-2: ${sizeValidation.reason}`
10624
+ `imageResolution ${JSON.stringify(size)} is not supported by ${request.model}: ${sizeValidation.reason}`
10602
10625
  );
10603
10626
  }
10604
10627
  return {
@@ -10641,7 +10664,7 @@ async function generateImagesWithOpenAiImageApi(request) {
10641
10664
  model: request.model
10642
10665
  });
10643
10666
  const startedAtMs = Date.now();
10644
- const params = resolveOpenAiImageRequestParams(request);
10667
+ const params = resolveGptImage2RequestParams(request);
10645
10668
  const styleImages = await createOpenAiStyleImageFiles(request.styleImages);
10646
10669
  const hasStyleImages = Boolean(styleImages && styleImages.length > 0);
10647
10670
  const outputMimeType = resolveOpenAiImageMimeType(params.outputFormat);
@@ -10769,7 +10792,8 @@ async function generateImagesWithChatGptImageTool(request) {
10769
10792
  model: request.model
10770
10793
  });
10771
10794
  const startedAtMs = Date.now();
10772
- const numImagesPerPrompt = request.numImages ?? 1;
10795
+ const params = resolveGptImage2RequestParams(request);
10796
+ const outputMimeType = resolveOpenAiImageMimeType(params.outputFormat);
10773
10797
  let totalUsage;
10774
10798
  let costUsd = 0;
10775
10799
  let outputImages = 0;
@@ -10777,7 +10801,7 @@ async function generateImagesWithChatGptImageTool(request) {
10777
10801
  type: "llm.call.started",
10778
10802
  imagePromptCount: promptEntries.length,
10779
10803
  styleImageCount: request.styleImages?.length ?? 0,
10780
- numImagesPerPrompt
10804
+ numImagesPerPrompt: params.n
10781
10805
  });
10782
10806
  try {
10783
10807
  const images = [];
@@ -10787,7 +10811,7 @@ async function generateImagesWithChatGptImageTool(request) {
10787
10811
  imagePrompt,
10788
10812
  hasStyleImages: Boolean(request.styleImages && request.styleImages.length > 0)
10789
10813
  });
10790
- for (let imageIndex = 0; imageIndex < numImagesPerPrompt; imageIndex += 1) {
10814
+ for (let imageIndex = 0; imageIndex < params.n; imageIndex += 1) {
10791
10815
  const chatGptInput = toChatGptInput(
10792
10816
  buildChatGptImageInputContent({
10793
10817
  prompt,
@@ -10804,11 +10828,22 @@ async function generateImagesWithChatGptImageTool(request) {
10804
10828
  model: providerInfo.model,
10805
10829
  store: false,
10806
10830
  stream: true,
10807
- instructions: chatGptInput.instructions ?? "Use the image_generation tool to generate exactly one PNG image. Do not return prose instead of the image.",
10831
+ instructions: chatGptInput.instructions ?? "Use the image_generation tool to generate exactly one image. Do not return prose instead of the image.",
10808
10832
  input: preparedInput,
10809
10833
  tool_choice: "required",
10810
10834
  parallel_tool_calls: false,
10811
- tools: [{ type: "image_generation", output_format: "png" }]
10835
+ tools: [
10836
+ {
10837
+ type: "image_generation",
10838
+ size: params.size,
10839
+ quality: params.quality,
10840
+ output_format: params.outputFormat ?? "png",
10841
+ ...request.outputCompression !== void 0 ? { output_compression: request.outputCompression } : {},
10842
+ ...params.background ? { background: params.background } : {},
10843
+ ...params.moderation ? { moderation: params.moderation } : {},
10844
+ ...request.action ? { action: request.action } : {}
10845
+ }
10846
+ ]
10812
10847
  },
10813
10848
  signal: request.signal
10814
10849
  });
@@ -10820,7 +10855,7 @@ async function generateImagesWithChatGptImageTool(request) {
10820
10855
  }
10821
10856
  for (const call of result.imageGenerationCalls) {
10822
10857
  images.push({
10823
- mimeType: "image/png",
10858
+ mimeType: outputMimeType,
10824
10859
  data: import_node_buffer4.Buffer.from(call.result, "base64")
10825
10860
  });
10826
10861
  }
@@ -10831,8 +10866,8 @@ async function generateImagesWithChatGptImageTool(request) {
10831
10866
  modelId: request.model,
10832
10867
  tokens: usage,
10833
10868
  responseImages: result.imageGenerationCalls.length,
10834
- imageSize: "1024x1024",
10835
- imageQuality: "medium"
10869
+ imageSize: params.size,
10870
+ imageQuality: params.quality
10836
10871
  });
10837
10872
  }
10838
10873
  }
@@ -10844,7 +10879,7 @@ async function generateImagesWithChatGptImageTool(request) {
10844
10879
  usage: totalUsage,
10845
10880
  costUsd,
10846
10881
  imageCount: images.length,
10847
- attempts: promptEntries.length * numImagesPerPrompt
10882
+ attempts: promptEntries.length * params.n
10848
10883
  });
10849
10884
  return images;
10850
10885
  } catch (error) {