@ljoukov/llm 4.1.0 → 4.1.1

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
@@ -2674,7 +2674,7 @@ function getOpenAiFetch() {
2674
2674
  headersTimeout: timeoutMs
2675
2675
  });
2676
2676
  openAiClientState.cachedFetch = ((input, init) => {
2677
- return (0, import_undici2.fetch)(input, {
2677
+ return fetch(input, {
2678
2678
  ...init ?? {},
2679
2679
  dispatcher
2680
2680
  });
@@ -3384,6 +3384,9 @@ var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
3384
3384
  var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
3385
3385
  var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
3386
3386
  var FILES_TEMP_ROOT = import_node_path4.default.join(import_node_os3.default.tmpdir(), "ljoukov-llm-files");
3387
+ var FILES_CACHE_ROOT = import_node_path4.default.join(FILES_TEMP_ROOT, "cache");
3388
+ var FILES_CACHE_CONTENT_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT, "content");
3389
+ var FILES_CACHE_METADATA_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT, "metadata");
3387
3390
  var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
3388
3391
  metadataById: /* @__PURE__ */ new Map(),
3389
3392
  openAiUploadCacheByKey: /* @__PURE__ */ new Map(),
@@ -3529,6 +3532,12 @@ function toStoredFile(file) {
3529
3532
  function buildCacheKey(filename, mimeType, sha256Hex) {
3530
3533
  return `${sha256Hex}\0${filename}\0${mimeType}`;
3531
3534
  }
3535
+ function buildCachedContentPath(sha256Hex) {
3536
+ return import_node_path4.default.join(FILES_CACHE_CONTENT_ROOT, sha256Hex);
3537
+ }
3538
+ function buildCachedMetadataPath(fileId) {
3539
+ return import_node_path4.default.join(FILES_CACHE_METADATA_ROOT, `${fileId}.json`);
3540
+ }
3532
3541
  function isFresh(file) {
3533
3542
  if (!file.expires_at) {
3534
3543
  return true;
@@ -3549,6 +3558,82 @@ function recordMetadata(metadata) {
3549
3558
  }
3550
3559
  return metadata;
3551
3560
  }
3561
+ async function ensureFilesCacheReady() {
3562
+ await (0, import_promises2.mkdir)(FILES_CACHE_CONTENT_ROOT, { recursive: true });
3563
+ await (0, import_promises2.mkdir)(FILES_CACHE_METADATA_ROOT, { recursive: true });
3564
+ }
3565
+ async function cacheBufferLocally(bytes, sha256Hex) {
3566
+ await ensureFilesCacheReady();
3567
+ const localPath = buildCachedContentPath(sha256Hex);
3568
+ try {
3569
+ await (0, import_promises2.writeFile)(localPath, bytes, { flag: "wx" });
3570
+ } catch (error) {
3571
+ const code = error.code;
3572
+ if (code !== "EEXIST") {
3573
+ throw error;
3574
+ }
3575
+ }
3576
+ return localPath;
3577
+ }
3578
+ async function cacheFileLocally(filePath, sha256Hex) {
3579
+ await ensureFilesCacheReady();
3580
+ const localPath = buildCachedContentPath(sha256Hex);
3581
+ try {
3582
+ await (0, import_promises2.copyFile)(filePath, localPath);
3583
+ } catch (error) {
3584
+ const code = error.code;
3585
+ if (code !== "EEXIST") {
3586
+ throw error;
3587
+ }
3588
+ }
3589
+ return localPath;
3590
+ }
3591
+ async function persistMetadataToDisk(metadata) {
3592
+ await ensureFilesCacheReady();
3593
+ const payload = {
3594
+ file: metadata.file,
3595
+ filename: metadata.filename,
3596
+ bytes: metadata.bytes,
3597
+ mimeType: metadata.mimeType,
3598
+ sha256Hex: metadata.sha256Hex,
3599
+ localPath: metadata.localPath
3600
+ };
3601
+ await (0, import_promises2.writeFile)(
3602
+ buildCachedMetadataPath(metadata.file.id),
3603
+ `${JSON.stringify(payload, null, 2)}
3604
+ `
3605
+ );
3606
+ }
3607
+ async function loadPersistedMetadata(fileId) {
3608
+ try {
3609
+ const payload = JSON.parse(
3610
+ await (0, import_promises2.readFile)(buildCachedMetadataPath(fileId), "utf8")
3611
+ );
3612
+ if (!payload || typeof payload !== "object" || !payload.file) {
3613
+ return void 0;
3614
+ }
3615
+ if (payload.localPath) {
3616
+ try {
3617
+ const localStats = await (0, import_promises2.stat)(payload.localPath);
3618
+ if (!localStats.isFile()) {
3619
+ return void 0;
3620
+ }
3621
+ } catch {
3622
+ return void 0;
3623
+ }
3624
+ }
3625
+ return recordMetadata({
3626
+ file: payload.file,
3627
+ filename: payload.filename,
3628
+ bytes: payload.bytes,
3629
+ mimeType: payload.mimeType,
3630
+ sha256Hex: payload.sha256Hex,
3631
+ localPath: payload.localPath
3632
+ });
3633
+ } catch {
3634
+ return void 0;
3635
+ }
3636
+ }
3552
3637
  async function uploadOpenAiFileFromBytes(params) {
3553
3638
  const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3554
3639
  const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
@@ -3697,17 +3782,23 @@ async function retrieveOpenAiFile(fileId) {
3697
3782
  if (cached && isFresh(cached.file)) {
3698
3783
  return cached;
3699
3784
  }
3785
+ const persisted = await loadPersistedMetadata(fileId);
3786
+ if (persisted && isFresh(persisted.file)) {
3787
+ return persisted;
3788
+ }
3700
3789
  const client = getOpenAiClient();
3701
3790
  const retrieved = await client.files.retrieve(fileId);
3702
3791
  const file = toStoredFile(retrieved);
3703
- return recordMetadata({
3792
+ const metadata = recordMetadata({
3704
3793
  file,
3705
3794
  filename: file.filename,
3706
3795
  bytes: file.bytes,
3707
- mimeType: cached?.mimeType ?? resolveMimeType(file.filename, void 0),
3708
- sha256Hex: cached?.sha256Hex,
3709
- localPath: cached?.localPath
3796
+ mimeType: cached?.mimeType ?? persisted?.mimeType ?? resolveMimeType(file.filename, void 0),
3797
+ sha256Hex: cached?.sha256Hex ?? persisted?.sha256Hex,
3798
+ localPath: cached?.localPath ?? persisted?.localPath
3710
3799
  });
3800
+ await persistMetadataToDisk(metadata);
3801
+ return metadata;
3711
3802
  }
3712
3803
  function buildGeminiMirrorName(sha256Hex) {
3713
3804
  return `files/${sha256Hex.slice(0, 40)}`;
@@ -3819,6 +3910,7 @@ async function materializeOpenAiFile(fileId) {
3819
3910
  sha256Hex,
3820
3911
  localPath
3821
3912
  });
3913
+ await persistMetadataToDisk(updated);
3822
3914
  return {
3823
3915
  file: updated.file,
3824
3916
  filename: updated.filename,
@@ -3982,7 +4074,13 @@ async function filesCreate(params) {
3982
4074
  sha256Hex: sha256Hex2,
3983
4075
  bytes: info.size
3984
4076
  });
3985
- return uploaded2.file;
4077
+ const localPath2 = await cacheFileLocally(filePath, sha256Hex2);
4078
+ const cached2 = recordMetadata({
4079
+ ...uploaded2,
4080
+ localPath: localPath2
4081
+ });
4082
+ await persistMetadataToDisk(cached2);
4083
+ return cached2.file;
3986
4084
  }
3987
4085
  const filename = normaliseFilename(params.filename);
3988
4086
  const bytes = toBuffer(params.data);
@@ -3996,7 +4094,13 @@ async function filesCreate(params) {
3996
4094
  expiresAfterSeconds,
3997
4095
  sha256Hex
3998
4096
  });
3999
- return uploaded.file;
4097
+ const localPath = await cacheBufferLocally(bytes, sha256Hex);
4098
+ const cached = recordMetadata({
4099
+ ...uploaded,
4100
+ localPath
4101
+ });
4102
+ await persistMetadataToDisk(cached);
4103
+ return cached.file;
4000
4104
  }
4001
4105
  async function filesRetrieve(fileId) {
4002
4106
  return (await retrieveOpenAiFile(fileId)).file;
@@ -4029,6 +4133,10 @@ async function filesDelete(fileId) {
4029
4133
  const response = await getOpenAiClient().files.delete(fileId);
4030
4134
  filesState.metadataById.delete(fileId);
4031
4135
  filesState.materializedById.delete(fileId);
4136
+ try {
4137
+ await (0, import_promises2.unlink)(buildCachedMetadataPath(fileId));
4138
+ } catch {
4139
+ }
4032
4140
  return {
4033
4141
  id: response.id,
4034
4142
  deleted: response.deleted,
@@ -4508,8 +4616,7 @@ function toGeminiPart(part) {
4508
4616
  return {
4509
4617
  fileData: {
4510
4618
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4511
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4512
- displayName: part.filename ?? void 0
4619
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4513
4620
  }
4514
4621
  };
4515
4622
  }
@@ -4527,8 +4634,7 @@ function toGeminiPart(part) {
4527
4634
  return {
4528
4635
  fileData: {
4529
4636
  fileUri: part.image_url,
4530
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4531
- displayName: part.filename ?? void 0
4637
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4532
4638
  }
4533
4639
  };
4534
4640
  }
@@ -4537,8 +4643,7 @@ function toGeminiPart(part) {
4537
4643
  return {
4538
4644
  fileData: {
4539
4645
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4540
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4541
- displayName: part.filename ?? void 0
4646
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4542
4647
  }
4543
4648
  };
4544
4649
  }
@@ -4564,8 +4669,7 @@ function toGeminiPart(part) {
4564
4669
  return {
4565
4670
  fileData: {
4566
4671
  fileUri: part.file_url,
4567
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4568
- displayName: part.filename ?? void 0
4672
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4569
4673
  }
4570
4674
  };
4571
4675
  }
@@ -4797,7 +4901,7 @@ async function prepareOpenAiPromptContentItem(item) {
4797
4901
  mimeType,
4798
4902
  filename
4799
4903
  });
4800
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4904
+ return { type: "input_file", file_id: uploaded.fileId };
4801
4905
  }
4802
4906
  if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4803
4907
  const parsed = parseDataUrlPayload(item.file_url);
@@ -4812,7 +4916,7 @@ async function prepareOpenAiPromptContentItem(item) {
4812
4916
  guessInlineDataFilename(parsed.mimeType)
4813
4917
  )
4814
4918
  });
4815
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4919
+ return { type: "input_file", file_id: uploaded.fileId };
4816
4920
  }
4817
4921
  return item;
4818
4922
  }
@@ -4877,21 +4981,16 @@ async function prepareGeminiPromptContents(contents) {
4877
4981
  for (const part of content.parts ?? []) {
4878
4982
  const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4879
4983
  if (canonicalFileId) {
4880
- const metadata = await getCanonicalFileMetadata(canonicalFileId);
4984
+ await getCanonicalFileMetadata(canonicalFileId);
4881
4985
  if (backend === "api") {
4882
4986
  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);
4987
+ parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
4888
4988
  } else {
4889
4989
  const mirrored = await ensureVertexFileMirror(canonicalFileId);
4890
4990
  parts.push({
4891
4991
  fileData: {
4892
4992
  fileUri: mirrored.fileUri,
4893
- mimeType: mirrored.mimeType,
4894
- displayName: metadata.filename
4993
+ mimeType: mirrored.mimeType
4895
4994
  }
4896
4995
  });
4897
4996
  }
@@ -4910,18 +5009,13 @@ async function prepareGeminiPromptContents(contents) {
4910
5009
  });
4911
5010
  if (backend === "api") {
4912
5011
  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);
5012
+ parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
4918
5013
  } else {
4919
5014
  const mirrored = await ensureVertexFileMirror(stored.fileId);
4920
5015
  parts.push({
4921
5016
  fileData: {
4922
5017
  fileUri: mirrored.fileUri,
4923
- mimeType: mirrored.mimeType,
4924
- displayName: filename
5018
+ mimeType: mirrored.mimeType
4925
5019
  }
4926
5020
  });
4927
5021
  }
@@ -5442,7 +5536,7 @@ function toOpenAiInput(contents) {
5442
5536
  ...part.file_id ? { file_id: part.file_id } : {},
5443
5537
  ...part.file_data ? { file_data: part.file_data } : {},
5444
5538
  ...part.file_url ? { file_url: part.file_url } : {},
5445
- ...part.filename ? { filename: part.filename } : {}
5539
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5446
5540
  });
