@ljoukov/llm 7.0.12 → 7.0.13

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
@@ -182,6 +182,42 @@ console.log(result.text);
182
182
  console.log(result.usage, result.costUsd);
183
183
  ```
184
184
 
185
+ ### Image Generation
186
+
187
+ ```ts
188
+ import {
189
+ generateImages,
190
+ type LlmOpenAiImageResolution,
191
+ OPENAI_GPT_IMAGE_2_QUALITY_LEVELS,
192
+ OPENAI_GPT_IMAGE_2_RESOLUTIONS,
193
+ OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS,
194
+ } from "@ljoukov/llm";
195
+
196
+ const customResolution = "1440x960" satisfies LlmOpenAiImageResolution;
197
+
198
+ const images = await generateImages({
199
+ model: "gpt-image-2",
200
+ stylePrompt: "Warm amber desk light, deep blue night, cinematic laboratory mood.",
201
+ imagePrompts: ["A compact lab bench still life with glassware and an open notebook"],
202
+ imageResolution: customResolution,
203
+ imageQuality: "low",
204
+ numImages: 1,
205
+ });
206
+
207
+ console.log(OPENAI_GPT_IMAGE_2_RESOLUTIONS, OPENAI_GPT_IMAGE_2_QUALITY_LEVELS);
208
+ console.log(OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS);
209
+ console.log(images[0]?.mimeType, images[0]?.data.byteLength);
210
+ ```
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
215
+ `"auto"`; custom literal `WIDTHxHEIGHT` resolutions are also accepted when they satisfy
216
+ `OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS`: each edge must be at most 3840px, each edge must be a
217
+ multiple of 16px, the long edge must be at most 3:1 relative to the short edge, and total pixels
218
+ must be between 655,360 and 8,294,400. Resolutions above 3,686,400 pixels are documented as
219
+ experimental by OpenAI.
220
+
185
221
  ### Streaming (response + thoughts + usage)
186
222
 
187
223
  ```ts
