@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.js CHANGED
@@ -2533,7 +2533,7 @@ async function runGeminiCall(fn, modelId, runOptions) {
2533
2533
 
2534
2534
  // src/openai/client.ts
2535
2535
  import OpenAI2 from "openai";
2536
- import { Agent as Agent2, fetch as undiciFetch2 } from "undici";
2536
+ import { Agent as Agent2 } from "undici";
2537
2537
  var openAiClientState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.openAiClientState"), () => ({
2538
2538
  cachedApiKey: null,
2539
2539
  cachedClient: null,
@@ -2562,7 +2562,7 @@ function getOpenAiFetch() {
2562
2562
  headersTimeout: timeoutMs
2563
2563
  });
2564
2564
  openAiClientState.cachedFetch = ((input, init) => {
2565
- return undiciFetch2(input, {
2565
+ return fetch(input, {
2566
2566
  ...init ?? {},
2567
2567
  dispatcher
2568
2568
  });
@@ -3259,7 +3259,7 @@ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
3259
3259
  import { Buffer as Buffer4, File as NodeFile } from "buffer";
3260
3260
  import { createHash } from "crypto";
3261
3261
  import { createReadStream, createWriteStream, openAsBlob } from "fs";
3262
- import { mkdir as mkdir2, mkdtemp, stat, unlink, writeFile as writeFile2 } from "fs/promises";
3262
+ import { copyFile, mkdir as mkdir2, mkdtemp, readFile, stat, unlink, writeFile as writeFile2 } from "fs/promises";
3263
3263
  import os3 from "os";
3264
3264
  import path4 from "path";
3265
3265
  import { Readable } from "stream";
@@ -3272,6 +3272,9 @@ var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
3272
3272
  var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
3273
3273
  var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
3274
3274
  var FILES_TEMP_ROOT = path4.join(os3.tmpdir(), "ljoukov-llm-files");
3275
+ var FILES_CACHE_ROOT = path4.join(FILES_TEMP_ROOT, "cache");
3276
+ var FILES_CACHE_CONTENT_ROOT = path4.join(FILES_CACHE_ROOT, "content");
3277
+ var FILES_CACHE_METADATA_ROOT = path4.join(FILES_CACHE_ROOT, "metadata");
3275
3278
  var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
3276
3279
  metadataById: /* @__PURE__ */ new Map(),
3277
3280
  openAiUploadCacheByKey: /* @__PURE__ */ new Map(),
@@ -3417,6 +3420,12 @@ function toStoredFile(file) {
3417
3420
  function buildCacheKey(filename, mimeType, sha256Hex) {
3418
3421
  return `${sha256Hex}\0${filename}\0${mimeType}`;
3419
3422
  }
3423
+ function buildCachedContentPath(sha256Hex) {
3424
+ return path4.join(FILES_CACHE_CONTENT_ROOT, sha256Hex);
3425
+ }
3426
+ function buildCachedMetadataPath(fileId) {
3427
+ return path4.join(FILES_CACHE_METADATA_ROOT, `${fileId}.json`);
3428
+ }
3420
3429
  function isFresh(file) {
3421
3430
  if (!file.expires_at) {
3422
3431
  return true;
@@ -3437,6 +3446,82 @@ function recordMetadata(metadata) {
3437
3446
  }
3438
3447
  return metadata;
3439
3448
  }
3449
+ async function ensureFilesCacheReady() {
3450
+ await mkdir2(FILES_CACHE_CONTENT_ROOT, { recursive: true });
3451
+ await mkdir2(FILES_CACHE_METADATA_ROOT, { recursive: true });
3452
+ }
3453
+ async function cacheBufferLocally(bytes, sha256Hex) {
3454
+ await ensureFilesCacheReady();
3455
+ const localPath = buildCachedContentPath(sha256Hex);
3456
+ try {
3457
+ await writeFile2(localPath, bytes, { flag: "wx" });
3458
+ } catch (error) {
3459
+ const code = error.code;
3460
+ if (code !== "EEXIST") {
3461
+ throw error;
3462
+ }
3463
+ }
3464
+ return localPath;
3465
+ }
3466
+ async function cacheFileLocally(filePath, sha256Hex) {
3467
+ await ensureFilesCacheReady();
3468
+ const localPath = buildCachedContentPath(sha256Hex);
3469
+ try {
3470
+ await copyFile(filePath, localPath);
3471
+ } catch (error) {
3472
+ const code = error.code;
3473
+ if (code !== "EEXIST") {
3474
+ throw error;
3475
+ }
3476
+ }
3477
+ return localPath;
3478
+ }
3479
+ async function persistMetadataToDisk(metadata) {
3480
+ await ensureFilesCacheReady();
3481
+ const payload = {
3482
+ file: metadata.file,
3483
+ filename: metadata.filename,
3484
+ bytes: metadata.bytes,
3485
+ mimeType: metadata.mimeType,
3486
+ sha256Hex: metadata.sha256Hex,
3487
+ localPath: metadata.localPath
3488
+ };
3489
+ await writeFile2(
3490
+ buildCachedMetadataPath(metadata.file.id),
3491
+ `${JSON.stringify(payload, null, 2)}
3492
+ `
3493
+ );
3494
+ }
3495
+ async function loadPersistedMetadata(fileId) {
3496
+ try {
3497
+ const payload = JSON.parse(
3498
+ await readFile(buildCachedMetadataPath(fileId), "utf8")
3499
+ );
3500
+ if (!payload || typeof payload !== "object" || !payload.file) {
3501
+ return void 0;
3502
+ }
3503
+ if (payload.localPath) {
3504
+ try {
3505
+ const localStats = await stat(payload.localPath);
3506
+ if (!localStats.isFile()) {
3507
+ return void 0;
3508
+ }
3509
+ } catch {
3510
+ return void 0;
3511
+ }
3512
+ }
3513
+ return recordMetadata({
3514
+ file: payload.file,
3515
+ filename: payload.filename,
3516
+ bytes: payload.bytes,
3517
+ mimeType: payload.mimeType,
3518
+ sha256Hex: payload.sha256Hex,
3519
+ localPath: payload.localPath
3520
+ });
3521
+ } catch {
3522
+ return void 0;
3523
+ }
3524
+ }
3440
3525
  async function uploadOpenAiFileFromBytes(params) {
3441
3526
  const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3442
3527
  const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
@@ -3585,17 +3670,23 @@ async function retrieveOpenAiFile(fileId) {
3585
3670
  if (cached && isFresh(cached.file)) {
3586
3671
  return cached;
3587
3672
  }
3673
+ const persisted = await loadPersistedMetadata(fileId);
3674
+ if (persisted && isFresh(persisted.file)) {
3675
+ return persisted;
3676
+ }
3588
3677
  const client = getOpenAiClient();
3589
3678
  const retrieved = await client.files.retrieve(fileId);
3590
3679
  const file = toStoredFile(retrieved);
3591
- return recordMetadata({
3680
+ const metadata = recordMetadata({
3592
3681
  file,
3593
3682
  filename: file.filename,
3594
3683
  bytes: file.bytes,
3595
- mimeType: cached?.mimeType ?? resolveMimeType(file.filename, void 0),
3596
- sha256Hex: cached?.sha256Hex,
3597
- localPath: cached?.localPath
3684
+ mimeType: cached?.mimeType ?? persisted?.mimeType ?? resolveMimeType(file.filename, void 0),
3685
+ sha256Hex: cached?.sha256Hex ?? persisted?.sha256Hex,
3686
+ localPath: cached?.localPath ?? persisted?.localPath
3598
3687
  });
3688
+ await persistMetadataToDisk(metadata);
3689
+ return metadata;
3599
3690
  }
3600
3691
  function buildGeminiMirrorName(sha256Hex) {
3601
3692
  return `files/${sha256Hex.slice(0, 40)}`;
@@ -3707,6 +3798,7 @@ async function materializeOpenAiFile(fileId) {
3707
3798
  sha256Hex,
3708
3799
  localPath
3709
3800
  });
3801
+ await persistMetadataToDisk(updated);
3710
3802
  return {
3711
3803
  file: updated.file,
3712
3804
  filename: updated.filename,
@@ -3870,7 +3962,13 @@ async function filesCreate(params) {
3870
3962
  sha256Hex: sha256Hex2,
3871
3963
  bytes: info.size
3872
3964
  });
3873
- return uploaded2.file;
3965
+ const localPath2 = await cacheFileLocally(filePath, sha256Hex2);
3966
+ const cached2 = recordMetadata({
3967
+ ...uploaded2,
3968
+ localPath: localPath2
3969
+ });
3970
+ await persistMetadataToDisk(cached2);
3971
+ return cached2.file;
3874
3972
  }
3875
3973
  const filename = normaliseFilename(params.filename);
3876
3974
  const bytes = toBuffer(params.data);
@@ -3884,7 +3982,13 @@ async function filesCreate(params) {
3884
3982
  expiresAfterSeconds,
3885
3983
  sha256Hex
3886
3984
  });
3887
- return uploaded.file;
3985
+ const localPath = await cacheBufferLocally(bytes, sha256Hex);
3986
+ const cached = recordMetadata({
3987
+ ...uploaded,
3988
+ localPath
3989
+ });
3990
+ await persistMetadataToDisk(cached);
3991
+ return cached.file;
3888
3992
  }
3889
3993
  async function filesRetrieve(fileId) {
3890
3994
  return (await retrieveOpenAiFile(fileId)).file;
@@ -3917,6 +4021,10 @@ async function filesDelete(fileId) {
3917
4021
  const response = await getOpenAiClient().files.delete(fileId);
3918
4022
  filesState.metadataById.delete(fileId);
3919
4023
  filesState.materializedById.delete(fileId);
4024
+ try {
4025
+ await unlink(buildCachedMetadataPath(fileId));
4026
+ } catch {
4027
+ }
3920
4028
  return {
3921
4029
  id: response.id,
3922
4030
  deleted: response.deleted,
@@ -3945,6 +4053,71 @@ var files = {
3945
4053
  content: filesContent
3946
4054
  };
3947
4055
 
4056
+ // src/telemetry.ts
4057
+ var telemetryState = getRuntimeSingleton(
4058
+ /* @__PURE__ */ Symbol.for("@ljoukov/llm.telemetryState"),
4059
+ () => ({
4060
+ configuredTelemetry: void 0
4061
+ })
4062
+ );
4063
+ function configureTelemetry(telemetry = void 0) {
4064
+ telemetryState.configuredTelemetry = telemetry === void 0 || telemetry === false ? void 0 : telemetry;
4065
+ }
4066
+ function resetTelemetry() {
4067
+ telemetryState.configuredTelemetry = void 0;
4068
+ }
4069
+ function isPromiseLike2(value) {
4070
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
4071
+ }
4072
+ function resolveTelemetrySelection(telemetry) {
4073
+ if (telemetry === false) {
4074
+ return void 0;
4075
+ }
4076
+ if (telemetry !== void 0) {
4077
+ return telemetry;
4078
+ }
4079
+ return telemetryState.configuredTelemetry;
4080
+ }
4081
+ function createTelemetrySession(telemetry) {
4082
+ const config = resolveTelemetrySelection(telemetry);
4083
+ if (!config) {
4084
+ return void 0;
4085
+ }
4086
+ const pending = /* @__PURE__ */ new Set();
4087
+ const trackPromise = (promise) => {
4088
+ pending.add(promise);
4089
+ promise.finally(() => {
4090
+ pending.delete(promise);
4091
+ });
4092
+ };
4093
+ const emit = (event) => {
4094
+ try {
4095
+ const output = config.sink.emit(event);
4096
+ if (isPromiseLike2(output)) {
4097
+ const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
4098
+ trackPromise(task);
4099
+ }
4100
+ } catch {
4101
+ }
4102
+ };
4103
+ const flush = async () => {
4104
+ while (pending.size > 0) {
4105
+ await Promise.allSettled([...pending]);
4106
+ }
4107
+ if (typeof config.sink.flush === "function") {
4108
+ try {
4109
+ await config.sink.flush();
4110
+ } catch {
4111
+ }
4112
+ }
4113
+ };
4114
+ return {
4115
+ includeStreamEvents: config.includeStreamEvents === true,
4116
+ emit,
4117
+ flush
4118
+ };
4119
+ }
4120
+
3948
4121
  // src/llm.ts
3949
4122
  var toolCallContextStorage = getRuntimeSingleton(
3950
4123
  /* @__PURE__ */ Symbol.for("@ljoukov/llm.toolCallContextStorage"),
@@ -4396,8 +4569,7 @@ function toGeminiPart(part) {
4396
4569
  return {
4397
4570
  fileData: {
4398
4571
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4399
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4400
- displayName: part.filename ?? void 0
4572
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4401
4573
  }
4402
4574
  };
4403
4575
  }
@@ -4415,8 +4587,7 @@ function toGeminiPart(part) {
4415
4587
  return {
4416
4588
  fileData: {
4417
4589
  fileUri: part.image_url,
4418
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4419
- displayName: part.filename ?? void 0
4590
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4420
4591
  }
4421
4592
  };
4422
4593
  }
@@ -4425,8 +4596,7 @@ function toGeminiPart(part) {
4425
4596
  return {
4426
4597
  fileData: {
4427
4598
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4428
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4429
- displayName: part.filename ?? void 0
4599
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4430
4600
  }
4431
4601
  };
4432
4602
  }
@@ -4452,8 +4622,7 @@ function toGeminiPart(part) {
4452
4622
  return {
4453
4623
  fileData: {
4454
4624
  fileUri: part.file_url,
4455
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4456
- displayName: part.filename ?? void 0
4625
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4457
4626
  }
4458
4627
  };
4459
4628
  }
@@ -4685,7 +4854,7 @@ async function prepareOpenAiPromptContentItem(item) {
4685
4854
  mimeType,
4686
4855
  filename
4687
4856
  });
4688
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4857
+ return { type: "input_file", file_id: uploaded.fileId };
4689
4858
  }
4690
4859
  if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4691
4860
  const parsed = parseDataUrlPayload(item.file_url);
@@ -4700,7 +4869,7 @@ async function prepareOpenAiPromptContentItem(item) {
4700
4869
  guessInlineDataFilename(parsed.mimeType)
4701
4870
  )
4702
4871
  });
4703
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4872
+ return { type: "input_file", file_id: uploaded.fileId };
4704
4873
  }
4705
4874
  return item;
4706
4875
  }
@@ -4765,21 +4934,16 @@ async function prepareGeminiPromptContents(contents) {
4765
4934
  for (const part of content.parts ?? []) {
4766
4935
  const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4767
4936
  if (canonicalFileId) {
4768
- const metadata = await getCanonicalFileMetadata(canonicalFileId);
4937
+ await getCanonicalFileMetadata(canonicalFileId);
4769
4938
  if (backend === "api") {
4770
4939
  const mirrored = await ensureGeminiFileMirror(canonicalFileId);
4771
- const mirroredPart = createPartFromUri(mirrored.uri, mirrored.mimeType);
4772
- if (metadata.filename && mirroredPart.fileData) {
4773
- mirroredPart.fileData.displayName = metadata.filename;
4774
- }
4775
- parts.push(mirroredPart);
4940
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
4776
4941
  } else {
4777
4942
  const mirrored = await ensureVertexFileMirror(canonicalFileId);
4778
4943
  parts.push({
4779
4944
  fileData: {
4780
4945
  fileUri: mirrored.fileUri,
4781
- mimeType: mirrored.mimeType,
4782
- displayName: metadata.filename
4946
+ mimeType: mirrored.mimeType
4783
4947
  }
4784
4948
  });
4785
4949
  }
@@ -4798,18 +4962,13 @@ async function prepareGeminiPromptContents(contents) {
4798
4962
  });
4799
4963
  if (backend === "api") {
4800
4964
  const mirrored = await ensureGeminiFileMirror(stored.fileId);
4801
- const mirroredPart = createPartFromUri(mirrored.uri, mirrored.mimeType);
4802
- if (filename && mirroredPart.fileData) {
4803
- mirroredPart.fileData.displayName = filename;
4804
- }
4805
- parts.push(mirroredPart);
4965
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
4806
4966
  } else {
4807
4967
  const mirrored = await ensureVertexFileMirror(stored.fileId);
4808
4968
  parts.push({
4809
4969
  fileData: {
4810
4970
  fileUri: mirrored.fileUri,
4811
- mimeType: mirrored.mimeType,
4812
- displayName: filename
4971
+ mimeType: mirrored.mimeType
4813
4972
  }
4814
4973
  });
4815
4974
  }
@@ -5330,7 +5489,7 @@ function toOpenAiInput(contents) {
5330
5489
  ...part.file_id ? { file_id: part.file_id } : {},
5331
5490
  ...part.file_data ? { file_data: part.file_data } : {},
5332
5491
  ...part.file_url ? { file_url: part.file_url } : {},
5333
- ...part.filename ? { filename: part.filename } : {}
5492
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5334
5493
  });
5335
5494
  break;
5336
5495
  default:
@@ -5415,7 +5574,7 @@ function toChatGptInput(contents) {
5415
5574
  ...part.file_id ? { file_id: part.file_id } : {},
5416
5575
  ...part.file_data ? { file_data: part.file_data } : {},
5417
5576
  ...part.file_url ? { file_url: part.file_url } : {},
5418
- ...part.filename ? { filename: part.filename } : {}
5577
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5419
5578
  });
5420
5579
  break;
5421
5580
  default:
@@ -5545,6 +5704,65 @@ function mergeTokenUpdates(current, next) {
5545
5704
  toolUsePromptTokens: next.toolUsePromptTokens ?? current.toolUsePromptTokens
5546
5705
  };
5547
5706
  }
5707
+ function sumUsageValue(current, next) {
5708
+ if (typeof next !== "number" || !Number.isFinite(next)) {
5709
+ return current;
5710
+ }
5711
+ const normalizedNext = Math.max(0, next);
5712
+ if (typeof current !== "number" || !Number.isFinite(current)) {
5713
+ return normalizedNext;
5714
+ }
5715
+ return Math.max(0, current) + normalizedNext;
5716
+ }
5717
+ function sumUsageTokens(current, next) {
5718
+ if (!next) {
5719
+ return current;
5720
+ }
5721
+ return {
5722
+ promptTokens: sumUsageValue(current?.promptTokens, next.promptTokens),
5723
+ cachedTokens: sumUsageValue(current?.cachedTokens, next.cachedTokens),
5724
+ responseTokens: sumUsageValue(current?.responseTokens, next.responseTokens),
5725
+ responseImageTokens: sumUsageValue(current?.responseImageTokens, next.responseImageTokens),
5726
+ thinkingTokens: sumUsageValue(current?.thinkingTokens, next.thinkingTokens),
5727
+ totalTokens: sumUsageValue(current?.totalTokens, next.totalTokens),
5728
+ toolUsePromptTokens: sumUsageValue(current?.toolUsePromptTokens, next.toolUsePromptTokens)
5729
+ };
5730
+ }
5731
+ function countInlineImagesInContent(content) {
5732
+ if (!content) {
5733
+ return 0;
5734
+ }
5735
+ let count = 0;
5736
+ for (const part of content.parts) {
5737
+ if (part.type === "inlineData" && isInlineImageMime(part.mimeType)) {
5738
+ count += 1;
5739
+ }
5740
+ }
5741
+ return count;
5742
+ }
5743
+ function createLlmTelemetryEmitter(params) {
5744
+ const session = createTelemetrySession(params.telemetry);
5745
+ const callId = randomBytes(8).toString("hex");
5746
+ return {
5747
+ includeStreamEvents: session?.includeStreamEvents === true,
5748
+ emit: (event) => {
5749
+ if (!session) {
5750
+ return;
5751
+ }
5752
+ session.emit({
5753
+ ...event,
5754
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5755
+ callId,
5756
+ operation: params.operation,
5757
+ provider: params.provider,
5758
+ model: params.model
5759
+ });
5760
+ },
5761
+ flush: async () => {
5762
+ await session?.flush();
5763
+ }
5764
+ };
5765
+ }
5548
5766
  function toMaybeNumber(value) {
5549
5767
  if (typeof value === "number" && Number.isFinite(value)) {
5550
5768
  return value;
@@ -6022,8 +6240,7 @@ function buildGeminiToolOutputMediaPart(item) {
6022
6240
  return {
6023
6241
  fileData: {
6024
6242
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6025
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6026
- displayName: item.filename ?? void 0
6243
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6027
6244
  }
6028
6245
  };
6029
6246
  }
@@ -6042,8 +6259,7 @@ function buildGeminiToolOutputMediaPart(item) {
6042
6259
  return {
6043
6260
  fileData: {
6044
6261
  fileUri: item.image_url,
6045
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6046
- displayName: item.filename ?? void 0
6262
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6047
6263
  }
6048
6264
  };
6049
6265
  }
@@ -6052,8 +6268,7 @@ function buildGeminiToolOutputMediaPart(item) {
6052
6268
  return {
6053
6269
  fileData: {
6054
6270
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6055
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6056
- displayName: item.filename ?? void 0
6271
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6057
6272
  }
6058
6273
  };
6059
6274
  }
@@ -6076,12 +6291,7 @@ function buildGeminiToolOutputMediaPart(item) {
6076
6291
  return part;
6077
6292
  }
6078
6293
  if (typeof item.file_url === "string" && item.file_url.trim().length > 0 && inferredMimeType) {
6079
- const part = createPartFromUri(item.file_url, inferredMimeType);
6080
- const displayName = item.filename?.trim();
6081
- if (displayName && part.fileData) {
6082
- part.fileData.displayName = displayName;
6083
- }
6084
- return part;
6294
+ return createPartFromUri(item.file_url, inferredMimeType);
6085
6295
  }
6086
6296
  }
6087
6297
  return null;
@@ -6962,6 +7172,10 @@ async function runTextCall(params) {
6962
7172
  let responseRole;
6963
7173
  let latestUsage;
6964
7174
  let responseImages = 0;
7175
+ const pushEvent = (event) => {
7176
+ queue.push(event);
7177
+ params.onEvent?.(event);
7178
+ };
6965
7179
  const pushDelta = (channel, text) => {
6966
7180
  if (!text) {
6967
7181
  return;
@@ -6972,7 +7186,7 @@ async function runTextCall(params) {
6972
7186
  } else {
6973
7187
  callLogger?.appendResponseDelta(text);
6974
7188
  }
6975
- queue.push({ type: "delta", channel, text });
7189
+ pushEvent({ type: "delta", channel, text });
6976
7190
  };
6977
7191
  const pushInline = (data, mimeType) => {
6978
7192
  if (!data) {
@@ -7042,7 +7256,7 @@ async function runTextCall(params) {
7042
7256
  }
7043
7257
  case "response.refusal.delta": {
7044
7258
  blocked = true;
7045
- queue.push({ type: "blocked" });
7259
+ pushEvent({ type: "blocked" });
7046
7260
  break;
7047
7261
  }
7048
7262
  default:
@@ -7051,7 +7265,7 @@ async function runTextCall(params) {
7051
7265
  }
7052
7266
  const finalResponse = await stream.finalResponse();
7053
7267
  modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
7054
- queue.push({ type: "model", modelVersion });
7268
+ pushEvent({ type: "model", modelVersion });
7055
7269
  if (finalResponse.error) {
7056
7270
  const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
7057
7271
  throw new Error(message);
@@ -7115,11 +7329,11 @@ async function runTextCall(params) {
7115
7329
  });
7116
7330
  blocked = blocked || result2.blocked;
7117
7331
  if (blocked) {
7118
- queue.push({ type: "blocked" });
7332
+ pushEvent({ type: "blocked" });
7119
7333
  }
7120
7334
  if (result2.model) {
7121
7335
  modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result2.model}`;
7122
- queue.push({ type: "model", modelVersion });
7336
+ pushEvent({ type: "model", modelVersion });
7123
7337
  }
7124
7338
  latestUsage = extractChatGptUsageTokens(result2.usage);
7125
7339
  const fallbackText = typeof result2.text === "string" ? result2.text : "";
@@ -7157,11 +7371,11 @@ async function runTextCall(params) {
7157
7371
  { signal }
7158
7372
  );
7159
7373
  modelVersion = typeof response.model === "string" ? response.model : request.model;
7160
- queue.push({ type: "model", modelVersion });
7374
+ pushEvent({ type: "model", modelVersion });
7161
7375
  const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
7162
7376
  if (choice?.finish_reason === "content_filter") {
7163
7377
  blocked = true;
7164
- queue.push({ type: "blocked" });
7378
+ pushEvent({ type: "blocked" });
7165
7379
  }
7166
7380
  const textOutput = extractFireworksMessageText(
7167
7381
  choice?.message
@@ -7203,11 +7417,11 @@ async function runTextCall(params) {
7203
7417
  for await (const chunk of stream) {
7204
7418
  if (chunk.modelVersion) {
7205
7419
  modelVersion = chunk.modelVersion;
7206
- queue.push({ type: "model", modelVersion });
7420
+ pushEvent({ type: "model", modelVersion });
7207
7421
  }
7208
7422
  if (chunk.promptFeedback?.blockReason) {
7209
7423
  blocked = true;
7210
- queue.push({ type: "blocked" });
7424
+ pushEvent({ type: "blocked" });
7211
7425
  }
7212
7426
  latestUsage = mergeTokenUpdates(
7213
7427
  latestUsage,
@@ -7220,7 +7434,7 @@ async function runTextCall(params) {
7220
7434
  const primary = candidates[0];
7221
7435
  if (primary && isModerationFinish(primary.finishReason)) {
7222
7436
  blocked = true;
7223
- queue.push({ type: "blocked" });
7437
+ pushEvent({ type: "blocked" });
7224
7438
  }
7225
7439
  for (const candidate of candidates) {
7226
7440
  const candidateContent = candidate.content;
@@ -7257,7 +7471,7 @@ async function runTextCall(params) {
7257
7471
  imageSize: request.imageSize
7258
7472
  });
7259
7473
  if (latestUsage) {
7260
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7474
+ pushEvent({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7261
7475
  }
7262
7476
  callLogger?.complete({
7263
7477
  responseText: text,
@@ -7311,18 +7525,76 @@ async function runTextCall(params) {
7311
7525
  });
7312
7526
  return result;
7313
7527
  }
7314
- function streamText(request) {
7528
+ function startTextStream(request, operation) {
7315
7529
  const queue = createAsyncQueue();
7316
7530
  const abortController = new AbortController();
7531
+ const provider = resolveProvider(request.model).provider;
7532
+ const telemetry = createLlmTelemetryEmitter({
7533
+ telemetry: request.telemetry,
7534
+ operation,
7535
+ provider,
7536
+ model: request.model
7537
+ });
7538
+ const startedAtMs = Date.now();
7539
+ telemetry.emit({
7540
+ type: "llm.call.started",
7541
+ inputMode: typeof request.input === "string" ? "string" : "messages",
7542
+ toolCount: request.tools?.length ?? 0,
7543
+ responseModalities: request.responseModalities
7544
+ });
7317
7545
  const result = (async () => {
7546
+ let uploadMetrics = emptyFileUploadMetrics();
7318
7547
  try {
7319
- const output = await runTextCall({ request, queue, abortController });
7548
+ let output;
7549
+ await collectFileUploadMetrics(async () => {
7550
+ try {
7551
+ output = await runTextCall({
7552
+ request,
7553
+ queue,
7554
+ abortController,
7555
+ onEvent: telemetry.includeStreamEvents ? (event) => {
7556
+ telemetry.emit({ type: "llm.call.stream", event });
7557
+ } : void 0
7558
+ });
7559
+ } finally {
7560
+ uploadMetrics = getCurrentFileUploadMetrics();
7561
+ }
7562
+ });
7563
+ if (!output) {
7564
+ throw new Error("LLM text call returned no result.");
7565
+ }
7566
+ telemetry.emit({
7567
+ type: "llm.call.completed",
7568
+ success: true,
7569
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7570
+ modelVersion: output.modelVersion,
7571
+ blocked: output.blocked,
7572
+ usage: output.usage,
7573
+ costUsd: output.costUsd,
7574
+ outputTextChars: output.text.length,
7575
+ thoughtChars: output.thoughts.length,
7576
+ responseImages: countInlineImagesInContent(output.content),
7577
+ uploadCount: uploadMetrics.count,
7578
+ uploadBytes: uploadMetrics.totalBytes,
7579
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
7580
+ });
7320
7581
  queue.close();
7321
7582
  return output;
7322
7583
  } catch (error) {
7323
7584
  const err = error instanceof Error ? error : new Error(String(error));
7585
+ telemetry.emit({
7586
+ type: "llm.call.completed",
7587
+ success: false,
7588
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7589
+ uploadCount: uploadMetrics.count,
7590
+ uploadBytes: uploadMetrics.totalBytes,
7591
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
7592
+ error: err.message
7593
+ });
7324
7594
  queue.fail(err);
7325
7595
  throw err;
7596
+ } finally {
7597
+ await telemetry.flush();
7326
7598
  }
7327
7599
  })();
7328
7600
  return {
@@ -7331,8 +7603,11 @@ function streamText(request) {
7331
7603
  abort: () => abortController.abort()
7332
7604
  };
7333
7605
  }
7606
+ function streamText(request) {
7607
+ return startTextStream(request, "streamText");
7608
+ }
7334
7609
  async function generateText(request) {
7335
- const call = streamText(request);
7610
+ const call = startTextStream(request, "generateText");
7336
7611
  for await (const _event of call.events) {
7337
7612
  }
7338
7613
  return await call.result;
@@ -7358,9 +7633,26 @@ function buildJsonSchemaConfig(request) {
7358
7633
  } : void 0;
7359
7634
  return { providerInfo, responseJsonSchema, openAiTextFormat };
7360
7635
  }
7361
- function streamJson(request) {
7636
+ function startJsonStream(request, operation) {
7362
7637
  const queue = createAsyncQueue();
7363
7638
  const abortController = new AbortController();
7639
+ const provider = resolveProvider(request.model).provider;
7640
+ const telemetry = createLlmTelemetryEmitter({
7641
+ telemetry: request.telemetry,
7642
+ operation,
7643
+ provider,
7644
+ model: request.model
7645
+ });
7646
+ const startedAtMs = Date.now();
7647
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7648
+ const streamMode = request.streamMode ?? "partial";
7649
+ telemetry.emit({
7650
+ type: "llm.call.started",
7651
+ inputMode: typeof request.input === "string" ? "string" : "messages",
7652
+ toolCount: request.tools?.length ?? 0,
7653
+ maxAttempts,
7654
+ streamMode
7655
+ });
7364
7656
  const resolveAbortSignal = () => {
7365
7657
  if (!request.signal) {
7366
7658
  return abortController.signal;
@@ -7379,135 +7671,155 @@ function streamJson(request) {
7379
7671
  return abortController.signal;
7380
7672
  };
7381
7673
  const result = (async () => {
7382
- const signal = resolveAbortSignal();
7383
- const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7384
- const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7385
- const streamMode = request.streamMode ?? "partial";
7386
- const failures = [];
7387
- let openAiTextFormatForAttempt = openAiTextFormat;
7388
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7389
- let rawText = "";
7390
- let lastPartial = "";
7391
- try {
7392
- const call = streamText({
7393
- model: request.model,
7394
- input: request.input,
7395
- instructions: request.instructions,
7396
- tools: request.tools,
7397
- responseMimeType: request.responseMimeType ?? "application/json",
7398
- responseJsonSchema,
7399
- thinkingLevel: request.thinkingLevel,
7400
- ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7401
- signal
7402
- });
7674
+ let uploadMetrics = emptyFileUploadMetrics();
7675
+ let attemptsUsed = 0;
7676
+ try {
7677
+ let output;
7678
+ await collectFileUploadMetrics(async () => {
7403
7679
  try {
7404
- for await (const event of call.events) {
7405
- queue.push(event);
7406
- if (event.type === "delta" && event.channel === "response") {
7407
- rawText += event.text;
7408
- if (streamMode === "partial") {
7409
- const partial = parsePartialJsonFromLlmText(rawText);
7410
- if (partial !== null) {
7411
- const serialized = JSON.stringify(partial);
7412
- if (serialized !== lastPartial) {
7413
- lastPartial = serialized;
7414
- queue.push({
7415
- type: "json",
7416
- stage: "partial",
7417
- value: partial
7418
- });
7680
+ const signal = resolveAbortSignal();
7681
+ const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7682
+ const failures = [];
7683
+ let openAiTextFormatForAttempt = openAiTextFormat;
7684
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7685
+ attemptsUsed = attempt;
7686
+ let rawText = "";
7687
+ let lastPartial = "";
7688
+ try {
7689
+ const call = streamText({
7690
+ model: request.model,
7691
+ input: request.input,
7692
+ instructions: request.instructions,
7693
+ tools: request.tools,
7694
+ responseMimeType: request.responseMimeType ?? "application/json",
7695
+ responseJsonSchema,
7696
+ thinkingLevel: request.thinkingLevel,
7697
+ ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7698
+ telemetry: false,
7699
+ signal
7700
+ });
7701
+ try {
7702
+ for await (const event of call.events) {
7703
+ queue.push(event);
7704
+ if (telemetry.includeStreamEvents) {
7705
+ telemetry.emit({ type: "llm.call.stream", event });
7706
+ }
7707
+ if (event.type === "delta" && event.channel === "response") {
7708
+ rawText += event.text;
7709
+ if (streamMode === "partial") {
7710
+ const partial = parsePartialJsonFromLlmText(rawText);
7711
+ if (partial !== null) {
7712
+ const serialized = JSON.stringify(partial);
7713
+ if (serialized !== lastPartial) {
7714
+ lastPartial = serialized;
7715
+ queue.push({
7716
+ type: "json",
7717
+ stage: "partial",
7718
+ value: partial
7719
+ });
7720
+ }
7721
+ }
7722
+ }
7419
7723
  }
7420
7724
  }
7725
+ } catch (streamError) {
7726
+ await call.result.catch(() => void 0);
7727
+ throw streamError;
7728
+ }
7729
+ const result2 = await call.result;
7730
+ rawText = rawText || result2.text;
7731
+ const cleanedText = normalizeJsonText(rawText);
7732
+ const repairedText = escapeNewlinesInStrings(cleanedText);
7733
+ const payload = JSON.parse(repairedText);
7734
+ const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7735
+ const parsed = request.schema.parse(normalized);
7736
+ queue.push({ type: "json", stage: "final", value: parsed });
7737
+ output = { value: parsed, rawText, result: result2 };
7738
+ return;
7739
+ } catch (error) {
7740
+ const handled = error instanceof Error ? error : new Error(String(error));
7741
+ failures.push({ attempt, rawText, error: handled });
7742
+ if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7743
+ openAiTextFormatForAttempt = void 0;
7744
+ }
7745
+ if (attempt >= maxAttempts) {
7746
+ throw new LlmJsonCallError(
7747
+ `LLM JSON call failed after ${attempt} attempt(s)`,
7748
+ failures
7749
+ );
7421
7750
  }
7422
7751
  }
7423
7752
  }
7424
- } catch (streamError) {
7425
- await call.result.catch(() => void 0);
7426
- throw streamError;
7427
- }
7428
- const result2 = await call.result;
7429
- rawText = rawText || result2.text;
7430
- const cleanedText = normalizeJsonText(rawText);
7431
- const repairedText = escapeNewlinesInStrings(cleanedText);
7432
- const payload = JSON.parse(repairedText);
7433
- const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7434
- const parsed = request.schema.parse(normalized);
7435
- queue.push({ type: "json", stage: "final", value: parsed });
7436
- queue.close();
7437
- return { value: parsed, rawText, result: result2 };
7438
- } catch (error) {
7439
- const handled = error instanceof Error ? error : new Error(String(error));
7440
- failures.push({ attempt, rawText, error: handled });
7441
- if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7442
- openAiTextFormatForAttempt = void 0;
7443
- }
7444
- if (attempt >= maxAttempts) {
7445
- throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
7753
+ throw new LlmJsonCallError("LLM JSON call failed", failures);
7754
+ } finally {
7755
+ uploadMetrics = getCurrentFileUploadMetrics();
7446
7756
  }
7447
- }
7757
+ });
7758
+ if (!output) {
7759
+ throw new Error("LLM JSON call returned no result.");
7760
+ }
7761
+ telemetry.emit({
7762
+ type: "llm.call.completed",
7763
+ success: true,
7764
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7765
+ modelVersion: output.result.modelVersion,
7766
+ blocked: output.result.blocked,
7767
+ usage: output.result.usage,
7768
+ costUsd: output.result.costUsd,
7769
+ rawTextChars: output.rawText.length,
7770
+ attempts: attemptsUsed,
7771
+ uploadCount: uploadMetrics.count,
7772
+ uploadBytes: uploadMetrics.totalBytes,
7773
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
7774
+ });
7775
+ queue.close();
7776
+ return output;
7777
+ } catch (error) {
7778
+ const err = error instanceof Error ? error : new Error(String(error));
7779
+ telemetry.emit({
7780
+ type: "llm.call.completed",
7781
+ success: false,
7782
+ durationMs: Math.max(0, Date.now() - startedAtMs),
7783
+ attempts: attemptsUsed > 0 ? attemptsUsed : void 0,
7784
+ uploadCount: uploadMetrics.count,
7785
+ uploadBytes: uploadMetrics.totalBytes,
7786
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
7787
+ error: err.message
7788
+ });
7789
+ queue.fail(err);
7790
+ throw err;
7791
+ } finally {
7792
+ await telemetry.flush();
7448
7793
  }
7449
- throw new LlmJsonCallError("LLM JSON call failed", failures);
7450
- })().catch((error) => {
7451
- const err = error instanceof Error ? error : new Error(String(error));
7452
- queue.fail(err);
7453
- throw err;
7454
- });
7794
+ })();
7455
7795
  return {
7456
7796
  events: queue.iterable,
7457
7797
  result,
7458
7798
  abort: () => abortController.abort()
7459
7799
  };
7460
7800
  }
7801
+ function streamJson(request) {
7802
+ return startJsonStream(request, "streamJson");
7803
+ }
7461
7804
  async function generateJson(request) {
7462
- const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
7463
- const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
7464
- let openAiTextFormatForAttempt = openAiTextFormat;
7465
- const failures = [];
7466
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7467
- let rawText = "";
7468
- try {
7469
- const call = streamText({
7470
- model: request.model,
7471
- input: request.input,
7472
- instructions: request.instructions,
7473
- tools: request.tools,
7474
- responseMimeType: request.responseMimeType ?? "application/json",
7475
- responseJsonSchema,
7476
- thinkingLevel: request.thinkingLevel,
7477
- ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
7478
- signal: request.signal
7479
- });
7480
- try {
7481
- for await (const event of call.events) {
7482
- request.onEvent?.(event);
7483
- if (event.type === "delta" && event.channel === "response") {
7484
- rawText += event.text;
7485
- }
7486
- }
7487
- } catch (streamError) {
7488
- await call.result.catch(() => void 0);
7489
- throw streamError;
7490
- }
7491
- const result = await call.result;
7492
- rawText = rawText || result.text;
7493
- const cleanedText = normalizeJsonText(rawText);
7494
- const repairedText = escapeNewlinesInStrings(cleanedText);
7495
- const payload = JSON.parse(repairedText);
7496
- const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
7497
- const parsed = request.schema.parse(normalized);
7498
- return { value: parsed, rawText, result };
7499
- } catch (error) {
7500
- const handled = error instanceof Error ? error : new Error(String(error));
7501
- failures.push({ attempt, rawText, error: handled });
7502
- if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
7503
- openAiTextFormatForAttempt = void 0;
7504
- }
7505
- if (attempt >= maxAttempts) {
7506
- throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
7805
+ const call = startJsonStream(
7806
+ {
7807
+ ...request,
7808
+ streamMode: "final"
7809
+ },
7810
+ "generateJson"
7811
+ );
7812
+ try {
7813
+ for await (const event of call.events) {
7814
+ if (event.type !== "json") {
7815
+ request.onEvent?.(event);
7507
7816
  }
7508
7817
  }
7818
+ } catch (streamError) {
7819
+ await call.result.catch(() => void 0);
7820
+ throw streamError;
7509
7821
  }
7510
- throw new LlmJsonCallError("LLM JSON call failed", failures);
7822
+ return await call.result;
7511
7823
  }
7512
7824
  var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
7513
7825
  function resolveToolLoopContents(input) {
@@ -9123,7 +9435,10 @@ function streamToolLoop(request) {
9123
9435
  abort: () => abortController.abort()
9124
9436
  };
9125
9437
  }
9126
- var IMAGE_GRADE_SCHEMA = z3.enum(["pass", "fail"]);
9438
+ var IMAGE_GRADE_VALUE_SCHEMA = z3.enum(["pass", "fail"]);
9439
+ var IMAGE_GRADE_SCHEMA = z3.object({
9440
+ grade: IMAGE_GRADE_VALUE_SCHEMA
9441
+ });
9127
9442
  async function gradeGeneratedImage(params) {
9128
9443
  const parts = [
9129
9444
  {
@@ -9134,7 +9449,7 @@ async function gradeGeneratedImage(params) {
9134
9449
  "Image prompt to grade:",
9135
9450
  params.imagePrompt,
9136
9451
  "",
9137
- 'Respond with the JSON string "pass" or "fail".'
9452
+ 'Respond with JSON like {"grade":"pass"} or {"grade":"fail"}.'
9138
9453
  ].join("\\n")
9139
9454
  },
9140
9455
  {
@@ -9143,12 +9458,13 @@ async function gradeGeneratedImage(params) {
9143
9458
  mimeType: params.image.mimeType ?? "image/png"
9144
9459
  }
9145
9460
  ];
9146
- const { value } = await generateJson({
9461
+ const { value, result } = await generateJson({
9147
9462
  model: params.model,
9148
9463
  input: [{ role: "user", content: parts }],
9149
- schema: IMAGE_GRADE_SCHEMA
9464
+ schema: IMAGE_GRADE_SCHEMA,
9465
+ telemetry: false
9150
9466
  });
9151
- return value;
9467
+ return { grade: value.grade, result };
9152
9468
  }
9153
9469
  async function generateImages(request) {
9154
9470
  const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 4));
@@ -9168,6 +9484,19 @@ async function generateImages(request) {
9168
9484
  if (!gradingPrompt) {
9169
9485
  throw new Error("imageGradingPrompt must be a non-empty string");
9170
9486
  }
9487
+ const telemetry = createLlmTelemetryEmitter({
9488
+ telemetry: request.telemetry,
9489
+ operation: "generateImages",
9490
+ provider: resolveProvider(request.model).provider,
9491
+ model: request.model
9492
+ });
9493
+ const startedAtMs = Date.now();
9494
+ telemetry.emit({
9495
+ type: "llm.call.started",
9496
+ imagePromptCount: promptList.length,
9497
+ styleImageCount: request.styleImages?.length ?? 0,
9498
+ maxAttempts
9499
+ });
9171
9500
  const addText = (parts, text) => {
9172
9501
  const lastPart = parts[parts.length - 1];
9173
9502
  if (lastPart !== void 0 && lastPart.type === "text") {
@@ -9225,6 +9554,9 @@ async function generateImages(request) {
9225
9554
  const inputMessages = [{ role: "user", content: buildInitialPromptParts() }];
9226
9555
  const orderedEntries = [...promptEntries];
9227
9556
  const resolvedImages = /* @__PURE__ */ new Map();
9557
+ let totalCostUsd = 0;
9558
+ let totalUsage;
9559
+ let attemptsUsed = 0;
9228
9560
  const removeResolvedEntries = (resolved) => {
9229
9561
  if (resolved.size === 0) {
9230
9562
  return;
@@ -9239,70 +9571,118 @@ async function generateImages(request) {
9239
9571
  }
9240
9572
  }
9241
9573
  };
9242
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9243
- const result = await generateText({
9244
- model: request.model,
9245
- input: inputMessages,
9246
- responseModalities: ["IMAGE", "TEXT"],
9247
- imageAspectRatio: request.imageAspectRatio,
9248
- imageSize: request.imageSize ?? "2K"
9249
- });
9250
- if (result.blocked || !result.content) {
9251
- continue;
9252
- }
9253
- const images = extractImages(result.content);
9254
- if (images.length > 0 && promptEntries.length > 0) {
9255
- const assignedCount = Math.min(images.length, promptEntries.length);
9256
- const pendingAssignments = promptEntries.slice(0, assignedCount);
9257
- const assignedImages = images.slice(0, assignedCount);
9258
- const gradeResults = await Promise.all(
9259
- pendingAssignments.map(
9260
- (entry, index) => gradeGeneratedImage({
9261
- gradingPrompt,
9262
- imagePrompt: entry.prompt,
9263
- image: (() => {
9264
- const image = assignedImages[index];
9265
- if (!image) {
9266
- throw new Error("Image generation returned fewer images than expected.");
9574
+ let uploadMetrics = emptyFileUploadMetrics();
9575
+ try {
9576
+ await collectFileUploadMetrics(async () => {
9577
+ try {
9578
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9579
+ attemptsUsed = attempt;
9580
+ const result = await generateText({
9581
+ model: request.model,
9582
+ input: inputMessages,
9583
+ responseModalities: ["IMAGE", "TEXT"],
9584
+ imageAspectRatio: request.imageAspectRatio,
9585
+ imageSize: request.imageSize ?? "2K",
9586
+ telemetry: false
9587
+ });
9588
+ totalCostUsd += result.costUsd;
9589
+ totalUsage = sumUsageTokens(totalUsage, result.usage);
9590
+ if (result.blocked || !result.content) {
9591
+ continue;
9592
+ }
9593
+ const images = extractImages(result.content);
9594
+ if (images.length > 0 && promptEntries.length > 0) {
9595
+ const assignedCount = Math.min(images.length, promptEntries.length);
9596
+ const pendingAssignments = promptEntries.slice(0, assignedCount);
9597
+ const assignedImages = images.slice(0, assignedCount);
9598
+ const gradeResults = await Promise.all(
9599
+ pendingAssignments.map(
9600
+ (entry, index) => gradeGeneratedImage({
9601
+ gradingPrompt,
9602
+ imagePrompt: entry.prompt,
9603
+ image: (() => {
9604
+ const image = assignedImages[index];
9605
+ if (!image) {
9606
+ throw new Error("Image generation returned fewer images than expected.");
9607
+ }
9608
+ return image;
9609
+ })(),
9610
+ model: "gpt-5.2"
9611
+ })
9612
+ )
9613
+ );
9614
+ const passedEntries = /* @__PURE__ */ new Set();
9615
+ for (let i = 0; i < gradeResults.length; i += 1) {
9616
+ const gradeResult = gradeResults[i];
9617
+ const entry = pendingAssignments[i];
9618
+ const image = assignedImages[i];
9619
+ if (!gradeResult || !entry || !image) {
9620
+ continue;
9267
9621
  }
9268
- return image;
9269
- })(),
9270
- model: "gpt-5.2"
9271
- })
9272
- )
9273
- );
9274
- const passedEntries = /* @__PURE__ */ new Set();
9275
- for (let i = 0; i < gradeResults.length; i += 1) {
9276
- const grade = gradeResults[i];
9277
- const entry = pendingAssignments[i];
9278
- const image = assignedImages[i];
9279
- if (!grade || !entry || !image) {
9280
- continue;
9281
- }
9282
- if (grade === "pass") {
9283
- resolvedImages.set(entry.index, image);
9284
- passedEntries.add(entry.index);
9622
+ totalCostUsd += gradeResult.result.costUsd;
9623
+ totalUsage = sumUsageTokens(totalUsage, gradeResult.result.usage);
9624
+ if (gradeResult.grade === "pass") {
9625
+ resolvedImages.set(entry.index, image);
9626
+ passedEntries.add(entry.index);
9627
+ }
9628
+ }
9629
+ removeResolvedEntries(passedEntries);
9630
+ }
9631
+ if (promptEntries.length === 0) {
9632
+ break;
9633
+ }
9634
+ inputMessages.push({
9635
+ role: "assistant",
9636
+ content: result.content.parts
9637
+ });
9638
+ inputMessages.push({
9639
+ role: "user",
9640
+ content: buildContinuationPromptParts(promptEntries)
9641
+ });
9285
9642
  }
9643
+ } finally {
9644
+ uploadMetrics = getCurrentFileUploadMetrics();
9286
9645
  }
9287
- removeResolvedEntries(passedEntries);
9288
- }
9289
- if (promptEntries.length === 0) {
9290
- break;
9291
- }
9292
- inputMessages.push({
9293
- role: "assistant",
9294
- content: result.content.parts
9295
9646
  });
9296
- inputMessages.push({ role: "user", content: buildContinuationPromptParts(promptEntries) });
9297
- }
9298
- const orderedImages = [];
9299
- for (const entry of orderedEntries) {
9300
- const image = resolvedImages.get(entry.index);
9301
- if (image) {
9302
- orderedImages.push(image);
9647
+ const orderedImages = [];
9648
+ for (const entry of orderedEntries) {
9649
+ const image = resolvedImages.get(entry.index);
9650
+ if (image) {
9651
+ orderedImages.push(image);
9652
+ }
9303
9653
  }
9654
+ const outputImages = orderedImages.slice(0, numImages);
9655
+ telemetry.emit({
9656
+ type: "llm.call.completed",
9657
+ success: true,
9658
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9659
+ usage: totalUsage,
9660
+ costUsd: totalCostUsd,
9661
+ imageCount: outputImages.length,
9662
+ attempts: attemptsUsed,
9663
+ uploadCount: uploadMetrics.count,
9664
+ uploadBytes: uploadMetrics.totalBytes,
9665
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
9666
+ });
9667
+ return outputImages;
9668
+ } catch (error) {
9669
+ const err = error instanceof Error ? error : new Error(String(error));
9670
+ telemetry.emit({
9671
+ type: "llm.call.completed",
9672
+ success: false,
9673
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9674
+ usage: totalUsage,
9675
+ costUsd: totalCostUsd,
9676
+ attempts: attemptsUsed > 0 ? attemptsUsed : void 0,
9677
+ uploadCount: uploadMetrics.count,
9678
+ uploadBytes: uploadMetrics.totalBytes,
9679
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
9680
+ error: err.message
9681
+ });
9682
+ throw err;
9683
+ } finally {
9684
+ await telemetry.flush();
9304
9685
  }
9305
- return orderedImages.slice(0, numImages);
9306
9686
  }
9307
9687
  async function generateImageInBatches(request) {
9308
9688
  const {
@@ -11953,7 +12333,7 @@ function isNoEntError(error) {
11953
12333
 
11954
12334
  // src/agent.ts
11955
12335
  async function runAgentLoop(request) {
11956
- const telemetry = createAgentTelemetrySession(request.telemetry);
12336
+ const telemetry = createTelemetrySession(request.telemetry);
11957
12337
  const logging = createRootAgentLoggingSession(request);
11958
12338
  try {
11959
12339
  return await runWithAgentLoggingSession(logging, async () => {
@@ -12039,7 +12419,7 @@ async function runAgentLoopInternal(request, context) {
12039
12419
  logging: _logging,
12040
12420
  ...toolLoopRequest
12041
12421
  } = request;
12042
- const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
12422
+ const telemetrySession = context.telemetry ?? createTelemetrySession(telemetry);
12043
12423
  const loggingSession = context.logging;
12044
12424
  const runId = randomRunId();
12045
12425
  const startedAtMs = Date.now();
@@ -12102,15 +12482,15 @@ async function runAgentLoopInternal(request, context) {
12102
12482
  ].join(" ")
12103
12483
  );
12104
12484
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
12105
- const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
12485
+ const includeStreamEvents = telemetrySession?.includeStreamEvents === true;
12106
12486
  const streamEventLogger = loggingSession ? createAgentStreamEventLogger({
12107
12487
  append: (line) => {
12108
12488
  loggingSession.logLine(`[agent:${runId}] ${line}`);
12109
12489
  }
12110
12490
  }) : void 0;
12111
- const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
12491
+ const wrappedOnEvent = sourceOnEvent || includeStreamEvents ? (event) => {
12112
12492
  sourceOnEvent?.(event);
12113
- if (includeLlmStreamEvents) {
12493
+ if (includeStreamEvents) {
12114
12494
  emitTelemetry({ type: "agent.run.stream", event });
12115
12495
  }
12116
12496
  streamEventLogger?.appendEvent(event);
@@ -12348,7 +12728,7 @@ function countToolCalls(result) {
12348
12728
  }
12349
12729
  return count;
12350
12730
  }
12351
- function sumUsageValue(current, next) {
12731
+ function sumUsageValue2(current, next) {
12352
12732
  if (typeof next !== "number" || !Number.isFinite(next)) {
12353
12733
  return current;
12354
12734
  }
@@ -12366,20 +12746,17 @@ function summarizeResultUsage(result) {
12366
12746
  continue;
12367
12747
  }
12368
12748
  summary = {
12369
- promptTokens: sumUsageValue(summary?.promptTokens, usage.promptTokens),
12370
- cachedTokens: sumUsageValue(summary?.cachedTokens, usage.cachedTokens),
12371
- responseTokens: sumUsageValue(summary?.responseTokens, usage.responseTokens),
12372
- responseImageTokens: sumUsageValue(summary?.responseImageTokens, usage.responseImageTokens),
12373
- thinkingTokens: sumUsageValue(summary?.thinkingTokens, usage.thinkingTokens),
12374
- totalTokens: sumUsageValue(summary?.totalTokens, usage.totalTokens),
12375
- toolUsePromptTokens: sumUsageValue(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
12749
+ promptTokens: sumUsageValue2(summary?.promptTokens, usage.promptTokens),
12750
+ cachedTokens: sumUsageValue2(summary?.cachedTokens, usage.cachedTokens),
12751
+ responseTokens: sumUsageValue2(summary?.responseTokens, usage.responseTokens),
12752
+ responseImageTokens: sumUsageValue2(summary?.responseImageTokens, usage.responseImageTokens),
12753
+ thinkingTokens: sumUsageValue2(summary?.thinkingTokens, usage.thinkingTokens),
12754
+ totalTokens: sumUsageValue2(summary?.totalTokens, usage.totalTokens),
12755
+ toolUsePromptTokens: sumUsageValue2(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
12376
12756
  };
12377
12757
  }
12378
12758
  return summary;
12379
12759
  }
12380
- function isPromiseLike2(value) {
12381
- return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
12382
- }
12383
12760
  function resolveAgentLoggingSelection(value) {
12384
12761
  if (value === false) {
12385
12762
  return void 0;
@@ -12413,60 +12790,6 @@ function createRootAgentLoggingSession(request) {
12413
12790
  mirrorToConsole: selected.mirrorToConsole !== false
12414
12791
  });
12415
12792
  }
12416
- function isAgentTelemetrySink(value) {
12417
- return typeof value === "object" && value !== null && typeof value.emit === "function";
12418
- }
12419
- function resolveTelemetrySelection(telemetry) {
12420
- if (!telemetry) {
12421
- return void 0;
12422
- }
12423
- if (isAgentTelemetrySink(telemetry)) {
12424
- return { sink: telemetry };
12425
- }
12426
- if (isAgentTelemetrySink(telemetry.sink)) {
12427
- return telemetry;
12428
- }
12429
- throw new Error("Invalid runAgentLoop telemetry config: expected a sink with emit(event).");
12430
- }
12431
- function createAgentTelemetrySession(telemetry) {
12432
- const config = resolveTelemetrySelection(telemetry);
12433
- if (!config) {
12434
- return void 0;
12435
- }
12436
- const pending = /* @__PURE__ */ new Set();
12437
- const trackPromise = (promise) => {
12438
- pending.add(promise);
12439
- promise.finally(() => {
12440
- pending.delete(promise);
12441
- });
12442
- };
12443
- const emit = (event) => {
12444
- try {
12445
- const output = config.sink.emit(event);
12446
- if (isPromiseLike2(output)) {
12447
- const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
12448
- trackPromise(task);
12449
- }
12450
- } catch {
12451
- }
12452
- };
12453
- const flush = async () => {
12454
- while (pending.size > 0) {
12455
- await Promise.allSettled([...pending]);
12456
- }
12457
- if (typeof config.sink.flush === "function") {
12458
- try {
12459
- await config.sink.flush();
12460
- } catch {
12461
- }
12462
- }
12463
- };
12464
- return {
12465
- includeLlmStreamEvents: config.includeLlmStreamEvents === true,
12466
- emit,
12467
- flush
12468
- };
12469
- }
12470
12793
  function createAgentTelemetryEmitter(params) {
12471
12794
  return (event) => {
12472
12795
  if (!params.session) {
@@ -13159,6 +13482,7 @@ export {
13159
13482
  applyPatch,
13160
13483
  configureGemini,
13161
13484
  configureModelConcurrency,
13485
+ configureTelemetry,
13162
13486
  convertGooglePartsToLlmParts,
13163
13487
  createApplyPatchTool,
13164
13488
  createCodexApplyPatchTool,
@@ -13207,6 +13531,7 @@ export {
13207
13531
  parseJsonFromLlmText,
13208
13532
  refreshChatGptOauthToken,
13209
13533
  resetModelConcurrencyConfig,
13534
+ resetTelemetry,
13210
13535
  resolveFilesystemToolProfile,
13211
13536
  resolveFireworksModelId,
13212
13537
  runAgentLoop,