5447
5541
  break;
5448
5542
  default:
@@ -5527,7 +5621,7 @@ function toChatGptInput(contents) {
5527
5621
  ...part.file_id ? { file_id: part.file_id } : {},
5528
5622
  ...part.file_data ? { file_data: part.file_data } : {},
5529
5623
  ...part.file_url ? { file_url: part.file_url } : {},
5530
- ...part.filename ? { filename: part.filename } : {}
5624
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5531
5625
  });
5532
5626
  break;
5533
5627
  default:
@@ -6134,8 +6228,7 @@ function buildGeminiToolOutputMediaPart(item) {
6134
6228
  return {
6135
6229
  fileData: {
6136
6230
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6137
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6138
- displayName: item.filename ?? void 0
6231
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6139
6232
  }
6140
6233
  };
6141
6234
  }
@@ -6154,8 +6247,7 @@ function buildGeminiToolOutputMediaPart(item) {
6154
6247
  return {
6155
6248
  fileData: {
6156
6249
  fileUri: item.image_url,
6157
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6158
- displayName: item.filename ?? void 0
6250
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6159
6251
  }
6160
6252
  };
6161
6253
  }
@@ -6164,8 +6256,7 @@ function buildGeminiToolOutputMediaPart(item) {
6164
6256
  return {
6165
6257
  fileData: {
6166
6258
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6167
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6168
- displayName: item.filename ?? void 0
6259
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6169
6260
  }
6170
6261
  };
6171
6262
  }
@@ -6188,12 +6279,7 @@ function buildGeminiToolOutputMediaPart(item) {
6188
6279
  return part;
6189
6280
  }
6190
6281
  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;
6282
+ return (0, import_genai2.createPartFromUri)(item.file_url, inferredMimeType);
6197
6283
  }
6198
6284
  }
6199
6285
  return null;