package/dist/index.cjs CHANGED
@@ -50,6 +50,17 @@ __export(index_exports, {
50
50
  LLM_MODEL_IDS: () => LLM_MODEL_IDS,
51
51
  LLM_TEXT_MODEL_IDS: () => LLM_TEXT_MODEL_IDS,
52
52
  LlmJsonCallError: () => LlmJsonCallError,
53
+ OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION: () => OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION,
54
+ OPENAI_GPT_IMAGE_2_BACKGROUNDS: () => OPENAI_GPT_IMAGE_2_BACKGROUNDS,
55
+ OPENAI_GPT_IMAGE_2_MODERATION_LEVELS: () => OPENAI_GPT_IMAGE_2_MODERATION_LEVELS,
56
+ OPENAI_GPT_IMAGE_2_NUM_IMAGES: () => OPENAI_GPT_IMAGE_2_NUM_IMAGES,
57
+ OPENAI_GPT_IMAGE_2_OUTPUT_FORMATS: () => OPENAI_GPT_IMAGE_2_OUTPUT_FORMATS,
58
+ OPENAI_GPT_IMAGE_2_PARTIAL_IMAGE_COUNTS: () => OPENAI_GPT_IMAGE_2_PARTIAL_IMAGE_COUNTS,
59
+ OPENAI_GPT_IMAGE_2_POPULAR_RESOLUTIONS: () => OPENAI_GPT_IMAGE_2_POPULAR_RESOLUTIONS,
60
+ OPENAI_GPT_IMAGE_2_QUALITY_LEVELS: () => OPENAI_GPT_IMAGE_2_QUALITY_LEVELS,
61
+ OPENAI_GPT_IMAGE_2_RESOLUTIONS: () => OPENAI_GPT_IMAGE_2_RESOLUTIONS,
62
+ OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS: () => OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS,
63
+ OPENAI_IMAGE_MODEL_IDS: () => OPENAI_IMAGE_MODEL_IDS,
53
64
  OPENAI_MODEL_IDS: () => OPENAI_MODEL_IDS,
54
65
  appendMarkdownSourcesSection: () => appendMarkdownSourcesSection,
55
66
  applyPatch: () => applyPatch,
@@ -99,6 +110,7 @@ __export(index_exports, {
99
110
  isLlmImageModelId: () => isLlmImageModelId,
100
111
  isLlmModelId: () => isLlmModelId,
101
112
  isLlmTextModelId: () => isLlmTextModelId,
113
+ isOpenAiImageModelId: () => isOpenAiImageModelId,
102
114
  isOpenAiModelId: () => isOpenAiModelId,
103
115
  loadEnvFromFile: () => loadEnvFromFile,
104
116
  loadLocalEnv: () => loadLocalEnv,
@@ -118,7 +130,8 @@ __export(index_exports, {
118
130
  streamToolLoop: () => streamToolLoop,
119
131
  stripCodexCitationMarkers: () => stripCodexCitationMarkers,
120
132
  toGeminiJsonSchema: () => toGeminiJsonSchema,
121
- tool: () => tool
133
+ tool: () => tool,
134
+ validateOpenAiGptImage2Resolution: () => validateOpenAiGptImage2Resolution
122
135
  });
123
136
  module.exports = __toCommonJS(index_exports);
124
137
 
@@ -130,6 +143,7 @@ var import_node_path5 = __toESM(require("path"), 1);
130
143
  var import_genai2 = require("@google/genai");
131
144
  var import_zod_to_json_schema = require("@alcyone-labs/zod-to-json-schema");
132
145
  var import_zod3 = require("zod");
146
+ var import_openai3 = require("openai");
133
147
 
134
148
  // src/utils/asyncQueue.ts
135
149
  function createAsyncQueue() {
@@ -331,6 +345,81 @@ var OPENAI_MODEL_IDS = [
331
345
  function isOpenAiModelId(value) {
332
346
  return OPENAI_MODEL_IDS.includes(value);
333
347
  }
348
+ var OPENAI_IMAGE_MODEL_IDS = ["gpt-image-2"];
349
+ function isOpenAiImageModelId(value) {
350
+ return OPENAI_IMAGE_MODEL_IDS.includes(value);
351
+ }
352
+ var OPENAI_GPT_IMAGE_2_POPULAR_RESOLUTIONS = [
353
+ "1024x1024",
354
+ "1536x1024",
355
+ "1024x1536",
356
+ "2048x2048",
357
+ "2048x1152",
358
+ "3840x2160",
359
+ "2160x3840"
360
+ ];
361
+ var OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION = "auto";
362
+ var OPENAI_GPT_IMAGE_2_RESOLUTIONS = [
363
+ ...OPENAI_GPT_IMAGE_2_POPULAR_RESOLUTIONS,
364
+ OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION
365
+ ];
366
+ var OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS = {
367
+ maxEdgePixels: 3840,
368
+ edgeMultiplePixels: 16,
369
+ maxLongToShortEdgeRatio: 3,
370
+ minTotalPixels: 655360,
371
+ maxTotalPixels: 8294400,
372
+ experimentalTotalPixelsThreshold: 3686400
373
+ };
374
+ function validateOpenAiGptImage2Resolution(value) {
375
+ if (value === OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION) {
376
+ return { valid: true };
377
+ }
378
+ const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(value);
379
+ if (!match) {
380
+ return { valid: false, reason: 'Expected "auto" or a WIDTHxHEIGHT pixel string.' };
381
+ }
382
+ const width = Number(match[1]);
383
+ const height = Number(match[2]);
384
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height)) {
385
+ return { valid: false, reason: "Width and height must be safe integer pixel counts." };
386
+ }
387
+ const constraints = OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS;
388
+ if (width > constraints.maxEdgePixels || height > constraints.maxEdgePixels) {
389
+ return {
390
+ valid: false,
391
+ reason: `Width and height must each be at most ${constraints.maxEdgePixels}px.`
392
+ };
393
+ }
394
+ if (width % constraints.edgeMultiplePixels !== 0 || height % constraints.edgeMultiplePixels !== 0) {
395
+ return {
396
+ valid: false,
397
+ reason: `Width and height must each be multiples of ${constraints.edgeMultiplePixels}px.`
398
+ };
399
+ }
400
+ const totalPixels = width * height;
401
+ if (totalPixels < constraints.minTotalPixels || totalPixels > constraints.maxTotalPixels) {
402
+ return {
403
+ valid: false,
404
+ reason: `Total pixels must be between ${constraints.minTotalPixels} and ${constraints.maxTotalPixels}.`
405
+ };
406
+ }
407
+ const longEdge = Math.max(width, height);
408
+ const shortEdge = Math.min(width, height);
409
+ if (longEdge / shortEdge > constraints.maxLongToShortEdgeRatio) {
410
+ return {
411
+ valid: false,
412
+ reason: `The long edge must be at most ${constraints.maxLongToShortEdgeRatio}:1 relative to the short edge.`
413
+ };
414
+ }
415
+ return { valid: true };
416
+ }
417
+ var OPENAI_GPT_IMAGE_2_QUALITY_LEVELS = ["low", "medium", "high", "auto"];
418
+ var OPENAI_GPT_IMAGE_2_OUTPUT_FORMATS = ["png", "jpeg", "webp"];
419
+ var OPENAI_GPT_IMAGE_2_BACKGROUNDS = ["opaque", "auto"];
420
+ var OPENAI_GPT_IMAGE_2_MODERATION_LEVELS = ["low", "auto"];
421
+ var OPENAI_GPT_IMAGE_2_PARTIAL_IMAGE_COUNTS = [0, 1, 2, 3];
422
+ var OPENAI_GPT_IMAGE_2_NUM_IMAGES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
334
423
  var CHATGPT_MODEL_IDS = [
335
424
  "chatgpt-gpt-5.5",
336
425
  "chatgpt-gpt-5.5-fast",
@@ -413,6 +502,27 @@ var OPENAI_GPT_54_NANO_PRICING = {
413
502
  cachedRate: 5e-3 / 1e6,
414
503
  outputRate: 0.4 / 1e6
415
504
  };
505
+ var OPENAI_GPT_IMAGE_2_PRICING = {
506
+ defaultQuality: "medium",
507
+ defaultResolution: "1024x1024",
508
+ imagePrices: {
509
+ low: {
510
+ "1024x1024": 6e-3,
511
+ "1024x1536": 5e-3,
512
+ "1536x1024": 5e-3
513
+ },
514
+ medium: {
515
+ "1024x1024": 0.053,
516
+ "1024x1536": 0.041,
517
+ "1536x1024": 0.041
518
+ },
519
+ high: {
520
+ "1024x1024": 0.211,
521
+ "1024x1536": 0.165,
522
+ "1536x1024": 0.165
523
+ }
524
+ }
525
+ };
416
526
  function getOpenAiPricing(modelId) {
417
527
  if (isExperimentalChatGptModelId(modelId)) {
418
528
  return OPENAI_GPT_54_PRICING;
@@ -440,6 +550,9 @@ function getOpenAiPricing(modelId) {
440
550
  }
441
551
  return void 0;
442
552
  }
553
+ function getOpenAiImagePricing(modelId) {
554
+ return isOpenAiImageModelId(modelId) ? OPENAI_GPT_IMAGE_2_PRICING : void 0;
555
+ }
443
556
 
444
557
  // src/utils/cost.ts
445
558
  function resolveUsageNumber(value) {
@@ -452,8 +565,18 @@ function estimateCallCostUsd({
452
565
  modelId,
453
566
  tokens,
454
567
  responseImages,
455
- imageSize
568
+ imageSize,
569
+ imageQuality
456
570
  }) {
571
+ const openAiImagePricing = getOpenAiImagePricing(modelId);
572
+ if (openAiImagePricing) {
573
+ return estimateOpenAiImageCostUsd({
574
+ pricing: openAiImagePricing,
575
+ responseImages,
576
+ imageSize,
577
+ imageQuality
578
+ });
579
+ }
457
580
  if (!tokens) {
458
581
  return 0;
459
582
  }
@@ -515,6 +638,40 @@ function estimateCallCostUsd({
515
638
  }
516
639
  return 0;
517
640
  }
641
+ function estimateOpenAiImageCostUsd({
642
+ pricing,
643
+ responseImages,
644
+ imageSize,
645
+ imageQuality
646
+ }) {
647
+ if (responseImages <= 0) {
648
+ return 0;
649
+ }
650
+ const quality = imageQuality === "low" || imageQuality === "medium" || imageQuality === "high" ? imageQuality : pricing.defaultQuality;
651
+ const resolution = resolveOpenAiImagePriceResolution(imageSize) ?? pricing.defaultResolution;
652
+ return responseImages * pricing.imagePrices[quality][resolution];
653
+ }
654
+ function resolveOpenAiImagePriceResolution(imageSize) {
655
+ if (imageSize === "1024x1024" || imageSize === "1024x1536" || imageSize === "1536x1024") {
656
+ return imageSize;
657
+ }
658
+ if (!imageSize || imageSize === "auto") {
659
+ return void 0;
660
+ }
661
+ const match = /^(\d+)x(\d+)$/.exec(imageSize);
662
+ if (!match) {
663
+ return void 0;
664
+ }
665
+ const width = Number(match[1]);
666
+ const height = Number(match[2]);
667
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
668
+ return void 0;
669
+ }
670
+ if (width === height) {
671
+ return "1024x1024";
672
+ }
673
+ return width > height ? "1536x1024" : "1024x1536";
674
+ }
518
675
 
519
676
  // src/openai/chatgpt-codex.ts
520
677
  var import_node_os2 = __toESM(require("os"), 1);
@@ -4495,13 +4652,13 @@ var LLM_TEXT_MODEL_IDS = [
4495
4652
  ...FIREWORKS_MODEL_IDS,
4496
4653
  ...GEMINI_TEXT_MODEL_IDS
4497
4654
  ];
4498
- var LLM_IMAGE_MODEL_IDS = [...GEMINI_IMAGE_MODEL_IDS];
4655
+ var LLM_IMAGE_MODEL_IDS = [...OPENAI_IMAGE_MODEL_IDS, ...GEMINI_IMAGE_MODEL_IDS];
4499
4656
  var LLM_MODEL_IDS = [...LLM_TEXT_MODEL_IDS, ...LLM_IMAGE_MODEL_IDS];
4500
4657
  function isLlmTextModelId(value) {
4501
4658
  return isOpenAiModelId(value) || isChatGptModelId(value) || isFireworksModelId(value) || isGeminiTextModelId(value);
4502
4659
  }
4503
4660
  function isLlmImageModelId(value) {
4504
- return isGeminiImageModelId(value);
4661
+ return isOpenAiImageModelId(value) || isGeminiImageModelId(value);
4505
4662
  }
4506
4663
  function isLlmModelId(value) {
4507
4664
  return isLlmTextModelId(value) || isLlmImageModelId(value);
@@ -4513,6 +4670,9 @@ var LlmJsonCallError = class extends Error {
4513
4670
  this.name = "LlmJsonCallError";
4514
4671
  }
4515
4672
  };
4673
+ function isOpenAiGenerateImagesRequest(request) {
4674
+ return isOpenAiImageModelId(request.model);
4675
+ }
4516
4676
  function tool(options) {
4517
4677
  return {
4518
4678
  type: "function",
@@ -5103,6 +5263,9 @@ function resolveProvider(model) {
5103
5263
  return { provider: "fireworks", model: fireworksModel };
5104
5264
  }
5105
5265
  }
5266
+ if (isOpenAiImageModelId(model)) {
5267
+ return { provider: "openai", model };
5268
+ }
5106
5269
  if (isOpenAiModelId(model)) {
5107
5270
  return {
5108
5271
  provider: "openai",
@@ -5110,7 +5273,7 @@ function resolveProvider(model) {
5110
5273
  serviceTier: resolveOpenAiServiceTier(model)
5111
5274
  };
5112
5275
  }
5113
- throw new Error(`Unsupported text model: ${model}`);
5276
+ throw new Error(`Unsupported model: ${model}`);
5114
5277
  }
5115
5278
  function isOpenAiCodexModel(modelId) {
5116
5279
  return modelId.includes("codex");
@@ -6266,8 +6429,11 @@ function mergeTokenUpdates(current, next) {
6266
6429
  }
6267
6430
  return {
6268
6431
  promptTokens: next.promptTokens ?? current.promptTokens,
6432
+ promptTextTokens: next.promptTextTokens ?? current.promptTextTokens,
6433
+ promptImageTokens: next.promptImageTokens ?? current.promptImageTokens,
6269
6434
  cachedTokens: next.cachedTokens ?? current.cachedTokens,
6270
6435
  responseTokens: next.responseTokens ?? current.responseTokens,
6436
+ responseTextTokens: next.responseTextTokens ?? current.responseTextTokens,
6271
6437
  responseImageTokens: next.responseImageTokens ?? current.responseImageTokens,
6272
6438
  thinkingTokens: next.thinkingTokens ?? current.thinkingTokens,
6273
6439
  totalTokens: next.totalTokens ?? current.totalTokens,
@@ -6290,8 +6456,11 @@ function sumUsageTokens(current, next) {
6290
6456
  }
6291
6457
  return {
6292
6458
  promptTokens: sumUsageValue(current?.promptTokens, next.promptTokens),
6459
+ promptTextTokens: sumUsageValue(current?.promptTextTokens, next.promptTextTokens),
6460
+ promptImageTokens: sumUsageValue(current?.promptImageTokens, next.promptImageTokens),
6293
6461
  cachedTokens: sumUsageValue(current?.cachedTokens, next.cachedTokens),
6294
6462
  responseTokens: sumUsageValue(current?.responseTokens, next.responseTokens),
6463
+ responseTextTokens: sumUsageValue(current?.responseTextTokens, next.responseTextTokens),
6295
6464
  responseImageTokens: sumUsageValue(current?.responseImageTokens, next.responseImageTokens),
6296
6465
  thinkingTokens: sumUsageValue(current?.thinkingTokens, next.thinkingTokens),
6297
6466
  totalTokens: sumUsageValue(current?.totalTokens, next.totalTokens),
@@ -6406,10 +6575,22 @@ function extractOpenAiUsageTokens(usage) {
6406
6575
  const cachedTokens = toMaybeNumber(
6407
6576
  usage.input_tokens_details?.cached_tokens
6408
6577
  );
6578
+ const promptTextTokens = toMaybeNumber(
6579
+ usage.input_tokens_details?.text_tokens
6580
+ );
6581
+ const promptImageTokens = toMaybeNumber(
6582
+ usage.input_tokens_details?.image_tokens
6583
+ );
6409
6584
  const outputTokensRaw = toMaybeNumber(usage.output_tokens);
6410
6585
  const reasoningTokens = toMaybeNumber(
6411
6586
  usage.output_tokens_details?.reasoning_tokens
6412
6587
  );
6588
+ const responseTextTokens = toMaybeNumber(
6589
+ usage.output_tokens_details?.text_tokens
6590
+ );
6591
+ const responseImageTokens = toMaybeNumber(
6592
+ usage.output_tokens_details?.image_tokens
6593
+ );
6413
6594
  const totalTokens = toMaybeNumber(usage.total_tokens);
6414
6595
  let responseTokens;
6415
6596
  if (outputTokensRaw !== void 0) {
@@ -6421,8 +6602,12 @@ function extractOpenAiUsageTokens(usage) {
6421
6602
  }
6422
6603
  return {
6423
6604
  promptTokens,
6605
+ promptTextTokens,
6606
+ promptImageTokens,
6424
6607
  cachedTokens,
6425
6608
  responseTokens,
6609
+ responseTextTokens,
6610
+ responseImageTokens,
6426
6611
  thinkingTokens: reasoningTokens,
6427
6612
  totalTokens
6428
6613
  };
@@ -7898,6 +8083,9 @@ async function runTextCall(params) {
7898
8083
  const { result } = await collectFileUploadMetrics(async () => {
7899
8084
  try {
7900
8085
  if (provider === "openai") {
8086
+ if (isOpenAiImageModelId(request.model)) {
8087
+ throw new Error("gpt-image-2 is an image generation model; use generateImages().");
8088
+ }
7901
8089
  const openAiInput = await maybePrepareOpenAiPromptInput(
7902
8090
  toOpenAiInput(contents, {
7903
8091
  defaultMediaResolution: request.mediaResolution,
@@ -10263,7 +10451,184 @@ async function gradeGeneratedImage(params) {
10263
10451
  });
10264
10452
  return { grade: value.grade, result };
10265
10453
  }
10454
+ function resolveOpenAiImageMimeType(outputFormat) {
10455
+ switch (outputFormat) {
10456
+ case "jpeg":
10457
+ return "image/jpeg";
10458
+ case "webp":
10459
+ return "image/webp";
10460
+ case "png":
10461
+ case void 0:
10462
+ return "image/png";
10463
+ }
10464
+ }
10465
+ function buildOpenAiImagePrompt(params) {
10466
+ return [
10467
+ "Follow the requested visual style.",
10468
+ "",
10469
+ "Style:",
10470
+ params.stylePrompt.trim(),
10471
+ ...params.hasStyleImages ? [
10472
+ "",
10473
+ "Use the attached reference image or images for palette, lighting, mood, composition, and material feel."
10474
+ ] : [],
10475
+ "",
10476
+ "Image:",
10477
+ params.imagePrompt.trim()
10478
+ ].filter((line) => line.length > 0).join("\n");
10479
+ }
10480
+ function resolveOpenAiImageRequestParams(request) {
10481
+ if (request.partialImages !== void 0) {
10482
+ throw new Error("partialImages is only supported for streaming image generation.");
10483
+ }
10484
+ if (request.outputCompression !== void 0 && (!Number.isInteger(request.outputCompression) || request.outputCompression < 0 || request.outputCompression > 100)) {
10485
+ throw new Error("outputCompression must be an integer from 0 to 100.");
10486
+ }
10487
+ if (request.outputCompression !== void 0 && request.outputFormat !== "jpeg" && request.outputFormat !== "webp") {
10488
+ throw new Error("outputCompression requires outputFormat to be jpeg or webp.");
10489
+ }
10490
+ const size = request.imageResolution ?? "auto";
10491
+ const sizeValidation = validateOpenAiGptImage2Resolution(size);
10492
+ if (!sizeValidation.valid) {
10493
+ throw new Error(
10494
+ `imageResolution ${JSON.stringify(size)} is not supported by gpt-image-2: ${sizeValidation.reason}`
10495
+ );
10496
+ }
10497
+ return {
10498
+ size,
10499
+ quality: request.imageQuality ?? "auto",
10500
+ outputFormat: request.outputFormat,
10501
+ n: request.numImages ?? 1,
10502
+ background: request.background,
10503
+ moderation: request.moderation
10504
+ };
10505
+ }
10506
+ async function createOpenAiStyleImageFiles(styleImages) {
10507
+ if (!styleImages || styleImages.length === 0) {
10508
+ return void 0;
10509
+ }
10510
+ return await Promise.all(
10511
+ styleImages.map(async (image, index) => {
10512
+ const mimeType = image.mimeType ?? "image/png";
10513
+ const extension = resolveAttachmentExtension(mimeType);
10514
+ return await (0, import_openai3.toFile)(image.data, `style-${index + 1}.${extension}`, { type: mimeType });
10515
+ })
10516
+ );
10517
+ }
10518
+ async function generateImagesWithOpenAiImageApi(request) {
10519
+ const promptEntries = Array.from(request.imagePrompts, (rawPrompt, index) => {
10520
+ const prompt = rawPrompt.trim();
10521
+ if (!prompt) {
10522
+ throw new Error(`imagePrompts[${index}] must be a non-empty string`);
10523
+ }
10524
+ return prompt;
10525
+ });
10526
+ if (promptEntries.length === 0) {
10527
+ return [];
10528
+ }
10529
+ const provider = resolveProvider(request.model).provider;
10530
+ const telemetry = createLlmTelemetryEmitter({
10531
+ telemetry: request.telemetry,
10532
+ operation: "generateImages",
10533
+ provider,
10534
+ model: request.model
10535
+ });
10536
+ const startedAtMs = Date.now();
10537
+ const params = resolveOpenAiImageRequestParams(request);
10538
+ const styleImages = await createOpenAiStyleImageFiles(request.styleImages);
10539
+ const hasStyleImages = Boolean(styleImages && styleImages.length > 0);
10540
+ const outputMimeType = resolveOpenAiImageMimeType(params.outputFormat);
10541
+ let totalUsage;
10542
+ let costUsd = 0;
10543
+ let outputImages = 0;
10544
+ telemetry.emit({
10545
+ type: "llm.call.started",
10546
+ imagePromptCount: promptEntries.length,
10547
+ styleImageCount: request.styleImages?.length ?? 0,
10548
+ numImagesPerPrompt: params.n
10549
+ });
10550
+ try {
10551
+ const images = [];
10552
+ for (const imagePrompt of promptEntries) {
10553
+ const prompt = buildOpenAiImagePrompt({
10554
+ stylePrompt: request.stylePrompt,
10555
+ imagePrompt,
10556
+ hasStyleImages
10557
+ });
10558
+ const response = await runOpenAiCall(async (client) => {
10559
+ const payload = {
10560
+ model: request.model,
10561
+ prompt,
10562
+ n: params.n,
10563
+ size: params.size,
10564
+ quality: params.quality,
10565
+ ...params.outputFormat ? { output_format: params.outputFormat } : {},
10566
+ ...request.outputCompression !== void 0 ? { output_compression: request.outputCompression } : {},
10567
+ ...params.background ? { background: params.background } : {},
10568
+ ...params.moderation ? { moderation: params.moderation } : {}
10569
+ };
10570
+ if (styleImages && styleImages.length > 0) {
10571
+ return await client.images.edit(
10572
+ {
10573
+ ...payload,
10574
+ image: styleImages
10575
+ },
10576
+ { signal: request.signal }
10577
+ );
10578
+ }
10579
+ return await client.images.generate(payload, { signal: request.signal });
10580
+ }, request.model);
10581
+ const data = Array.isArray(response.data) ? response.data ?? [] : [];
10582
+ for (const item of data) {
10583
+ if (typeof item.b64_json !== "string" || item.b64_json.length === 0) {
10584
+ continue;
10585
+ }
10586
+ images.push({
10587
+ mimeType: outputMimeType,
10588
+ data: import_node_buffer4.Buffer.from(item.b64_json, "base64")
10589
+ });
10590
+ }
10591
+ outputImages = images.length;
10592
+ const usage = extractOpenAiUsageTokens(response.usage);
10593
+ totalUsage = sumUsageTokens(totalUsage, usage);
10594
+ costUsd += estimateCallCostUsd({
10595
+ modelId: request.model,
10596
+ tokens: usage,
10597
+ responseImages: data.length,
10598
+ imageSize: params.size,
10599
+ imageQuality: params.quality
10600
+ });
10601
+ }
10602
+ telemetry.emit({
10603
+ type: "llm.call.completed",
10604
+ success: true,
10605
+ durationMs: Math.max(0, Date.now() - startedAtMs),
10606
+ usage: totalUsage,
10607
+ costUsd,
10608
+ imageCount: images.length,
10609
+ attempts: promptEntries.length
10610
+ });
10611
+ return images;
10612
+ } catch (error) {
10613
+ const err = error instanceof Error ? error : new Error(String(error));
10614
+ telemetry.emit({
10615
+ type: "llm.call.completed",
10616
+ success: false,
10617
+ durationMs: Math.max(0, Date.now() - startedAtMs),
10618
+ usage: totalUsage,
10619
+ costUsd,
10620
+ imageCount: outputImages,
10621
+ error: err.message
10622
+ });
10623
+ throw err;
10624
+ } finally {
10625
+ await telemetry.flush();
10626
+ }
10627
+ }
10266
10628
  async function generateImages(request) {
10629
+ if (isOpenAiGenerateImagesRequest(request)) {
10630
+ return await generateImagesWithOpenAiImageApi(request);
10631
+ }
10267
10632
  const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 4));
10268
10633
  const promptList = Array.from(request.imagePrompts);
10269
10634
  if (promptList.length === 0) {
@@ -10277,7 +10642,7 @@ async function generateImages(request) {
10277
10642
  }
10278
10643
  return { index: arrayIndex + 1, prompt: trimmedPrompt };
10279
10644
  });
10280
- const gradingPrompt = request.imageGradingPrompt.trim();
10645
+ const gradingPrompt = request.imageGradingPrompt?.trim() ?? "";
10281
10646
  if (!gradingPrompt) {
10282
10647
  throw new Error("imageGradingPrompt must be a non-empty string");
10283
10648
  }
@@ -13575,8 +13940,11 @@ function summarizeResultUsage(result) {
13575
13940
  }
13576
13941
  summary = {
13577
13942
  promptTokens: sumUsageValue2(summary?.promptTokens, usage.promptTokens),
13943
+ promptTextTokens: sumUsageValue2(summary?.promptTextTokens, usage.promptTextTokens),
13944
+ promptImageTokens: sumUsageValue2(summary?.promptImageTokens, usage.promptImageTokens),
13578
13945
  cachedTokens: sumUsageValue2(summary?.cachedTokens, usage.cachedTokens),
13579
13946
  responseTokens: sumUsageValue2(summary?.responseTokens, usage.responseTokens),
13947
+ responseTextTokens: sumUsageValue2(summary?.responseTextTokens, usage.responseTextTokens),
13580
13948
  responseImageTokens: sumUsageValue2(summary?.responseImageTokens, usage.responseImageTokens),
13581
13949
  thinkingTokens: sumUsageValue2(summary?.thinkingTokens, usage.thinkingTokens),
13582
13950
  totalTokens: sumUsageValue2(summary?.totalTokens, usage.totalTokens),
@@ -14308,6 +14676,17 @@ async function runCandidateEvolution(options) {
14308
14676
  LLM_MODEL_IDS,
14309
14677
  LLM_TEXT_MODEL_IDS,
14310
14678
  LlmJsonCallError,
14679
+ OPENAI_GPT_IMAGE_2_AUTO_RESOLUTION,
14680
+ OPENAI_GPT_IMAGE_2_BACKGROUNDS,
14681
+ OPENAI_GPT_IMAGE_2_MODERATION_LEVELS,
14682
+ OPENAI_GPT_IMAGE_2_NUM_IMAGES,
14683
+ OPENAI_GPT_IMAGE_2_OUTPUT_FORMATS,
14684
+ OPENAI_GPT_IMAGE_2_PARTIAL_IMAGE_COUNTS,
14685
+ OPENAI_GPT_IMAGE_2_POPULAR_RESOLUTIONS,
14686
+ OPENAI_GPT_IMAGE_2_QUALITY_LEVELS,
14687
+ OPENAI_GPT_IMAGE_2_RESOLUTIONS,
14688
+ OPENAI_GPT_IMAGE_2_SIZE_CONSTRAINTS,
14689
+ OPENAI_IMAGE_MODEL_IDS,
14311
14690
  OPENAI_MODEL_IDS,
14312
14691
  appendMarkdownSourcesSection,
14313
14692
  applyPatch,
@@ -14357,6 +14736,7 @@ async function runCandidateEvolution(options) {
14357
14736
  isLlmImageModelId,
14358
14737
  isLlmModelId,
14359
14738
  isLlmTextModelId,
14739
+ isOpenAiImageModelId,
14360
14740
  isOpenAiModelId,
14361
14741
  loadEnvFromFile,
14362
14742
  loadLocalEnv,
@@ -14376,6 +14756,7 @@ async function runCandidateEvolution(options) {
14376
14756
  streamToolLoop,
14377
14757
  stripCodexCitationMarkers,
14378
14758
  toGeminiJsonSchema,
14379
- tool
14759
+ tool,
14760
+ validateOpenAiGptImage2Resolution
14380
14761
  });
14381
14762
  //# sourceMappingURL=index.cjs.map