@ljoukov/llm 5.0.3 → 6.0.0

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.js CHANGED
@@ -6,6 +6,8 @@ import path5 from "path";
6
6
  import {
7
7
  FinishReason,
8
8
  FunctionCallingConfigMode,
9
+ MediaResolution,
10
+ PartMediaResolutionLevel,
9
11
  ThinkingLevel,
10
12
  createPartFromBase64,
11
13
  createPartFromFunctionResponse,
@@ -204,11 +206,6 @@ function getGeminiImagePricing(modelId) {
204
206
  }
205
207
 
206
208
  // src/openai/pricing.ts
207
- var OPENAI_GPT_52_PRICING = {
208
- inputRate: 1.75 / 1e6,
209
- cachedRate: 0.175 / 1e6,
210
- outputRate: 14 / 1e6
211
- };
212
209
  var OPENAI_GPT_54_PRICING = {
213
210
  inputRate: 2.5 / 1e6,
214
211
  cachedRate: 0.25 / 1e6,
@@ -219,37 +216,31 @@ var OPENAI_GPT_54_PRIORITY_PRICING = {
219
216
  cachedRate: 0.5 / 1e6,
220
217
  outputRate: 30 / 1e6
221
218
  };
222
- var OPENAI_GPT_53_CODEX_PRICING = {
223
- inputRate: 1.25 / 1e6,
224
- cachedRate: 0.125 / 1e6,
225
- outputRate: 10 / 1e6
226
- };
227
- var OPENAI_GPT_5_MINI_PRICING = {
219
+ var OPENAI_GPT_54_MINI_PRICING = {
228
220
  inputRate: 0.25 / 1e6,
229
221
  cachedRate: 0.025 / 1e6,
230
222
  outputRate: 2 / 1e6
231
223
  };
224
+ var OPENAI_GPT_54_NANO_PRICING = {
225
+ inputRate: 0.05 / 1e6,
226
+ cachedRate: 5e-3 / 1e6,
227
+ outputRate: 0.4 / 1e6
228
+ };
232
229
  function getOpenAiPricing(modelId) {
233
230
  if (modelId.includes("gpt-5.4-fast")) {
234
231
  return OPENAI_GPT_54_PRIORITY_PRICING;
235
232
  }
236
- if (modelId.includes("gpt-5.4")) {
237
- return OPENAI_GPT_54_PRICING;
238
- }
239
- if (modelId.includes("gpt-5.3-codex-spark")) {
240
- return OPENAI_GPT_5_MINI_PRICING;
241
- }
242
- if (modelId.includes("gpt-5.3-codex")) {
243
- return OPENAI_GPT_53_CODEX_PRICING;
233
+ if (modelId.includes("gpt-5.4-mini")) {
234
+ return OPENAI_GPT_54_MINI_PRICING;
244
235
  }
245
- if (modelId.includes("gpt-5.2")) {
246
- return OPENAI_GPT_52_PRICING;
236
+ if (modelId.includes("gpt-5.4-nano")) {
237
+ return OPENAI_GPT_54_NANO_PRICING;
247
238
  }
248
- if (modelId.includes("gpt-5-mini")) {
249
- return OPENAI_GPT_5_MINI_PRICING;
239
+ if (modelId.includes("gpt-5.3-codex-spark")) {
240
+ return OPENAI_GPT_54_MINI_PRICING;
250
241
  }
251
- if (modelId.includes("gpt-5.1-codex-mini")) {
252
- return OPENAI_GPT_5_MINI_PRICING;
242
+ if (modelId.includes("gpt-5.4")) {
243
+ return OPENAI_GPT_54_PRICING;
253
244
  }
254
245
  return void 0;
255
246
  }
@@ -2718,22 +2709,15 @@ async function runOpenAiCall(fn, modelId, runOptions) {
2718
2709
  }
2719
2710
 
2720
2711
  // src/openai/models.ts
2721
- var OPENAI_MODEL_IDS = [
2722
- "gpt-5.4",
2723
- "gpt-5.3-codex",
2724
- "gpt-5.2",
2725
- "gpt-5.1-codex-mini"
2726
- ];
2712
+ var OPENAI_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"];
2727
2713
  function isOpenAiModelId(value) {
2728
2714
  return OPENAI_MODEL_IDS.includes(value);
2729
2715
  }
2730
2716
  var CHATGPT_MODEL_IDS = [
2731
2717
  "chatgpt-gpt-5.4",
2732
2718
  "chatgpt-gpt-5.4-fast",
2733
- "chatgpt-gpt-5.3-codex",
2734
- "chatgpt-gpt-5.3-codex-spark",
2735
- "chatgpt-gpt-5.2",
2736
- "chatgpt-gpt-5.1-codex-mini"
2719
+ "chatgpt-gpt-5.4-mini",
2720
+ "chatgpt-gpt-5.3-codex-spark"
2737
2721
  ];
2738
2722
  function isChatGptModelId(value) {
2739
2723
  return CHATGPT_MODEL_IDS.includes(value);
@@ -4417,6 +4401,72 @@ function parseCanonicalGeminiFileId(fileUri) {
4417
4401
  const fileId = fileUri.slice(CANONICAL_GEMINI_FILE_URI_PREFIX.length).trim();
4418
4402
  return fileId.length > 0 ? fileId : void 0;
4419
4403
  }
4404
+ function isLlmMediaResolution(value) {
4405
+ return value === "auto" || value === "low" || value === "medium" || value === "high" || value === "original";
4406
+ }
4407
+ function resolveEffectiveMediaResolution(detail, fallback) {
4408
+ return detail ?? fallback;
4409
+ }
4410
+ function supportsOpenAiOriginalImageDetail(model) {
4411
+ if (!model) {
4412
+ return false;
4413
+ }
4414
+ const providerModel = isChatGptModelId(model) ? resolveChatGptProviderModel(model) : model;
4415
+ const match = /^gpt-(\d+)(?:\.(\d+))?/u.exec(providerModel);
4416
+ if (!match) {
4417
+ return false;
4418
+ }
4419
+ const major = Number(match[1]);
4420
+ const minor = Number(match[2] ?? "0");
4421
+ if (!Number.isFinite(major) || !Number.isFinite(minor)) {
4422
+ return false;
4423
+ }
4424
+ return major > 5 || major === 5 && minor >= 4;
4425
+ }
4426
+ function toOpenAiImageDetail(mediaResolution, model) {
4427
+ switch (mediaResolution) {
4428
+ case "low":
4429
+ return "low";
4430
+ case "medium":
4431
+ return "high";
4432
+ case "high":
4433
+ return "high";
4434
+ case "original":
4435
+ return supportsOpenAiOriginalImageDetail(model) ? "original" : "high";
4436
+ case "auto":
4437
+ default:
4438
+ return "auto";
4439
+ }
4440
+ }
4441
+ function toGeminiMediaResolution(mediaResolution) {
4442
+ switch (mediaResolution) {
4443
+ case "low":
4444
+ return MediaResolution.MEDIA_RESOLUTION_LOW;
4445
+ case "medium":
4446
+ return MediaResolution.MEDIA_RESOLUTION_MEDIUM;
4447
+ case "high":
4448
+ case "original":
4449
+ return MediaResolution.MEDIA_RESOLUTION_HIGH;
4450
+ case "auto":
4451
+ default:
4452
+ return void 0;
4453
+ }
4454
+ }
4455
+ function toGeminiPartMediaResolution(mediaResolution) {
4456
+ switch (mediaResolution) {
4457
+ case "low":
4458
+ return PartMediaResolutionLevel.MEDIA_RESOLUTION_LOW;
4459
+ case "medium":
4460
+ return PartMediaResolutionLevel.MEDIA_RESOLUTION_MEDIUM;
4461
+ case "high":
4462
+ return PartMediaResolutionLevel.MEDIA_RESOLUTION_HIGH;
4463
+ case "original":
4464
+ return PartMediaResolutionLevel.MEDIA_RESOLUTION_ULTRA_HIGH;
4465
+ case "auto":
4466
+ default:
4467
+ return void 0;
4468
+ }
4469
+ }
4420
4470
  function cloneContentPart(part) {
4421
4471
  switch (part.type) {
4422
4472
  case "text":
@@ -4545,7 +4595,8 @@ function convertGeminiContentToLlmContent(content) {
4545
4595
  parts: convertGooglePartsToLlmParts(content.parts ?? [])
4546
4596
  };
4547
4597
  }
4548
- function toGeminiPart(part) {
4598
+ function toGeminiPart(part, options) {
4599
+ const defaultMediaResolution = options?.defaultMediaResolution;
4549
4600
  switch (part.type) {
4550
4601
  case "text":
4551
4602
  return {
@@ -4553,6 +4604,18 @@ function toGeminiPart(part) {
4553
4604
  thought: part.thought === true ? true : void 0
4554
4605
  };
4555
4606
  case "inlineData": {
4607
+ if (isInlineImageMime(part.mimeType)) {
4608
+ const mimeType = part.mimeType ?? "application/octet-stream";
4609
+ const geminiPart = createPartFromBase64(
4610
+ part.data,
4611
+ mimeType,
4612
+ toGeminiPartMediaResolution(defaultMediaResolution)
4613
+ );
4614
+ if (part.filename && geminiPart.inlineData) {
4615
+ geminiPart.inlineData.displayName = part.filename;
4616
+ }
4617
+ return geminiPart;
4618
+ }
4556
4619
  const inlineData = {
4557
4620
  data: part.data,
4558
4621
  mimeType: part.mimeType
@@ -4565,31 +4628,35 @@ function toGeminiPart(part) {
4565
4628
  };
4566
4629
  }
4567
4630
  case "input_image": {
4631
+ const mediaResolution = resolveEffectiveMediaResolution(part.detail, defaultMediaResolution);
4632
+ const geminiPartMediaResolution = toGeminiPartMediaResolution(mediaResolution);
4568
4633
  if (part.file_id) {
4569
- return {
4570
- fileData: {
4571
- fileUri: buildCanonicalGeminiFileUri(part.file_id),
4572
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4573
- }
4574
- };
4634
+ return createPartFromUri(
4635
+ buildCanonicalGeminiFileUri(part.file_id),
4636
+ inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4637
+ geminiPartMediaResolution
4638
+ );
4575
4639
  }
4576
4640
  if (typeof part.image_url !== "string" || part.image_url.trim().length === 0) {
4577
4641
  throw new Error("input_image requires image_url or file_id.");
4578
4642
  }
4579
4643
  const parsed = parseDataUrlPayload(part.image_url);
4580
4644
  if (parsed) {
4581
- const geminiPart = createPartFromBase64(parsed.dataBase64, parsed.mimeType);
4645
+ const geminiPart = createPartFromBase64(
4646
+ parsed.dataBase64,
4647
+ parsed.mimeType,
4648
+ geminiPartMediaResolution
4649
+ );
4582
4650
  if (part.filename && geminiPart.inlineData) {
4583
4651
  geminiPart.inlineData.displayName = part.filename;
4584
4652
  }
4585
4653
  return geminiPart;
4586
4654
  }
4587
- return {
4588
- fileData: {
4589
- fileUri: part.image_url,
4590
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4591
- }
4592
- };
4655
+ return createPartFromUri(
4656
+ part.image_url,
4657
+ inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4658
+ geminiPartMediaResolution
4659
+ );
4593
4660
  }
4594
4661
  case "input_file": {
4595
4662
  if (part.file_id) {
@@ -4632,11 +4699,11 @@ function toGeminiPart(part) {
4632
4699
  throw new Error("Unsupported LLM content part");
4633
4700
  }
4634
4701
  }
4635
- function convertLlmContentToGeminiContent(content) {
4702
+ function convertLlmContentToGeminiContent(content, options) {
4636
4703
  const role = content.role === "assistant" ? "model" : "user";
4637
4704
  return {
4638
4705
  role,
4639
- parts: content.parts.map(toGeminiPart)
4706
+ parts: content.parts.map((part) => toGeminiPart(part, options))
4640
4707
  };
4641
4708
  }
4642
4709
  function resolveProvider(model) {
@@ -4817,7 +4884,7 @@ async function storeCanonicalPromptFile(options) {
4817
4884
  mimeType: options.mimeType
4818
4885
  };
4819
4886
  }
4820
- async function prepareOpenAiPromptContentItem(item) {
4887
+ async function prepareOpenAiPromptContentItem(item, options) {
4821
4888
  if (!isOpenAiNativeContentItem(item)) {
4822
4889
  return item;
4823
4890
  }
@@ -4836,7 +4903,10 @@ async function prepareOpenAiPromptContentItem(item) {
4836
4903
  });
4837
4904
  return {
4838
4905
  type: "input_image",
4839
- detail: item.detail === "high" || item.detail === "low" ? item.detail : "auto",
4906
+ detail: toOpenAiImageDetail(
4907
+ isLlmMediaResolution(item.detail) ? item.detail : void 0,
4908
+ options?.model
4909
+ ),
4840
4910
  file_id: uploaded.fileId
4841
4911
  };
4842
4912
  }
@@ -4873,7 +4943,7 @@ async function prepareOpenAiPromptContentItem(item) {
4873
4943
  }
4874
4944
  return item;
4875
4945
  }
4876
- async function prepareOpenAiPromptInput(input) {
4946
+ async function prepareOpenAiPromptInput(input, options) {
4877
4947
  const prepareItem = async (item) => {
4878
4948
  if (!item || typeof item !== "object") {
4879
4949
  return item;
@@ -4883,7 +4953,7 @@ async function prepareOpenAiPromptInput(input) {
4883
4953
  return {
4884
4954
  ...record,
4885
4955
  content: await Promise.all(
4886
- record.content.map((part) => prepareOpenAiPromptContentItem(part))
4956
+ record.content.map((part) => prepareOpenAiPromptContentItem(part, options))
4887
4957
  )
4888
4958
  };
4889
4959
  }
@@ -4891,19 +4961,19 @@ async function prepareOpenAiPromptInput(input) {
4891
4961
  return {
4892
4962
  ...record,
4893
4963
  output: await Promise.all(
4894
- record.output.map((part) => prepareOpenAiPromptContentItem(part))
4964
+ record.output.map((part) => prepareOpenAiPromptContentItem(part, options))
4895
4965
  )
4896
4966
  };
4897
4967
  }
4898
- return await prepareOpenAiPromptContentItem(item);
4968
+ return await prepareOpenAiPromptContentItem(item, options);
4899
4969
  };
4900
4970
  return await Promise.all(input.map((item) => prepareItem(item)));
4901
4971
  }
4902
- async function maybePrepareOpenAiPromptInput(input) {
4972
+ async function maybePrepareOpenAiPromptInput(input, options) {
4903
4973
  if (estimateOpenAiInlinePromptBytes(input) <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
4904
4974
  return Array.from(input);
4905
4975
  }
4906
- return await prepareOpenAiPromptInput(input);
4976
+ return await prepareOpenAiPromptInput(input, options);
4907
4977
  }
4908
4978
  function estimateGeminiInlinePromptBytes(contents) {
4909
4979
  let total = 0;
@@ -4934,22 +5004,25 @@ async function prepareGeminiPromptContents(contents) {
4934
5004
  for (const part of content.parts ?? []) {
4935
5005
  const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4936
5006
  if (canonicalFileId) {
5007
+ const mediaResolution = part.mediaResolution?.level;
4937
5008
  await getCanonicalFileMetadata(canonicalFileId);
4938
5009
  if (backend === "api") {
4939
5010
  const mirrored = await ensureGeminiFileMirror(canonicalFileId);
4940
- parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
5011
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType, mediaResolution));
4941
5012
  } else {
4942
5013
  const mirrored = await ensureVertexFileMirror(canonicalFileId);
4943
5014
  parts.push({
4944
5015
  fileData: {
4945
5016
  fileUri: mirrored.fileUri,
4946
5017
  mimeType: mirrored.mimeType
4947
- }
5018
+ },
5019
+ ...mediaResolution ? { mediaResolution: { level: mediaResolution } } : {}
4948
5020
  });
4949
5021
  }
4950
5022
  continue;
4951
5023
  }
4952
5024
  if (part.inlineData?.data) {
5025
+ const mediaResolution = part.mediaResolution?.level;
4953
5026
  const mimeType = part.inlineData.mimeType ?? "application/octet-stream";
4954
5027
  const filename = normaliseAttachmentFilename(
4955
5028
  getInlineAttachmentFilename(part.inlineData) ?? part.inlineData.displayName ?? guessInlineDataFilename(mimeType),
@@ -4962,14 +5035,15 @@ async function prepareGeminiPromptContents(contents) {
4962
5035
  });
4963
5036
  if (backend === "api") {
4964
5037
  const mirrored = await ensureGeminiFileMirror(stored.fileId);
4965
- parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
5038
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType, mediaResolution));
4966
5039
  } else {
4967
5040
  const mirrored = await ensureVertexFileMirror(stored.fileId);
4968
5041
  parts.push({
4969
5042
  fileData: {
4970
5043
  fileUri: mirrored.fileUri,
4971
5044
  mimeType: mirrored.mimeType
4972
- }
5045
+ },
5046
+ ...mediaResolution ? { mediaResolution: { level: mediaResolution } } : {}
4973
5047
  });
4974
5048
  }
4975
5049
  continue;
@@ -5432,7 +5506,7 @@ function resolveTextContents(input) {
5432
5506
  }
5433
5507
  return contents;
5434
5508
  }
5435
- function toOpenAiInput(contents) {
5509
+ function toOpenAiInput(contents, options) {
5436
5510
  const OPENAI_ROLE_FROM_LLM = {
5437
5511
  user: "user",
5438
5512
  assistant: "assistant",
@@ -5440,6 +5514,8 @@ function toOpenAiInput(contents) {
5440
5514
  developer: "developer",
5441
5515
  tool: "assistant"
5442
5516
  };
5517
+ const defaultMediaResolution = options?.defaultMediaResolution;
5518
+ const model = options?.model;
5443
5519
  return contents.map((content) => {
5444
5520
  const parts = [];
5445
5521
  for (const part of content.parts) {
@@ -5454,7 +5530,7 @@ function toOpenAiInput(contents) {
5454
5530
  const imagePart = {
5455
5531
  type: "input_image",
5456
5532
  image_url: dataUrl,
5457
- detail: "auto"
5533
+ detail: toOpenAiImageDetail(defaultMediaResolution, model)
5458
5534
  };
5459
5535
  setInlineAttachmentFilename(
5460
5536
  imagePart,
@@ -5471,11 +5547,15 @@ function toOpenAiInput(contents) {
5471
5547
  break;
5472
5548
  }
5473
5549
  case "input_image": {
5550
+ const mediaResolution = resolveEffectiveMediaResolution(
5551
+ part.detail,
5552
+ defaultMediaResolution
5553
+ );
5474
5554
  const imagePart = {
5475
5555
  type: "input_image",
5476
5556
  ...part.file_id ? { file_id: part.file_id } : {},
5477
5557
  ...part.image_url ? { image_url: part.image_url } : {},
5478
- detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
5558
+ detail: toOpenAiImageDetail(mediaResolution, model)
5479
5559
  };
5480
5560
  if (part.filename) {
5481
5561
  setInlineAttachmentFilename(imagePart, part.filename);
@@ -5508,9 +5588,11 @@ function toOpenAiInput(contents) {
5508
5588
  };
5509
5589
  });
5510
5590
  }
5511
- function toChatGptInput(contents) {
5591
+ function toChatGptInput(contents, options) {
5512
5592
  const instructionsParts = [];
5513
5593
  const input = [];
5594
+ const defaultMediaResolution = options?.defaultMediaResolution;
5595
+ const model = options?.model;
5514
5596
  for (const content of contents) {
5515
5597
  if (content.role === "system" || content.role === "developer") {
5516
5598
  for (const part of content.parts) {
@@ -5546,7 +5628,7 @@ function toChatGptInput(contents) {
5546
5628
  parts.push({
5547
5629
  type: "input_image",
5548
5630
  image_url: dataUrl,
5549
- detail: "auto"
5631
+ detail: toOpenAiImageDetail(defaultMediaResolution, model)
5550
5632
  });
5551
5633
  } else {
5552
5634
  parts.push({
@@ -5560,14 +5642,19 @@ function toChatGptInput(contents) {
5560
5642
  }
5561
5643
  break;
5562
5644
  }
5563
- case "input_image":
5645
+ case "input_image": {
5646
+ const mediaResolution = resolveEffectiveMediaResolution(
5647
+ part.detail,
5648
+ defaultMediaResolution
5649
+ );
5564
5650
  parts.push({
5565
5651
  type: "input_image",
5566
5652
  ...part.file_id ? { file_id: part.file_id } : {},
5567
5653
  ...part.image_url ? { image_url: part.image_url } : {},
5568
- detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
5654
+ detail: toOpenAiImageDetail(mediaResolution, model)
5569
5655
  });
5570
5656
  break;
5657
+ }
5571
5658
  case "input_file":
5572
5659
  parts.push({
5573
5660
  type: "input_file",
@@ -5960,6 +6047,9 @@ function isLlmToolOutputContentItem(value) {
5960
6047
  return false;
5961
6048
  }
5962
6049
  }
6050
+ if (value.detail !== void 0 && value.detail !== null && !isLlmMediaResolution(value.detail)) {
6051
+ return false;
6052
+ }
5963
6053
  return value.image_url !== void 0 || value.file_id !== void 0;
5964
6054
  }
5965
6055
  if (itemType === "input_file") {
@@ -5974,17 +6064,30 @@ function isLlmToolOutputContentItem(value) {
5974
6064
  }
5975
6065
  return false;
5976
6066
  }
5977
- function toOpenAiToolOutput(value) {
6067
+ function toOpenAiToolOutput(value, options) {
6068
+ const normalizeImageItem = (item) => {
6069
+ if (item.type !== "input_image") {
6070
+ return item;
6071
+ }
6072
+ const mediaResolution = resolveEffectiveMediaResolution(
6073
+ item.detail,
6074
+ options?.defaultMediaResolution
6075
+ );
6076
+ return {
6077
+ ...item,
6078
+ detail: toOpenAiImageDetail(mediaResolution, options?.model)
6079
+ };
6080
+ };
5978
6081
  if (isLlmToolOutputContentItem(value)) {
5979
- return [value];
6082
+ return [normalizeImageItem(value)];
5980
6083
  }
5981
6084
  if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
5982
- return value;
6085
+ return value.map((item) => normalizeImageItem(item));
5983
6086
  }
5984
6087
  return mergeToolOutput(value);
5985
6088
  }
5986
- function toChatGptToolOutput(value) {
5987
- const toolOutput = toOpenAiToolOutput(value);
6089
+ function toChatGptToolOutput(value, options) {
6090
+ const toolOutput = toOpenAiToolOutput(value, options);
5988
6091
  if (typeof toolOutput === "string") {
5989
6092
  return toolOutput;
5990
6093
  }
@@ -5996,7 +6099,12 @@ function toChatGptToolOutput(value) {
5996
6099
  type: "input_image",
5997
6100
  ...item.file_id ? { file_id: item.file_id } : {},
5998
6101
  ...item.image_url ? { image_url: item.image_url } : {},
5999
- ...item.detail ? { detail: item.detail } : {}
6102
+ ...item.detail ? {
6103
+ detail: toOpenAiImageDetail(
6104
+ resolveEffectiveMediaResolution(item.detail, options?.defaultMediaResolution),
6105
+ options?.model
6106
+ )
6107
+ } : {}
6000
6108
  };
6001
6109
  });
6002
6110
  }
@@ -6255,34 +6363,41 @@ async function maybeSpillCombinedToolCallOutputs(callResults, options) {
6255
6363
  })
6256
6364
  );
6257
6365
  }
6258
- function buildGeminiToolOutputMediaPart(item) {
6366
+ function buildGeminiToolOutputMediaPart(item, options) {
6259
6367
  if (item.type === "input_image") {
6368
+ const mediaResolution = resolveEffectiveMediaResolution(
6369
+ item.detail,
6370
+ options?.defaultMediaResolution
6371
+ );
6372
+ const geminiPartMediaResolution = toGeminiPartMediaResolution(mediaResolution);
6260
6373
  if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
6261
- return {
6262
- fileData: {
6263
- fileUri: buildCanonicalGeminiFileUri(item.file_id),
6264
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6265
- }
6266
- };
6374
+ return createPartFromUri(
6375
+ buildCanonicalGeminiFileUri(item.file_id),
6376
+ inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6377
+ geminiPartMediaResolution
6378
+ );
6267
6379
  }
6268
6380
  if (typeof item.image_url !== "string" || item.image_url.trim().length === 0) {
6269
6381
  return null;
6270
6382
  }
6271
6383
  const parsed = parseDataUrlPayload(item.image_url);
6272
6384
  if (parsed) {
6273
- const part = createPartFromBase64(parsed.dataBase64, parsed.mimeType);
6385
+ const part = createPartFromBase64(
6386
+ parsed.dataBase64,
6387
+ parsed.mimeType,
6388
+ geminiPartMediaResolution
6389
+ );
6274
6390
  const displayName = item.filename?.trim();
6275
6391
  if (displayName && part.inlineData) {
6276
6392
  part.inlineData.displayName = displayName;
6277
6393
  }
6278
6394
  return part;
6279
6395
  }
6280
- return {
6281
- fileData: {
6282
- fileUri: item.image_url,
6283
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6284
- }
6285
- };
6396
+ return createPartFromUri(
6397
+ item.image_url,
6398
+ inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6399
+ geminiPartMediaResolution
6400
+ );
6286
6401
  }
6287
6402
  if (item.type === "input_file") {
6288
6403
  if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
@@ -6360,7 +6475,9 @@ function buildGeminiFunctionResponseParts(options) {
6360
6475
  }
6361
6476
  const responseOutput = outputItems.map((item) => toGeminiToolOutputPlaceholder(item));
6362
6477
  const responseParts = outputItems.flatMap((item) => {
6363
- const mediaPart = buildGeminiToolOutputMediaPart(item);
6478
+ const mediaPart = buildGeminiToolOutputMediaPart(item, {
6479
+ defaultMediaResolution: options.defaultMediaResolution
6480
+ });
6364
6481
  return mediaPart ? [mediaPart] : [];
6365
6482
  });
6366
6483
  const responsePayload = { output: responseOutput };
@@ -7127,6 +7244,7 @@ function startLlmCallLoggerFromContents(options) {
7127
7244
  ...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
7128
7245
  ...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
7129
7246
  ...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
7247
+ ...options.request.mediaResolution ? { mediaResolution: options.request.mediaResolution } : {},
7130
7248
  ...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
7131
7249
  ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
7132
7250
  },
@@ -7237,7 +7355,13 @@ async function runTextCall(params) {
7237
7355
  const { result } = await collectFileUploadMetrics(async () => {
7238
7356
  try {
7239
7357
  if (provider === "openai") {
7240
- const openAiInput = await maybePrepareOpenAiPromptInput(toOpenAiInput(contents));
7358
+ const openAiInput = await maybePrepareOpenAiPromptInput(
7359
+ toOpenAiInput(contents, {
7360
+ defaultMediaResolution: request.mediaResolution,
7361
+ model: request.model
7362
+ }),
7363
+ { model: request.model }
7364
+ );
7241
7365
  const openAiTools = toOpenAiTools(request.tools);
7242
7366
  const reasoningEffort = resolveOpenAiReasoningEffort(
7243
7367
  modelForProvider,
@@ -7311,7 +7435,10 @@ async function runTextCall(params) {
7311
7435
  }
7312
7436
  }, modelForProvider);
7313
7437
  } else if (provider === "chatgpt") {
7314
- const chatGptInput = toChatGptInput(contents);
7438
+ const chatGptInput = toChatGptInput(contents, {
7439
+ defaultMediaResolution: request.mediaResolution,
7440
+ model: request.model
7441
+ });
7315
7442
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
7316
7443
  const openAiTools = toOpenAiTools(request.tools);
7317
7444
  const requestPayload = {
@@ -7408,12 +7535,18 @@ async function runTextCall(params) {
7408
7535
  }, modelForProvider);
7409
7536
  } else {
7410
7537
  const geminiContents = await maybePrepareGeminiPromptContents(
7411
- contents.map(convertLlmContentToGeminiContent)
7538
+ contents.map(
7539
+ (content2) => convertLlmContentToGeminiContent(content2, {
7540
+ defaultMediaResolution: request.mediaResolution
7541
+ })
7542
+ )
7412
7543
  );
7413
7544
  const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
7545
+ const mediaResolution = toGeminiMediaResolution(request.mediaResolution);
7414
7546
  const config = {
7415
7547
  maxOutputTokens: 32e3,
7416
7548
  ...thinkingConfig ? { thinkingConfig } : {},
7549
+ ...mediaResolution ? { mediaResolution } : {},
7417
7550
  ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
7418
7551
  ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
7419
7552
  ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
@@ -8091,7 +8224,10 @@ async function runToolLoop(request) {
8091
8224
  summary: "detailed"
8092
8225
  };
8093
8226
  let previousResponseId;
8094
- let input = toOpenAiInput(contents);
8227
+ let input = toOpenAiInput(contents, {
8228
+ defaultMediaResolution: request.mediaResolution,
8229
+ model: request.model
8230
+ });
8095
8231
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
8096
8232
  const turn = stepIndex + 1;
8097
8233
  const stepStartedAtMs = Date.now();
@@ -8118,7 +8254,9 @@ async function runToolLoop(request) {
8118
8254
  let reasoningSummary = "";
8119
8255
  let stepToolCallText;
8120
8256
  let stepToolCallPayload;
8121
- const preparedInput = await maybePrepareOpenAiPromptInput(input);
8257
+ const preparedInput = await maybePrepareOpenAiPromptInput(input, {
8258
+ model: request.model
8259
+ });
8122
8260
  const stepRequestPayload = {
8123
8261
  model: providerInfo.model,
8124
8262
  input: preparedInput,
@@ -8249,7 +8387,10 @@ async function runToolLoop(request) {
8249
8387
  const stepToolCalls = [];
8250
8388
  if (responseToolCalls.length === 0) {
8251
8389
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
8252
- const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
8390
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2, {
8391
+ defaultMediaResolution: request.mediaResolution,
8392
+ model: request.model
8393
+ }) : [];
8253
8394
  finalText = responseText;
8254
8395
  finalThoughts = reasoningSummary;
8255
8396
  const stepCompletedAtMs2 = Date.now();
@@ -8380,13 +8521,19 @@ async function runToolLoop(request) {
8380
8521
  toolOutputs.push({
8381
8522
  type: "custom_tool_call_output",
8382
8523
  call_id: entry.call.call_id,
8383
- output: toOpenAiToolOutput(outputPayload)
8524
+ output: toOpenAiToolOutput(outputPayload, {
8525
+ defaultMediaResolution: request.mediaResolution,
8526
+ model: request.model
8527
+ })
8384
8528
  });
8385
8529
  } else {
8386
8530
  toolOutputs.push({
8387
8531
  type: "function_call_output",
8388
8532
  call_id: entry.call.call_id,
8389
- output: toOpenAiToolOutput(outputPayload)
8533
+ output: toOpenAiToolOutput(outputPayload, {
8534
+ defaultMediaResolution: request.mediaResolution,
8535
+ model: request.model
8536
+ })
8390
8537
  });
8391
8538
  }
8392
8539
  }
@@ -8411,7 +8558,10 @@ async function runToolLoop(request) {
8411
8558
  timing
8412
8559
  });
8413
8560
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
8414
- const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
8561
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput, {
8562
+ defaultMediaResolution: request.mediaResolution,
8563
+ model: request.model
8564
+ }) : [];
8415
8565
  stepCallLogger?.complete({
8416
8566
  responseText,
8417
8567
  toolCallText: stepToolCallText,
@@ -8456,7 +8606,10 @@ async function runToolLoop(request) {
8456
8606
  const openAiNativeTools = toOpenAiTools(request.modelTools);
8457
8607
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
8458
8608
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
8459
- const toolLoopInput = toChatGptInput(contents);
8609
+ const toolLoopInput = toChatGptInput(contents, {
8610
+ defaultMediaResolution: request.mediaResolution,
8611
+ model: request.model
8612
+ });
8460
8613
  const conversationId = `tool-loop-${randomBytes(8).toString("hex")}`;
8461
8614
  const promptCacheKey = conversationId;
8462
8615
  let input = [...toolLoopInput.input];
@@ -8560,7 +8713,10 @@ async function runToolLoop(request) {
8560
8713
  stepToolCallText = serialiseLogArtifactText(stepToolCallPayload);
8561
8714
  if (responseToolCalls.length === 0) {
8562
8715
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
8563
- const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
8716
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2, {
8717
+ defaultMediaResolution: request.mediaResolution,
8718
+ model: request.model
8719
+ }).input : [];
8564
8720
  finalText = responseText;
8565
8721
  finalThoughts = reasoningSummaryText;
8566
8722
  const stepCompletedAtMs2 = Date.now();
@@ -8692,7 +8848,10 @@ async function runToolLoop(request) {
8692
8848
  toolOutputs.push({
8693
8849
  type: "custom_tool_call_output",
8694
8850
  call_id: entry.ids.callId,
8695
- output: toChatGptToolOutput(outputPayload)
8851
+ output: toChatGptToolOutput(outputPayload, {
8852
+ defaultMediaResolution: request.mediaResolution,
8853
+ model: request.model
8854
+ })
8696
8855
  });
8697
8856
  } else {
8698
8857
  toolOutputs.push({
@@ -8706,7 +8865,10 @@ async function runToolLoop(request) {
8706
8865
  toolOutputs.push({
8707
8866
  type: "function_call_output",
8708
8867
  call_id: entry.ids.callId,
8709
- output: toChatGptToolOutput(outputPayload)
8868
+ output: toChatGptToolOutput(outputPayload, {
8869
+ defaultMediaResolution: request.mediaResolution,
8870
+ model: request.model
8871
+ })
8710
8872
  });
8711
8873
  }
8712
8874
  }
@@ -8730,7 +8892,10 @@ async function runToolLoop(request) {
8730
8892
  timing
8731
8893
  });
8732
8894
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
8733
- const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
8895
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput, {
8896
+ defaultMediaResolution: request.mediaResolution,
8897
+ model: request.model
8898
+ }).input : [];
8734
8899
  stepCallLogger?.complete({
8735
8900
  responseText,
8736
8901
  toolCallText: stepToolCallText,
@@ -9061,7 +9226,11 @@ async function runToolLoop(request) {
9061
9226
  const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
9062
9227
  const geminiNativeTools = toGeminiTools(request.modelTools);
9063
9228
  const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
9064
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
9229
+ const geminiContents = contents.map(
9230
+ (content) => convertLlmContentToGeminiContent(content, {
9231
+ defaultMediaResolution: request.mediaResolution
9232
+ })
9233
+ );
9065
9234
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
9066
9235
  const turn = stepIndex + 1;
9067
9236
  const stepStartedAtMs = Date.now();
@@ -9079,6 +9248,7 @@ async function runToolLoop(request) {
9079
9248
  }
9080
9249
  };
9081
9250
  const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
9251
+ const mediaResolution = toGeminiMediaResolution(request.mediaResolution);
9082
9252
  const config = {
9083
9253
  maxOutputTokens: 32e3,
9084
9254
  tools: geminiTools,
@@ -9087,7 +9257,8 @@ async function runToolLoop(request) {
9087
9257
  mode: FunctionCallingConfigMode.VALIDATED
9088
9258
  }
9089
9259
  },
9090
- ...thinkingConfig ? { thinkingConfig } : {}
9260
+ ...thinkingConfig ? { thinkingConfig } : {},
9261
+ ...mediaResolution ? { mediaResolution } : {}
9091
9262
  };
9092
9263
  const onEvent = request.onEvent;
9093
9264
  const preparedGeminiContents = await maybePrepareGeminiPromptContents(geminiContents);
@@ -9243,7 +9414,13 @@ async function runToolLoop(request) {
9243
9414
  } else if (response.responseText.length > 0) {
9244
9415
  geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
9245
9416
  }
9246
- geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
9417
+ geminiContents.push(
9418
+ ...steeringInput2.map(
9419
+ (content) => convertLlmContentToGeminiContent(content, {
9420
+ defaultMediaResolution: request.mediaResolution
9421
+ })
9422
+ )
9423
+ );
9247
9424
  continue;
9248
9425
  }
9249
9426
  const toolCalls = [];
@@ -9335,7 +9512,8 @@ async function runToolLoop(request) {
9335
9512
  ...buildGeminiFunctionResponseParts({
9336
9513
  toolName: entry.toolName,
9337
9514
  callId: entry.call.id,
9338
- outputPayload
9515
+ outputPayload,
9516
+ defaultMediaResolution: request.mediaResolution
9339
9517
  })
9340
9518
  );
9341
9519
  }
@@ -9380,7 +9558,13 @@ async function runToolLoop(request) {
9380
9558
  geminiContents.push({ role: "user", parts: responseParts });
9381
9559
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
9382
9560
  if (steeringInput.length > 0) {
9383
- geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
9561
+ geminiContents.push(
9562
+ ...steeringInput.map(
9563
+ (content) => convertLlmContentToGeminiContent(content, {
9564
+ defaultMediaResolution: request.mediaResolution
9565
+ })
9566
+ )
9567
+ );
9384
9568
  }
9385
9569
  } catch (error) {
9386
9570
  stepCallLogger?.fail(error, {
@@ -9636,7 +9820,7 @@ async function generateImages(request) {
9636
9820
  }
9637
9821
  return image;
9638
9822
  })(),
9639
- model: "gpt-5.2"
9823
+ model: "gpt-5.4-mini"
9640
9824
  })
9641
9825
  )
9642
9826
  );
@@ -9852,7 +10036,6 @@ var spawnAgentInputSchema = z4.object({
9852
10036
  "When true, fork the current thread history into the new agent before sending the initial prompt. This must be used when you want the new agent to have exactly the same context as you."
9853
10037
  ),
9854
10038
  instructions: z4.string().nullish().describe("Optional extra instructions for this subagent instance."),
9855
- model: z4.string().nullish().describe("Optional model override. Must be one of this package's supported text model ids."),
9856
10039
  max_steps: z4.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).nullish().describe("Optional max step budget for each subagent run.")
9857
10040
  });
9858
10041
  var sendInputSchema = z4.object({
@@ -9943,7 +10126,6 @@ function resolveSubagentToolConfig(selection, currentDepth) {
9943
10126
  maxWaitTimeoutMs,
9944
10127
  promptPattern,
9945
10128
  ...instructions ? { instructions } : {},
9946
- ...config.model ? { model: config.model } : {},
9947
10129
  ...maxSteps ? { maxSteps } : {},
9948
10130
  inheritTools: config.inheritTools !== false,
9949
10131
  inheritFilesystemTool: config.inheritFilesystemTool !== false
@@ -9995,13 +10177,6 @@ function createSubagentToolController(options) {
9995
10177
  `Subagent depth limit reached (${options.config.maxDepth}). Cannot spawn at depth ${childDepth}.`
9996
10178
  );
9997
10179
  }
9998
- let model = options.config.model ?? options.parentModel;
9999
- if (input.model) {
10000
- if (!isLlmTextModelId(input.model)) {
10001
- throw new Error(`Unsupported subagent model id: ${input.model}`);
10002
- }
10003
- model = input.model;
10004
- }
10005
10180
  const id = `agent_${randomBytes2(6).toString("hex")}`;
10006
10181
  const now = Date.now();
10007
10182
  const { roleName, roleInstructions } = resolveAgentType(input.agent_type);
@@ -10021,7 +10196,7 @@ function createSubagentToolController(options) {
10021
10196
  const agent = {
10022
10197
  id,
10023
10198
  depth: childDepth,
10024
- model,
10199
+ model: options.parentModel,
10025
10200
  ...nickname ? { nickname } : {},
10026
10201
  agentRole: roleName,
10027
10202
  status: "idle",
@@ -11776,7 +11951,8 @@ async function viewImageCodex(input, options) {
11776
11951
  return [
11777
11952
  {
11778
11953
  type: "input_image",
11779
- image_url: `data:${mimeType};base64,${bytes.toString("base64")}`
11954
+ image_url: `data:${mimeType};base64,${bytes.toString("base64")}`,
11955
+ ...options.mediaResolution ? { detail: options.mediaResolution } : {}
11780
11956
  }
11781
11957
  ];
11782
11958
  }
@@ -12456,7 +12632,11 @@ async function runAgentLoopInternal(request, context) {
12456
12632
  const toolLoopRequestWithSteering = toolLoopRequest.steering === steeringChannel ? toolLoopRequest : { ...toolLoopRequest, steering: steeringChannel };
12457
12633
  const filesystemSelection = filesystemTool ?? filesystem_tool;
12458
12634
  const subagentSelection = subagentTool ?? subagent_tool ?? subagents;
12459
- const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
12635
+ const filesystemTools = resolveFilesystemTools(
12636
+ request.model,
12637
+ filesystemSelection,
12638
+ request.mediaResolution
12639
+ );
12460
12640
  const resolvedSubagentConfig = resolveSubagentToolConfig(subagentSelection, context.depth);
12461
12641
  const subagentController = createSubagentController({
12462
12642
  runId,
@@ -12608,24 +12788,47 @@ async function runAgentLoopInternal(request, context) {
12608
12788
  await subagentController?.closeAll();
12609
12789
  }
12610
12790
  }
12611
- function resolveFilesystemTools(model, selection) {
12791
+ function resolveFilesystemTools(model, selection, defaultMediaResolution) {
12792
+ const withDefaultMediaResolution = (options) => {
12793
+ if (defaultMediaResolution === void 0) {
12794
+ return options;
12795
+ }
12796
+ return {
12797
+ mediaResolution: defaultMediaResolution,
12798
+ ...options ?? {}
12799
+ };
12800
+ };
12612
12801
  if (selection === void 0 || selection === false) {
12613
12802
  return {};
12614
12803
  }
12615
12804
  if (selection === true) {
12616
- return createFilesystemToolSetForModel(model, "auto");
12805
+ return createFilesystemToolSetForModel(model, withDefaultMediaResolution(void 0) ?? {});
12617
12806
  }
12618
12807
  if (typeof selection === "string") {
12619
- return createFilesystemToolSetForModel(model, selection);
12808
+ return createFilesystemToolSetForModel(model, selection, withDefaultMediaResolution(void 0));
12620
12809
  }
12621
12810
  if (selection.enabled === false) {
12622
12811
  return {};
12623
12812
  }
12624
12813
  if (selection.options && selection.profile !== void 0) {
12625
- return createFilesystemToolSetForModel(model, selection.profile, selection.options);
12814
+ return createFilesystemToolSetForModel(
12815
+ model,
12816
+ selection.profile,
12817
+ withDefaultMediaResolution(selection.options)
12818
+ );
12626
12819
  }
12627
12820
  if (selection.options) {
12628
- return createFilesystemToolSetForModel(model, selection.options);
12821
+ return createFilesystemToolSetForModel(
12822
+ model,
12823
+ withDefaultMediaResolution(selection.options) ?? {}
12824
+ );
12825
+ }
12826
+ if (defaultMediaResolution !== void 0) {
12827
+ return createFilesystemToolSetForModel(
12828
+ model,
12829
+ selection.profile ?? "auto",
12830
+ withDefaultMediaResolution(void 0)
12831
+ );
12629
12832
  }
12630
12833
  return createFilesystemToolSetForModel(model, selection.profile ?? "auto");
12631
12834
  }
@@ -12648,7 +12851,7 @@ function createSubagentController(params) {
12648
12851
  return createSubagentToolController({
12649
12852
  config: params.resolvedSubagentConfig,
12650
12853
  parentDepth: params.depth,
12651
- parentModel: params.resolvedSubagentConfig.model ?? params.model,
12854
+ parentModel: params.model,
12652
12855
  forkContextMessages: normalizeForkContextMessages(params.toolLoopRequest.input),
12653
12856
  onBackgroundMessage: (message) => {
12654
12857
  params.steering?.append({ role: "user", content: message });
@@ -12668,6 +12871,7 @@ function createSubagentController(params) {
12668
12871
  modelTools: params.toolLoopRequest.modelTools,
12669
12872
  maxSteps: subagentRequest.maxSteps,
12670
12873
  thinkingLevel: params.toolLoopRequest.thinkingLevel,
12874
+ mediaResolution: params.toolLoopRequest.mediaResolution,
12671
12875
  signal: subagentRequest.signal
12672
12876
  },
12673
12877
  {