@ljoukov/llm 4.1.0 → 5.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.cjs CHANGED
@@ -53,6 +53,7 @@ __export(index_exports, {
53
53
  applyPatch: () => applyPatch,
54
54
  configureGemini: () => configureGemini,
55
55
  configureModelConcurrency: () => configureModelConcurrency,
56
+ configureTelemetry: () => configureTelemetry,
56
57
  convertGooglePartsToLlmParts: () => convertGooglePartsToLlmParts,
57
58
  createApplyPatchTool: () => createApplyPatchTool,
58
59
  createCodexApplyPatchTool: () => createCodexApplyPatchTool,
@@ -101,6 +102,7 @@ __export(index_exports, {
101
102
  parseJsonFromLlmText: () => parseJsonFromLlmText,
102
103
  refreshChatGptOauthToken: () => refreshChatGptOauthToken,
103
104
  resetModelConcurrencyConfig: () => resetModelConcurrencyConfig,
105
+ resetTelemetry: () => resetTelemetry,
104
106
  resolveFilesystemToolProfile: () => resolveFilesystemToolProfile,
105
107
  resolveFireworksModelId: () => resolveFireworksModelId,
106
108
  runAgentLoop: () => runAgentLoop,
@@ -2674,7 +2676,7 @@ function getOpenAiFetch() {
2674
2676
  headersTimeout: timeoutMs
2675
2677
  });
2676
2678
  openAiClientState.cachedFetch = ((input, init) => {
2677
- return (0, import_undici2.fetch)(input, {
2679
+ return fetch(input, {
2678
2680
  ...init ?? {},
2679
2681
  dispatcher
2680
2682
  });
@@ -3384,6 +3386,9 @@ var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
3384
3386
  var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
3385
3387
  var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
3386
3388
  var FILES_TEMP_ROOT = import_node_path4.default.join(import_node_os3.default.tmpdir(), "ljoukov-llm-files");
3389
+ var FILES_CACHE_ROOT = import_node_path4.default.join(FILES_TEMP_ROOT, "cache");
3390
+ var FILES_CACHE_CONTENT_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT, "content");
3391
+ var FILES_CACHE_METADATA_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT, "metadata");
3387
3392
  var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
3388
3393
  metadataById: /* @__PURE__ */ new Map(),
3389
3394
  openAiUploadCacheByKey: /* @__PURE__ */ new Map(),
@@ -3529,6 +3534,12 @@ function toStoredFile(file) {
3529
3534
  function buildCacheKey(filename, mimeType, sha256Hex) {
3530
3535
  return `${sha256Hex}\0${filename}\0${mimeType}`;
3531
3536
  }
3537
+ function buildCachedContentPath(sha256Hex) {
3538
+ return import_node_path4.default.join(FILES_CACHE_CONTENT_ROOT, sha256Hex);
3539
+ }
3540
+ function buildCachedMetadataPath(fileId) {
3541
+ return import_node_path4.default.join(FILES_CACHE_METADATA_ROOT, `${fileId}.json`);
3542
+ }
3532
3543
  function isFresh(file) {
3533
3544
  if (!file.expires_at) {
3534
3545
  return true;
@@ -3549,6 +3560,82 @@ function recordMetadata(metadata) {
3549
3560
  }
3550
3561
  return metadata;
3551
3562
  }
3563
+ async function ensureFilesCacheReady() {
3564
+ await (0, import_promises2.mkdir)(FILES_CACHE_CONTENT_ROOT, { recursive: true });
3565
+ await (0, import_promises2.mkdir)(FILES_CACHE_METADATA_ROOT, { recursive: true });
3566
+ }
3567
+ async function cacheBufferLocally(bytes, sha256Hex) {
3568
+ await ensureFilesCacheReady();
3569
+ const localPath = buildCachedContentPath(sha256Hex);
3570
+ try {
3571
+ await (0, import_promises2.writeFile)(localPath, bytes, { flag: "wx" });
3572
+ } catch (error) {
3573
+ const code = error.code;
3574
+ if (code !== "EEXIST") {
3575
+ throw error;
3576
+ }
3577
+ }
3578
+ return localPath;
3579
+ }
3580
+ async function cacheFileLocally(filePath, sha256Hex) {
3581
+ await ensureFilesCacheReady();
3582
+ const localPath = buildCachedContentPath(sha256Hex);
3583
+ try {
3584
+ await (0, import_promises2.copyFile)(filePath, localPath);
3585
+ } catch (error) {
3586
+ const code = error.code;
3587
+ if (code !== "EEXIST") {
3588
+ throw error;
3589
+ }
3590
+ }
3591
+ return localPath;
3592
+ }
3593
+ async function persistMetadataToDisk(metadata) {
3594
+ await ensureFilesCacheReady();
3595
+ const payload = {
3596
+ file: metadata.file,
3597
+ filename: metadata.filename,
3598
+ bytes: metadata.bytes,
3599
+ mimeType: metadata.mimeType,
3600
+ sha256Hex: metadata.sha256Hex,
3601
+ localPath: metadata.localPath
3602
+ };
3603
+ await (0, import_promises2.writeFile)(
3604
+ buildCachedMetadataPath(metadata.file.id),
3605
+ `${JSON.stringify(payload, null, 2)}
3606
+ `
3607
+ );
3608
+ }
3609
+ async function loadPersistedMetadata(fileId) {
3610
+ try {
3611
+ const payload = JSON.parse(
3612
+ await (0, import_promises2.readFile)(buildCachedMetadataPath(fileId), "utf8")
3613
+ );
3614
+ if (!payload || typeof payload !== "object" || !payload.file) {
3615
+ return void 0;
3616
+ }
3617
+ if (payload.localPath) {
3618
+ try {
3619
+ const localStats = await (0, import_promises2.stat)(payload.localPath);
3620
+ if (!localStats.isFile()) {
3621
+ return void 0;
3622
+ }
3623
+ } catch {
3624
+ return void 0;
3625
+ }
3626
+ }
3627
+ return recordMetadata({
3628
+ file: payload.file,
3629
+ filename: payload.filename,
3630
+ bytes: payload.bytes,
3631
+ mimeType: payload.mimeType,
3632
+ sha256Hex: payload.sha256Hex,
3633
+ localPath: payload.localPath
3634
+ });
3635
+ } catch {
3636
+ return void 0;
3637
+ }
3638
+ }
3552
3639
  async function uploadOpenAiFileFromBytes(params) {
3553
3640
  const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3554
3641
  const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
@@ -3697,17 +3784,23 @@ async function retrieveOpenAiFile(fileId) {
3697
3784
  if (cached && isFresh(cached.file)) {
3698
3785
  return cached;
3699
3786
  }
3787
+ const persisted = await loadPersistedMetadata(fileId);
3788
+ if (persisted && isFresh(persisted.file)) {
3789
+ return persisted;
3790
+ }
3700
3791
  const client = getOpenAiClient();
3701
3792
  const retrieved = await client.files.retrieve(fileId);
3702
3793
  const file = toStoredFile(retrieved);
3703
- return recordMetadata({
3794
+ const metadata = recordMetadata({
3704
3795
  file,
3705
3796
  filename: file.filename,
3706
3797
  bytes: file.bytes,
3707
- mimeType: cached?.mimeType ?? resolveMimeType(file.filename, void 0),
3708
- sha256Hex: cached?.sha256Hex,
3709
- localPath: cached?.localPath
3798
+ mimeType: cached?.mimeType ?? persisted?.mimeType ?? resolveMimeType(file.filename, void 0),
3799
+ sha256Hex: cached?.sha256Hex ?? persisted?.sha256Hex,
3800
+ localPath: cached?.localPath ?? persisted?.localPath
3710
3801
  });
3802
+ await persistMetadataToDisk(metadata);
3803
+ return metadata;
3711
3804
  }
3712
3805
  function buildGeminiMirrorName(sha256Hex) {
3713
3806
  return `files/${sha256Hex.slice(0, 40)}`;
@@ -3819,6 +3912,7 @@ async function materializeOpenAiFile(fileId) {
3819
3912
  sha256Hex,
3820
3913
  localPath
3821
3914
  });
3915
+ await persistMetadataToDisk(updated);
3822
3916
  return {
3823
3917
  file: updated.file,
3824
3918
  filename: updated.filename,
@@ -3982,7 +4076,13 @@ async function filesCreate(params) {
3982
4076
  sha256Hex: sha256Hex2,
3983
4077
  bytes: info.size
3984
4078
  });
3985
- return uploaded2.file;
4079
+ const localPath2 = await cacheFileLocally(filePath, sha256Hex2);
4080
+ const cached2 = recordMetadata({
4081
+ ...uploaded2,
4082
+ localPath: localPath2
4083
+ });
4084
+ await persistMetadataToDisk(cached2);
4085
+ return cached2.file;
3986
4086
  }
3987
4087
  const filename = normaliseFilename(params.filename);
3988
4088
  const bytes = toBuffer(params.data);
@@ -3996,7 +4096,13 @@ async function filesCreate(params) {
3996
4096
  expiresAfterSeconds,
3997
4097
  sha256Hex
3998
4098
  });
3999
- return uploaded.file;
4099
+ const localPath = await cacheBufferLocally(bytes, sha256Hex);
4100
+ const cached = recordMetadata({
4101
+ ...uploaded,
4102
+ localPath
4103
+ });
4104
+ await persistMetadataToDisk(cached);
4105
+ return cached.file;
4000
4106
  }
4001
4107
  async function filesRetrieve(fileId) {
4002
4108
  return (await retrieveOpenAiFile(fileId)).file;
@@ -4029,6 +4135,10 @@ async function filesDelete(fileId) {
4029
4135
  const response = await getOpenAiClient().files.delete(fileId);
4030
4136
  filesState.metadataById.delete(fileId);
4031
4137
  filesState.materializedById.delete(fileId);
4138
+ try {
4139
+ await (0, import_promises2.unlink)(buildCachedMetadataPath(fileId));
4140
+ } catch {
4141
+ }
4032
4142
  return {
4033
4143
  id: response.id,
4034
4144
  deleted: response.deleted,
@@ -4057,6 +4167,71 @@ var files = {
4057
4167
  content: filesContent
4058
4168
  };
4059
4169
 
4170
+ // src/telemetry.ts
4171
+ var telemetryState = getRuntimeSingleton(
4172
+ /* @__PURE__ */ Symbol.for("@ljoukov/llm.telemetryState"),
4173
+ () => ({
4174
+ configuredTelemetry: void 0
4175
+ })
4176
+ );
4177
+ function configureTelemetry(telemetry = void 0) {
4178
+ telemetryState.configuredTelemetry = telemetry === void 0 || telemetry === false ? void 0 : telemetry;
4179
+ }
4180
+ function resetTelemetry() {
4181
+ telemetryState.configuredTelemetry = void 0;
4182
+ }
4183
+ function isPromiseLike2(value) {
4184
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
4185
+ }
4186
+ function resolveTelemetrySelection(telemetry) {
4187
+ if (telemetry === false) {
4188
+ return void 0;
4189
+ }
4190
+ if (telemetry !== void 0) {
4191
+ return telemetry;
4192
+ }
4193
+ return telemetryState.configuredTelemetry;
4194
+ }
4195
+ function createTelemetrySession(telemetry) {
4196
+ const config = resolveTelemetrySelection(telemetry);
4197
+ if (!config) {
4198
+ return void 0;
4199
+ }
4200
+ const pending = /* @__PURE__ */ new Set();
4201
+ const trackPromise = (promise) => {
4202
+ pending.add(promise);
4203
+ promise.finally(() => {
4204
+ pending.delete(promise);
4205
+ });
4206
+ };
4207
+ const emit = (event) => {
4208
+ try {
4209
+ const output = config.sink.emit(event);
4210
+ if (isPromiseLike2(output)) {
4211
+ const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
4212
+ trackPromise(task);
4213
+ }
4214
+ } catch {
4215
+ }
4216
+ };
4217
+ const flush = async () => {
4218
+ while (pending.size > 0) {
4219
+ await Promise.allSettled([...pending]);
4220
+ }
4221
+ if (typeof config.sink.flush === "function") {
4222
+ try {
4223
+ await config.sink.flush();
4224
+ } catch {
4225
+ }
4226
+ }
4227
+ };
4228
+ return {
4229
+ includeStreamEvents: config.includeStreamEvents === true,
4230
+ emit,
4231
+ flush
4232
+ };
4233
+ }
4234
+
4060
4235
  // src/llm.ts
4061
4236
  var toolCallContextStorage = getRuntimeSingleton(
4062
4237
  /* @__PURE__ */ Symbol.for("@ljoukov/llm.toolCallContextStorage"),
@@ -4508,8 +4683,7 @@ function toGeminiPart(part) {
4508
4683
  return {
4509
4684
  fileData: {
4510
4685
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4511
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4512
- displayName: part.filename ?? void 0
4686
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4513
4687
  }
4514
4688
  };
4515
4689
  }
@@ -4527,8 +4701,7 @@ function toGeminiPart(part) {
4527
4701
  return {
4528
4702
  fileData: {
4529
4703
  fileUri: part.image_url,
4530
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4531
- displayName: part.filename ?? void 0
4704
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4532
4705
  }
4533
4706
  };
4534
4707
  }
@@ -4537,8 +4710,7 @@ function toGeminiPart(part) {
4537
4710
  return {
4538
4711
  fileData: {
4539
4712
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4540
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4541
- displayName: part.filename ?? void 0
4713
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4542
4714
  }
4543
4715
  };
4544
4716
  }
@@ -4564,8 +4736,7 @@ function toGeminiPart(part) {
4564
4736
  return {
4565
4737
  fileData: {
4566
4738
  fileUri: part.file_url,
4567
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4568
- displayName: part.filename ?? void 0
4739
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4569
4740
  }
4570
4741
  };
4571
4742
  }
@@ -4797,7 +4968,7 @@ async function prepareOpenAiPromptContentItem(item) {
4797
4968
  mimeType,
4798
4969
  filename
4799
4970
  });
4800
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4971
+ return { type: "input_file", file_id: uploaded.fileId };
4801
4972
  }
4802
4973
  if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4803
4974
  const parsed = parseDataUrlPayload(item.file_url);
@@ -4812,7 +4983,7 @@ async function prepareOpenAiPromptContentItem(item) {
4812
4983
  guessInlineDataFilename(parsed.mimeType)
4813
4984
  )
4814
4985
  });
4815
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4986
+ return { type: "input_file", file_id: uploaded.fileId };
4816
4987
  }
4817
4988
  return item;
4818
4989
  }
@@ -4877,21 +5048,16 @@ async function prepareGeminiPromptContents(contents) {
4877
5048
  for (const part of content.parts ?? []) {
4878
5049
  const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4879
5050
  if (canonicalFileId) {
4880
- const metadata = await getCanonicalFileMetadata(canonicalFileId);
5051
+ await getCanonicalFileMetadata(canonicalFileId);
4881
5052
  if (backend === "api") {
4882
5053
  const mirrored = await ensureGeminiFileMirror(canonicalFileId);
4883
- const mirroredPart = (0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType);
4884
- if (metadata.filename && mirroredPart.fileData) {
4885
- mirroredPart.fileData.displayName = metadata.filename;
4886
- }
4887
- parts.push(mirroredPart);
5054
+ parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
4888
5055
  } else {
4889
5056
  const mirrored = await ensureVertexFileMirror(canonicalFileId);
4890
5057
  parts.push({
4891
5058
  fileData: {
4892
5059
  fileUri: mirrored.fileUri,
4893
- mimeType: mirrored.mimeType,
4894
- displayName: metadata.filename
5060
+ mimeType: mirrored.mimeType
4895
5061
  }
4896
5062
  });
4897
5063
  }
@@ -4910,18 +5076,13 @@ async function prepareGeminiPromptContents(contents) {
4910
5076
  });
4911
5077
  if (backend === "api") {
4912
5078
  const mirrored = await ensureGeminiFileMirror(stored.fileId);
4913
- const mirroredPart = (0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType);
4914
- if (filename && mirroredPart.fileData) {
4915
- mirroredPart.fileData.displayName = filename;
4916
- }
4917
- parts.push(mirroredPart);
5079
+ parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
4918
5080
  } else {
4919
5081
  const mirrored = await ensureVertexFileMirror(stored.fileId);
4920
5082
  parts.push({
4921
5083
  fileData: {
4922
5084
  fileUri: mirrored.fileUri,
4923
- mimeType: mirrored.mimeType,
4924
- displayName: filename
5085
+ mimeType: mirrored.mimeType
4925
5086
  }
4926
5087
  });
4927
5088
  }
@@ -5442,7 +5603,7 @@ function toOpenAiInput(contents) {
5442
5603
  ...part.file_id ? { file_id: part.file_id } : {},
5443
5604
  ...part.file_data ? { file_data: part.file_data } : {},
5444
5605
  ...part.file_url ? { file_url: part.file_url } : {},
5445
- ...part.filename ? { filename: part.filename } : {}
5606
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5446
5607
  });
5447
5608
  break;
5448
5609
  default:
@@ -5527,7 +5688,7 @@ function toChatGptInput(contents) {
5527
5688
  ...part.file_id ? { file_id: part.file_id } : {},
5528
5689
  ...part.file_data ? { file_data: part.file_data } : {},
5529
5690
  ...part.file_url ? { file_url: part.file_url } : {},
5530
- ...part.filename ? { filename: part.filename } : {}
5691
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5531
5692
  });
5532
5693
  break;
5533
5694
  default:
@@ -5657,6 +5818,65 @@ function mergeTokenUpdates(current, next) {
5657
5818
  toolUsePromptTokens: next.toolUsePromptTokens ?? current.toolUsePromptTokens
5658
5819
  };
5659
5820
  }
5821
+ function sumUsageValue(current, next) {
5822
+ if (typeof next !== "number" || !Number.isFinite(next)) {
5823
+ return current;
5824
+ }
5825
+ const normalizedNext = Math.max(0, next);
5826
+ if (typeof current !== "number" || !Number.isFinite(current)) {
5827
+ return normalizedNext;
5828
+ }
5829
+ return Math.max(0, current) + normalizedNext;
5830
+ }
5831
+ function sumUsageTokens(current, next) {
5832
+ if (!next) {
5833
+ return current;
5834
+ }
5835
+ return {
5836
+ promptTokens: sumUsageValue(current?.promptTokens, next.promptTokens),
5837
+ cachedTokens: sumUsageValue(current?.cachedTokens, next.cachedTokens),
5838
+ responseTokens: sumUsageValue(current?.responseTokens, next.responseTokens),
5839
+ responseImageTokens: sumUsageValue(current?.responseImageTokens, next.responseImageTokens),
5840
+ thinkingTokens: sumUsageValue(current?.thinkingTokens, next.thinkingTokens),
5841
+ totalTokens: sumUsageValue(current?.totalTokens, next.totalTokens),
5842
+ toolUsePromptTokens: sumUsageValue(current?.toolUsePromptTokens, next.toolUsePromptTokens)
5843
+ };
5844
+ }
5845
+ function countInlineImagesInContent(content) {
5846
+ if (!content) {
5847
+ return 0;
5848
+ }
5849
+ let count = 0;
5850
+ for (const part of content.parts) {
5851
+ if (part.type === "inlineData" && isInlineImageMime(part.mimeType)) {
5852
+ count += 1;
5853
+ }
5854
+ }
5855
+ return count;
5856
+ }
5857
+ function createLlmTelemetryEmitter(params) {
5858
+ const session = createTelemetrySession(params.telemetry);
5859
+ const callId = (0, import_node_crypto2.randomBytes)(8).toString("hex");
5860
+ return {
5861
+ includeStreamEvents: session?.includeStreamEvents === true,
5862
+ emit: (event) => {
5863
+ if (!session) {
5864
+ return;
5865
+ }
5866
+ session.emit({
5867
+ ...event,
5868
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5869
+ callId,
5870
+ operation: params.operation,
5871
+ provider: params.provider,
5872
+ model: params.model
5873
+ });
5874
+ },
5875
+ flush: async () => {
5876
+ await session?.flush();
5877
+ }
5878
+ };
5879
+ }
5660
5880
  function toMaybeNumber(value) {
5661
5881
  if (typeof value === "number" && Number.isFinite(value)) {
5662
5882
  return value;
@@ -6134,8 +6354,7 @@ function buildGeminiToolOutputMediaPart(item) {
6134
6354
  return {
6135
6355
  fileData: {
6136
6356
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6137
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6138
- displayName: item.filename ?? void 0
6357
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6139
6358
  }
6140
6359
  };
6141
6360
  }
@@ -6154,8 +6373,7 @@ function buildGeminiToolOutputMediaPart(item) {
6154
6373
  return {
6155
6374
  fileData: {
6156
6375
  fileUri: item.image_url,
6157
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6158
- displayName: item.filename ?? void 0
6376
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6159
6377
  }
6160
6378
  };
6161
6379
  }
@@ -6164,8 +6382,7 @@ function buildGeminiToolOutputMediaPart(item) {
6164
6382
  return {
6165
6383
  fileData: {
6166
6384
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6167
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6168
- displayName: item.filename ?? void 0
6385
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6169
6386
  }
6170
6387
  };
6171
6388
  }
@@ -6188,12 +6405,7 @@ function buildGeminiToolOutputMediaPart(item) {
6188
6405
  return part;
6189
6406
  }
6190
6407
  if (typeof item.file_url === "string" && item.file_url.trim().length > 0 && inferredMimeType) {
6191
- const part = (0, import_genai2.createPartFromUri)(item.file_url, inferredMimeType);
6192
- const displayName = item.filename?.trim();
6193
- if (displayName && part.fileData) {
6194
- part.fileData.displayName = displayName;
6195
- }
6196
- return part;
6408
+ return (0, import_genai2.createPartFromUri)(item.file_url, inferredMimeType);
6197
6409
  }
6198
6410
  }
6199
6411
  return null;
@@ -7074,6 +7286,10 @@ async function runTextCall(params) {
7074
7286
  let responseRole;
7075
7287
  let latestUsage;
7076
7288
  let responseImages = 0;
7289
+ const pushEvent = (event) => {
7290
+ queue.push(event);
7291
+ params.onEvent?.(event);
7292
+ };
7077
7293
  const pushDelta = (channel, text) => {
7078
7294
  if (!text) {
7079
7295
  return;
@@ -7084,7 +7300,7 @@ async function runTextCall(params) {
7084
7300
  } else {
7085
7301
  callLogger?.appendResponseDelta(text);
7086
7302
  }
7087
- queue.push({ type: "delta", channel, text });
7303
+ pushEvent({ type: "delta", channel, text });
7088
7304
  };
7089
7305
  const pushInline = (data, mimeType) => {
7090
7306
  if (!data) {
@@ -7154,7 +7370,7 @@ async function runTextCall(params) {
7154
7370
  }
7155
7371
  case "response.refusal.delta": {
7156
7372
  blocked = true;
7157
- queue.push({ type: "blocked" });
7373
+ pushEvent({ type: "blocked" });
7158
7374
  break;
7159
7375
  }
7160
7376
  default:
@@ -7163,7 +7379,7 @@ async function runTextCall(params) {
7163
7379
  }
7164
7380
  const finalResponse = await stream.finalResponse();
7165
7381
  modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
7166
- queue.push({ type: "model", modelVersion });
7382
+ pushEvent({ type: "model", modelVersion });
7167
7383
  if (finalResponse.error) {
7168
7384
  const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
7169
7385
  throw new Error(message);
@@ -7227,11 +7443,11 @@ async function runTextCall(params) {
7227
7443
  });
7228
7444
  blocked = blocked || result2.blocked;
7229
7445
  if (blocked) {
7230
- queue.push({ type: "blocked" });
7446
+ pushEvent({ type: "blocked" });
7231
7447
  }
7232
7448
  if (result2.model) {
7233
7449
  modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result2.model}`;
7234
- queue.push({ type: "model", modelVersion });
7450
+ pushEvent({ type: "model", modelVersion });
7235
7451
  }
7236
7452
  latestUsage = extractChatGptUsageTokens(result2.usage);
7237
7453
  const fallbackText = typeof result2.text === "string" ? result2.text : "";
@@ -7269,11 +7485,11 @@ async function runTextCall(params) {
7269
7485
  { signal }
7270
7486
  );
7271
7487
  modelVersion = typeof response.model === "string" ? response.model : request.model;
7272
- queue.push({ type: "model", modelVersion });
7488
+ pushEvent({ type: "model", modelVersion });
7273
7489
  const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
7274
7490
  if (choice?.finish_reason === "content_filter") {
7275
7491
  blocked = true;
7276
- queue.push({ type: "blocked" });
7492
+ pushEvent({ type: "blocked" });
7277
7493
  }
7278
7494
  const textOutput = extractFireworksMessageText(
7279
7495
  choice?.message
@@ -7315,11 +7531,11 @@ async function runTextCall(params) {
7315
7531
  for await (const chunk of stream) {
7316
7532
  if (chunk.modelVersion) {
7317
7533
  modelVersion = chunk.modelVersion;
7318
- queue.push({ type: "model", modelVersion });
7534
+ pushEvent({ type: "model", modelVersion });
7319
7535
  }
7320
7536
  if (chunk.promptFeedback?.blockReason) {
7321
7537
  blocked = true;
7322
- queue.push({ type: "blocked" });
7538
+ pushEvent({ type: "blocked" });
7323
7539
  }
7324
7540
  latestUsage = mergeTokenUpdates(
7325
7541
  latestUsage,
@@ -7332,7 +7548,7 @@ async function runTextCall(params) {
7332
7548
  const primary = candidates[0];
7333
7549
  if (primary && isModerationFinish(primary.finishReason)) {
7334
7550
  blocked = true;
7335
- queue.push({ type: "blocked" });
7551
+ pushEvent({ type: "blocked" });
7336
7552
  }
7337
7553
  for (const candidate of candidates) {
7338
7554
  const candidateContent = candidate.content;
@@ -7369,7 +7585,7 @@ async function runTextCall(params) {
7369
7585
  imageSize: request.imageSize
7370
7586
  });
7371
7587
  if (latestUsage) {
7372
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7588
+ pushEvent({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7373
7589
  }
7374
7590
  callLogger?.complete({
7375
7591
  responseText: text,
@@ -7423,18 +7639,76 @@ async function runTextCall(params) {
7423
7639
  });
7424
7640
  return result;
7425
7641
  }
7426
- function streamText(request) {
7642
+ function startTextStream(request, operation) {
7427
7643
  const queue = createAsyncQueue();
7428
7644
  const abortController = new AbortController();
7645
+ const provider = resolveProvider(request.model).provider;
7646
+ const telemetry = createLlmTelemetryEmitter({
7647
+ telemetry: request.telemetry,
7648
+ operation,
7649
+ provider,
7650
+ model: request.model
7651
+ });
7652
+ const startedAtMs = Date.now();
7653
+ telemetry.emit({
7654
+ type: "llm.call.started",
7655
+ inputMode: typeof request.input === "string" ? "string" : "messages",
7656
+ toolCount: request.tools?.length ?? 0,
7657
+ responseModalities: request.responseModalities
7658
+ });
7429
7659
  const result = (async () => {
7660
+ let uploadMetrics = emptyFileUploadMetrics();
7430
7661
  try {
7431
- const output = await runTextCall({ request, queue, abortController });
7662
+ let output;
7663
+ await collectFileUploadMetrics(async () => {
7664
+ try {
7665
+ output = await runTextCall({
7666
+ request,
7667
+ queue,
7668
+ abortController,
7669
+ onEvent: telemetry.includeStreamEvents ? (event) => {
7670
+ telemetry.emit({ type: "llm.call.stream", event });
7671
+ } : void 0
7672
+ });
7673
+ } finally {
7674
+ uploadMetrics = getCurrentFileUploadMetrics();
7675
+ }
7676
+ });
7677
+ if (!output) {
7678
+ throw new Error("LLM text call returned no result.");
7679
+ }
7680
+ telemetry.emit({
7681
+ type: "llm.call.completed",
7682
+ success: true,
7683
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7684
+ modelVersion: output.modelVersion,
7685
+ blocked: output.blocked,
7686
+ usage: output.usage,
7687
+ costUsd: output.costUsd,
7688
+ outputTextChars: output.text.length,
7689
+ thoughtChars: output.thoughts.length,
7690
+ responseImages: countInlineImagesInContent(output.content),
7691
+ uploadCount: uploadMetrics.count,
7692
+ uploadBytes: uploadMetrics.totalBytes,
7693
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
7694
+ });
7432
7695
  queue.close();
7433
7696
  return output;
7434
7697
  } catch (error) {
7435
7698
  const err = error instanceof Error ? error : new Error(String(error));
7699
+ telemetry.emit({
7700
+ type: "llm.call.completed",
7701
+ success: false,
7702
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7703
+ uploadCount: uploadMetrics.count,
7704
+ uploadBytes: uploadMetrics.totalBytes,
7705
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
7706
+ error: err.message
7707
+ });
7436
7708
  queue.fail(err);
7437
7709
  throw err;
7710
+ } finally {
7711
+ await telemetry.flush();
7438
7712
  }
7439
7713
  })();
7440
7714
  return {
@@ -7443,8 +7717,11 @@ function streamText(request) {
7443
7717
  abort: () => abortController.abort()
7444
7718
  };
7445
7719
  }
7720
+ function streamText(request) {
7721
+ return startTextStream(request, "streamText");
7722
+ }
7446
7723
  async function generateText(request) {
7447
- const call = streamText(request);
7724
+ const call = startTextStream(request, "generateText");
7448
7725
  for await (const _event of call.events) {
7449
7726
  }
7450
7727
  return await call.result;
@@ -7470,9 +7747,26 @@ function buildJsonSchemaConfig(request) {
7470
7747
  } : void 0;
7471
7748
  return { providerInfo, responseJsonSchema, openAiTextFormat };
7472
7749
  }
7473
- function streamJson(request) {
7750
+ function startJsonStream(request, operation) {
7474
7751
  const queue = createAsyncQueue();
7475
7752
  const abortController = new AbortController();
7753
+ const provider = resolveProvider(request.model).provider;
7754
+ const telemetry = createLlmTelemetryEmitter({
7755
+ telemetry: request.telemetry,
7756
+ operation,
7757
+ provider,
7758
+ model: request.model
7759
+ });
7760
+ const startedAtMs = Date.now();
7761
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7762
+ const streamMode = request.streamMode ?? "partial";
7763
+ telemetry.emit({
7764
+ type: "llm.call.started",
7765
+ inputMode: typeof request.input === "string" ? "string" : "messages",
7766
+ toolCount: request.tools?.length ?? 0,
7767
+ maxAttempts,
7768
+ streamMode
7769
+ });
7476
7770
  const resolveAbortSignal = () => {
7477
7771
  if (!request.signal) {
7478
7772
  return abortController.signal;
@@ -7491,135 +7785,155 @@ function streamJson(request) {
7491
7785
  return abortController.signal;
7492
7786
  };
7493
7787
  const result = (async () => {
7494
- const signal = resolveAbortSignal();
7495
- const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7496
- const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7497
- const streamMode = request.streamMode ?? "partial";
7498
- const failures = [];
7499
- let openAiTextFormatForAttempt = openAiTextFormat;
7500
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7501
- let rawText = "";
7502
- let lastPartial = "";
7503
- try {
7504
- const call = streamText({
7505
- model: request.model,
7506
- input: request.input,
7507
- instructions: request.instructions,
7508
- tools: request.tools,
7509
- responseMimeType: request.responseMimeType ?? "application/json",
7510
- responseJsonSchema,
7511
- thinkingLevel: request.thinkingLevel,
7512
- ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7513
- signal
7514
- });
7788
+ let uploadMetrics = emptyFileUploadMetrics();
7789
+ let attemptsUsed = 0;
7790
+ try {
7791
+ let output;
7792
+ await collectFileUploadMetrics(async () => {
7515
7793
  try {
7516
- for await (const event of call.events) {
7517
- queue.push(event);
7518
- if (event.type === "delta" && event.channel === "response") {
7519
- rawText += event.text;
7520
- if (streamMode === "partial") {
7521
- const partial = parsePartialJsonFromLlmText(rawText);
7522
- if (partial !== null) {
7523
- const serialized = JSON.stringify(partial);
7524
- if (serialized !== lastPartial) {
7525
- lastPartial = serialized;
7526
- queue.push({
7527
- type: "json",
7528
- stage: "partial",
7529
- value: partial
7530
- });
7794
+ const signal = resolveAbortSignal();
7795
+ const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7796
+ const failures = [];
7797
+ let openAiTextFormatForAttempt = openAiTextFormat;
7798
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7799
+ attemptsUsed = attempt;
7800
+ let rawText = "";
7801
+ let lastPartial = "";
7802
+ try {
7803
+ const call = streamText({
7804
+ model: request.model,
7805
+ input: request.input,
7806
+ instructions: request.instructions,
7807
+ tools: request.tools,
7808
+ responseMimeType: request.responseMimeType ?? "application/json",
7809
+ responseJsonSchema,
7810
+ thinkingLevel: request.thinkingLevel,
7811
+ ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7812
+ telemetry: false,
7813
+ signal
7814
+ });
7815
+ try {
7816
+ for await (const event of call.events) {
7817
+ queue.push(event);
7818
+ if (telemetry.includeStreamEvents) {
7819
+ telemetry.emit({ type: "llm.call.stream", event });
7820
+ }
7821
+ if (event.type === "delta" && event.channel === "response") {
7822
+ rawText += event.text;
7823
+ if (streamMode === "partial") {
7824
+ const partial = parsePartialJsonFromLlmText(rawText);
7825
+ if (partial !== null) {
7826
+ const serialized = JSON.stringify(partial);
7827
+ if (serialized !== lastPartial) {
7828
+ lastPartial = serialized;
7829
+ queue.push({
7830
+ type: "json",
7831
+ stage: "partial",
7832
+ value: partial
7833
+ });
7834
+ }
7835
+ }
7836
+ }
7531
7837
  }
7532
7838
  }
7839
+ } catch (streamError) {
7840
+ await call.result.catch(() => void 0);
7841
+ throw streamError;
7842
+ }
7843
+ const result2 = await call.result;
7844
+ rawText = rawText || result2.text;
7845
+ const cleanedText = normalizeJsonText(rawText);
7846
+ const repairedText = escapeNewlinesInStrings(cleanedText);
7847
+ const payload = JSON.parse(repairedText);
7848
+ const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7849
+ const parsed = request.schema.parse(normalized);
7850
+ queue.push({ type: "json", stage: "final", value: parsed });
7851
+ output = { value: parsed, rawText, result: result2 };
7852
+ return;
7853
+ } catch (error) {
7854
+ const handled = error instanceof Error ? error : new Error(String(error));
7855
+ failures.push({ attempt, rawText, error: handled });
7856
+ if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7857
+ openAiTextFormatForAttempt = void 0;
7858
+ }
7859
+ if (attempt >= maxAttempts) {
7860
+ throw new LlmJsonCallError(
7861
+ `LLM JSON call failed after ${attempt} attempt(s)`,
7862
+ failures
7863
+ );
7533
7864
  }
7534
7865
  }
7535
7866
  }
7536
- } catch (streamError) {
7537
- await call.result.catch(() => void 0);
7538
- throw streamError;
7539
- }
7540
- const result2 = await call.result;
7541
- rawText = rawText || result2.text;
7542
- const cleanedText = normalizeJsonText(rawText);
7543
- const repairedText = escapeNewlinesInStrings(cleanedText);
7544
- const payload = JSON.parse(repairedText);
7545
- const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7546
- const parsed = request.schema.parse(normalized);
7547
- queue.push({ type: "json", stage: "final", value: parsed });
7548
- queue.close();
7549
- return { value: parsed, rawText, result: result2 };
7550
- } catch (error) {
7551
- const handled = error instanceof Error ? error : new Error(String(error));
7552
- failures.push({ attempt, rawText, error: handled });
7553
- if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7554
- openAiTextFormatForAttempt = void 0;
7555
- }
7556
- if (attempt >= maxAttempts) {
7557
- throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
7867
+ throw new LlmJsonCallError("LLM JSON call failed", failures);
7868
+ } finally {
7869
+ uploadMetrics = getCurrentFileUploadMetrics();
7558
7870
  }
7559
- }
7871
+ });
7872
+ if (!output) {
7873
+ throw new Error("LLM JSON call returned no result.");
7874
+ }
7875
+ telemetry.emit({
7876
+ type: "llm.call.completed",
7877
+ success: true,
7878
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7879
+ modelVersion: output.result.modelVersion,
7880
+ blocked: output.result.blocked,
7881
+ usage: output.result.usage,
7882
+ costUsd: output.result.costUsd,
7883
+ rawTextChars: output.rawText.length,
7884
+ attempts: attemptsUsed,
7885
+ uploadCount: uploadMetrics.count,
7886
+ uploadBytes: uploadMetrics.totalBytes,
7887
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
7888
+ });
7889
+ queue.close();
7890
+ return output;
7891
+ } catch (error) {
7892
+ const err = error instanceof Error ? error : new Error(String(error));
7893
+ telemetry.emit({
7894
+ type: "llm.call.completed",
7895
+ success: false,
7896
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7897
+ attempts: attemptsUsed > 0 ? attemptsUsed : void 0,
7898
+ uploadCount: uploadMetrics.count,
7899
+ uploadBytes: uploadMetrics.totalBytes,
7900
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
7901
+ error: err.message
7902
+ });
7903
+ queue.fail(err);
7904
+ throw err;
7905
+ } finally {
7906
+ await telemetry.flush();
7560
7907
  }
7561
- throw new LlmJsonCallError("LLM JSON call failed", failures);
7562
- })().catch((error) => {
7563
- const err = error instanceof Error ? error : new Error(String(error));
7564
- queue.fail(err);
7565
- throw err;
7566
- });
7908
+ })();
7567
7909
  return {
7568
7910
  events: queue.iterable,
7569
7911
  result,
7570
7912
  abort: () => abortController.abort()
7571
7913
  };
7572
7914
  }
7915
+ function streamJson(request) {
7916
+ return startJsonStream(request, "streamJson");
7917
+ }
7573
7918
  async function generateJson(request) {
7574
- const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7575
- const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7576
- let openAiTextFormatForAttempt = openAiTextFormat;
7577
- const failures = [];
7578
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7579
- let rawText = "";
7580
- try {
7581
- const call = streamText({
7582
- model: request.model,
7583
- input: request.input,
7584
- instructions: request.instructions,
7585
- tools: request.tools,
7586
- responseMimeType: request.responseMimeType ?? "application/json",
7587
- responseJsonSchema,
7588
- thinkingLevel: request.thinkingLevel,
7589
- ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7590
- signal: request.signal
7591
- });
7592
- try {
7593
- for await (const event of call.events) {
7594
- request.onEvent?.(event);
7595
- if (event.type === "delta" && event.channel === "response") {
7596
- rawText += event.text;
7597
- }
7598
- }
7599
- } catch (streamError) {
7600
- await call.result.catch(() => void 0);
7601
- throw streamError;
7602
- }
7603
- const result = await call.result;
7604
- rawText = rawText || result.text;
7605
- const cleanedText = normalizeJsonText(rawText);
7606
- const repairedText = escapeNewlinesInStrings(cleanedText);
7607
- const payload = JSON.parse(repairedText);
7608
- const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7609
- const parsed = request.schema.parse(normalized);
7610
- return { value: parsed, rawText, result };
7611
- } catch (error) {
7612
- const handled = error instanceof Error ? error : new Error(String(error));
7613
- failures.push({ attempt, rawText, error: handled });
7614
- if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7615
- openAiTextFormatForAttempt = void 0;
7616
- }
7617
- if (attempt >= maxAttempts) {
7618
- throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
7919
+ const call = startJsonStream(
7920
+ {
7921
+ ...request,
7922
+ streamMode: "final"
7923
+ },
7924
+ "generateJson"
7925
+ );
7926
+ try {
7927
+ for await (const event of call.events) {
7928
+ if (event.type !== "json") {
7929
+ request.onEvent?.(event);
7619
7930
  }
7620
7931
  }
7932
+ } catch (streamError) {
7933
+ await call.result.catch(() => void 0);
7934
+ throw streamError;
7621
7935
  }
7622
- throw new LlmJsonCallError("LLM JSON call failed", failures);
7936
+ return await call.result;
7623
7937
  }
7624
7938
  var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
7625
7939
  function resolveToolLoopContents(input) {
@@ -9235,7 +9549,10 @@ function streamToolLoop(request) {
9235
9549
  abort: () => abortController.abort()
9236
9550
  };
9237
9551
  }
9238
- var IMAGE_GRADE_SCHEMA = import_zod3.z.enum(["pass", "fail"]);
9552
+ var IMAGE_GRADE_VALUE_SCHEMA = import_zod3.z.enum(["pass", "fail"]);
9553
+ var IMAGE_GRADE_SCHEMA = import_zod3.z.object({
9554
+ grade: IMAGE_GRADE_VALUE_SCHEMA
9555
+ });
9239
9556
  async function gradeGeneratedImage(params) {
9240
9557
  const parts = [
9241
9558
  {
@@ -9246,7 +9563,7 @@ async function gradeGeneratedImage(params) {
9246
9563
  "Image prompt to grade:",
9247
9564
  params.imagePrompt,
9248
9565
  "",
9249
- 'Respond with the JSON string "pass" or "fail".'
9566
+ 'Respond with JSON like {"grade":"pass"} or {"grade":"fail"}.'
9250
9567
  ].join("\\n")
9251
9568
  },
9252
9569
  {
@@ -9255,12 +9572,13 @@ async function gradeGeneratedImage(params) {
9255
9572
  mimeType: params.image.mimeType ?? "image/png"
9256
9573
  }
9257
9574
  ];
9258
- const { value } = await generateJson({
9575
+ const { value, result } = await generateJson({
9259
9576
  model: params.model,
9260
9577
  input: [{ role: "user", content: parts }],
9261
- schema: IMAGE_GRADE_SCHEMA
9578
+ schema: IMAGE_GRADE_SCHEMA,
9579
+ telemetry: false
9262
9580
  });
9263
- return value;
9581
+ return { grade: value.grade, result };
9264
9582
  }
9265
9583
  async function generateImages(request) {
9266
9584
  const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 4));
@@ -9280,6 +9598,19 @@ async function generateImages(request) {
9280
9598
  if (!gradingPrompt) {
9281
9599
  throw new Error("imageGradingPrompt must be a non-empty string");
9282
9600
  }
9601
+ const telemetry = createLlmTelemetryEmitter({
9602
+ telemetry: request.telemetry,
9603
+ operation: "generateImages",
9604
+ provider: resolveProvider(request.model).provider,
9605
+ model: request.model
9606
+ });
9607
+ const startedAtMs = Date.now();
9608
+ telemetry.emit({
9609
+ type: "llm.call.started",
9610
+ imagePromptCount: promptList.length,
9611
+ styleImageCount: request.styleImages?.length ?? 0,
9612
+ maxAttempts
9613
+ });
9283
9614
  const addText = (parts, text) => {
9284
9615
  const lastPart = parts[parts.length - 1];
9285
9616
  if (lastPart !== void 0 && lastPart.type === "text") {
@@ -9337,6 +9668,9 @@ async function generateImages(request) {
9337
9668
  const inputMessages = [{ role: "user", content: buildInitialPromptParts() }];
9338
9669
  const orderedEntries = [...promptEntries];
9339
9670
  const resolvedImages = /* @__PURE__ */ new Map();
9671
+ let totalCostUsd = 0;
9672
+ let totalUsage;
9673
+ let attemptsUsed = 0;
9340
9674
  const removeResolvedEntries = (resolved) => {
9341
9675
  if (resolved.size === 0) {
9342
9676
  return;
@@ -9351,70 +9685,118 @@ async function generateImages(request) {
9351
9685
  }
9352
9686
  }
9353
9687
  };
9354
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9355
- const result = await generateText({
9356
- model: request.model,
9357
- input: inputMessages,
9358
- responseModalities: ["IMAGE", "TEXT"],
9359
- imageAspectRatio: request.imageAspectRatio,
9360
- imageSize: request.imageSize ?? "2K"
9361
- });
9362
- if (result.blocked || !result.content) {
9363
- continue;
9364
- }
9365
- const images = extractImages(result.content);
9366
- if (images.length > 0 && promptEntries.length > 0) {
9367
- const assignedCount = Math.min(images.length, promptEntries.length);
9368
- const pendingAssignments = promptEntries.slice(0, assignedCount);
9369
- const assignedImages = images.slice(0, assignedCount);
9370
- const gradeResults = await Promise.all(
9371
- pendingAssignments.map(
9372
- (entry, index) => gradeGeneratedImage({
9373
- gradingPrompt,
9374
- imagePrompt: entry.prompt,
9375
- image: (() => {
9376
- const image = assignedImages[index];
9377
- if (!image) {
9378
- throw new Error("Image generation returned fewer images than expected.");
9688
+ let uploadMetrics = emptyFileUploadMetrics();
9689
+ try {
9690
+ await collectFileUploadMetrics(async () => {
9691
+ try {
9692
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9693
+ attemptsUsed = attempt;
9694
+ const result = await generateText({
9695
+ model: request.model,
9696
+ input: inputMessages,
9697
+ responseModalities: ["IMAGE", "TEXT"],
9698
+ imageAspectRatio: request.imageAspectRatio,
9699
+ imageSize: request.imageSize ?? "2K",
9700
+ telemetry: false
9701
+ });
9702
+ totalCostUsd += result.costUsd;
9703
+ totalUsage = sumUsageTokens(totalUsage, result.usage);
9704
+ if (result.blocked || !result.content) {
9705
+ continue;
9706
+ }
9707
+ const images = extractImages(result.content);
9708
+ if (images.length > 0 && promptEntries.length > 0) {
9709
+ const assignedCount = Math.min(images.length, promptEntries.length);
9710
+ const pendingAssignments = promptEntries.slice(0, assignedCount);
9711
+ const assignedImages = images.slice(0, assignedCount);
9712
+ const gradeResults = await Promise.all(
9713
+ pendingAssignments.map(
9714
+ (entry, index) => gradeGeneratedImage({
9715
+ gradingPrompt,
9716
+ imagePrompt: entry.prompt,
9717
+ image: (() => {
9718
+ const image = assignedImages[index];
9719
+ if (!image) {
9720
+ throw new Error("Image generation returned fewer images than expected.");
9721
+ }
9722
+ return image;
9723
+ })(),
9724
+ model: "gpt-5.2"
9725
+ })
9726
+ )
9727
+ );
9728
+ const passedEntries = /* @__PURE__ */ new Set();
9729
+ for (let i = 0; i < gradeResults.length; i += 1) {
9730
+ const gradeResult = gradeResults[i];
9731
+ const entry = pendingAssignments[i];
9732
+ const image = assignedImages[i];
9733
+ if (!gradeResult || !entry || !image) {
9734
+ continue;
9379
9735
  }
9380
- return image;
9381
- })(),
9382
- model: "gpt-5.2"
9383
- })
9384
- )
9385
- );
9386
- const passedEntries = /* @__PURE__ */ new Set();
9387
- for (let i = 0; i < gradeResults.length; i += 1) {
9388
- const grade = gradeResults[i];
9389
- const entry = pendingAssignments[i];
9390
- const image = assignedImages[i];
9391
- if (!grade || !entry || !image) {
9392
- continue;
9393
- }
9394
- if (grade === "pass") {
9395
- resolvedImages.set(entry.index, image);
9396
- passedEntries.add(entry.index);
9736
+ totalCostUsd += gradeResult.result.costUsd;
9737
+ totalUsage = sumUsageTokens(totalUsage, gradeResult.result.usage);
9738
+ if (gradeResult.grade === "pass") {
9739
+ resolvedImages.set(entry.index, image);
9740
+ passedEntries.add(entry.index);
9741
+ }
9742
+ }
9743
+ removeResolvedEntries(passedEntries);
9744
+ }
9745
+ if (promptEntries.length === 0) {
9746
+ break;
9747
+ }
9748
+ inputMessages.push({
9749
+ role: "assistant",
9750
+ content: result.content.parts
9751
+ });
9752
+ inputMessages.push({
9753
+ role: "user",
9754
+ content: buildContinuationPromptParts(promptEntries)
9755
+ });
9397
9756
  }
9757
+ } finally {
9758
+ uploadMetrics = getCurrentFileUploadMetrics();
9398
9759
  }
9399
- removeResolvedEntries(passedEntries);
9400
- }
9401
- if (promptEntries.length === 0) {
9402
- break;
9403
- }
9404
- inputMessages.push({
9405
- role: "assistant",
9406
- content: result.content.parts
9407
9760
  });
9408
- inputMessages.push({ role: "user", content: buildContinuationPromptParts(promptEntries) });
9409
- }
9410
- const orderedImages = [];
9411
- for (const entry of orderedEntries) {
9412
- const image = resolvedImages.get(entry.index);
9413
- if (image) {
9414
- orderedImages.push(image);
9761
+ const orderedImages = [];
9762
+ for (const entry of orderedEntries) {
9763
+ const image = resolvedImages.get(entry.index);
9764
+ if (image) {
9765
+ orderedImages.push(image);
9766
+ }
9415
9767
  }
9768
+ const outputImages = orderedImages.slice(0, numImages);
9769
+ telemetry.emit({
9770
+ type: "llm.call.completed",
9771
+ success: true,
9772
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9773
+ usage: totalUsage,
9774
+ costUsd: totalCostUsd,
9775
+ imageCount: outputImages.length,
9776
+ attempts: attemptsUsed,
9777
+ uploadCount: uploadMetrics.count,
9778
+ uploadBytes: uploadMetrics.totalBytes,
9779
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
9780
+ });
9781
+ return outputImages;
9782
+ } catch (error) {
9783
+ const err = error instanceof Error ? error : new Error(String(error));
9784
+ telemetry.emit({
9785
+ type: "llm.call.completed",
9786
+ success: false,
9787
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9788
+ usage: totalUsage,
9789
+ costUsd: totalCostUsd,
9790
+ attempts: attemptsUsed > 0 ? attemptsUsed : void 0,
9791
+ uploadCount: uploadMetrics.count,
9792
+ uploadBytes: uploadMetrics.totalBytes,
9793
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
9794
+ error: err.message
9795
+ });
9796
+ throw err;
9797
+ } finally {
9798
+ await telemetry.flush();
9416
9799
  }
9417
- return orderedImages.slice(0, numImages);
9418
9800
  }
9419
9801
  async function generateImageInBatches(request) {
9420
9802
  const {
@@ -12065,7 +12447,7 @@ function isNoEntError(error) {
12065
12447
 
12066
12448
  // src/agent.ts
12067
12449
  async function runAgentLoop(request) {
12068
- const telemetry = createAgentTelemetrySession(request.telemetry);
12450
+ const telemetry = createTelemetrySession(request.telemetry);
12069
12451
  const logging = createRootAgentLoggingSession(request);
12070
12452
  try {
12071
12453
  return await runWithAgentLoggingSession(logging, async () => {
@@ -12151,7 +12533,7 @@ async function runAgentLoopInternal(request, context) {
12151
12533
  logging: _logging,
12152
12534
  ...toolLoopRequest
12153
12535
  } = request;
12154
- const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
12536
+ const telemetrySession = context.telemetry ?? createTelemetrySession(telemetry);
12155
12537
  const loggingSession = context.logging;
12156
12538
  const runId = randomRunId();
12157
12539
  const startedAtMs = Date.now();
@@ -12214,15 +12596,15 @@ async function runAgentLoopInternal(request, context) {
12214
12596
  ].join(" ")
12215
12597
  );
12216
12598
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
12217
- const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
12599
+ const includeStreamEvents = telemetrySession?.includeStreamEvents === true;
12218
12600
  const streamEventLogger = loggingSession ? createAgentStreamEventLogger({
12219
12601
  append: (line) => {
12220
12602
  loggingSession.logLine(`[agent:${runId}] ${line}`);
12221
12603
  }
12222
12604
  }) : void 0;
12223
- const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
12605
+ const wrappedOnEvent = sourceOnEvent || includeStreamEvents ? (event) => {
12224
12606
  sourceOnEvent?.(event);
12225
- if (includeLlmStreamEvents) {
12607
+ if (includeStreamEvents) {
12226
12608
  emitTelemetry({ type: "agent.run.stream", event });
12227
12609
  }
12228
12610
  streamEventLogger?.appendEvent(event);
@@ -12460,7 +12842,7 @@ function countToolCalls(result) {
12460
12842
  }
12461
12843
  return count;
12462
12844
  }
12463
- function sumUsageValue(current, next) {
12845
+ function sumUsageValue2(current, next) {
12464
12846
  if (typeof next !== "number" || !Number.isFinite(next)) {
12465
12847
  return current;
12466
12848
  }
@@ -12478,20 +12860,17 @@ function summarizeResultUsage(result) {
12478
12860
  continue;
12479
12861
  }
12480
12862
  summary = {
12481
- promptTokens: sumUsageValue(summary?.promptTokens, usage.promptTokens),
12482
- cachedTokens: sumUsageValue(summary?.cachedTokens, usage.cachedTokens),
12483
- responseTokens: sumUsageValue(summary?.responseTokens, usage.responseTokens),
12484
- responseImageTokens: sumUsageValue(summary?.responseImageTokens, usage.responseImageTokens),
12485
- thinkingTokens: sumUsageValue(summary?.thinkingTokens, usage.thinkingTokens),
12486
- totalTokens: sumUsageValue(summary?.totalTokens, usage.totalTokens),
12487
- toolUsePromptTokens: sumUsageValue(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
12863
+ promptTokens: sumUsageValue2(summary?.promptTokens, usage.promptTokens),
12864
+ cachedTokens: sumUsageValue2(summary?.cachedTokens, usage.cachedTokens),
12865
+ responseTokens: sumUsageValue2(summary?.responseTokens, usage.responseTokens),
12866
+ responseImageTokens: sumUsageValue2(summary?.responseImageTokens, usage.responseImageTokens),
12867
+ thinkingTokens: sumUsageValue2(summary?.thinkingTokens, usage.thinkingTokens),
12868
+ totalTokens: sumUsageValue2(summary?.totalTokens, usage.totalTokens),
12869
+ toolUsePromptTokens: sumUsageValue2(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
12488
12870
  };
12489
12871
  }
12490
12872
  return summary;
12491
12873
  }
12492
- function isPromiseLike2(value) {
12493
- return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
12494
- }
12495
12874
  function resolveAgentLoggingSelection(value) {
12496
12875
  if (value === false) {
12497
12876
  return void 0;
@@ -12525,60 +12904,6 @@ function createRootAgentLoggingSession(request) {
12525
12904
  mirrorToConsole: selected.mirrorToConsole !== false
12526
12905
  });
12527
12906
  }
12528
- function isAgentTelemetrySink(value) {
12529
- return typeof value === "object" && value !== null && typeof value.emit === "function";
12530
- }
12531
- function resolveTelemetrySelection(telemetry) {
12532
- if (!telemetry) {
12533
- return void 0;
12534
- }
12535
- if (isAgentTelemetrySink(telemetry)) {
12536
- return { sink: telemetry };
12537
- }
12538
- if (isAgentTelemetrySink(telemetry.sink)) {
12539
- return telemetry;
12540
- }
12541
- throw new Error("Invalid runAgentLoop telemetry config: expected a sink with emit(event).");
12542
- }
12543
- function createAgentTelemetrySession(telemetry) {
12544
- const config = resolveTelemetrySelection(telemetry);
12545
- if (!config) {
12546
- return void 0;
12547
- }
12548
- const pending = /* @__PURE__ */ new Set();
12549
- const trackPromise = (promise) => {
12550
- pending.add(promise);
12551
- promise.finally(() => {
12552
- pending.delete(promise);
12553
- });
12554
- };
12555
- const emit = (event) => {
12556
- try {
12557
- const output = config.sink.emit(event);
12558
- if (isPromiseLike2(output)) {
12559
- const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
12560
- trackPromise(task);
12561
- }
12562
- } catch {
12563
- }
12564
- };
12565
- const flush = async () => {
12566
- while (pending.size > 0) {
12567
- await Promise.allSettled([...pending]);
12568
- }
12569
- if (typeof config.sink.flush === "function") {
12570
- try {
12571
- await config.sink.flush();
12572
- } catch {
12573
- }
12574
- }
12575
- };
12576
- return {
12577
- includeLlmStreamEvents: config.includeLlmStreamEvents === true,
12578
- emit,
12579
- flush
12580
- };
12581
- }
12582
12907
  function createAgentTelemetryEmitter(params) {
12583
12908
  return (event) => {
12584
12909
  if (!params.session) {
@@ -13272,6 +13597,7 @@ async function runCandidateEvolution(options) {
13272
13597
  applyPatch,
13273
13598
  configureGemini,
13274
13599
  configureModelConcurrency,
13600
+ configureTelemetry,
13275
13601
  convertGooglePartsToLlmParts,
13276
13602
  createApplyPatchTool,
13277
13603
  createCodexApplyPatchTool,
@@ -13320,6 +13646,7 @@ async function runCandidateEvolution(options) {
13320
13646
  parseJsonFromLlmText,
13321
13647
  refreshChatGptOauthToken,
13322
13648
  resetModelConcurrencyConfig,
13649
+ resetTelemetry,
13323
13650
  resolveFilesystemToolProfile,
13324
13651
  resolveFireworksModelId,
13325
13652
  runAgentLoop,