@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.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,
@@ -4396,8 +4504,7 @@ function toGeminiPart(part) {
4396
4504
  return {
4397
4505
  fileData: {
4398
4506
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4399
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4400
- displayName: part.filename ?? void 0
4507
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4401
4508
  }
4402
4509
  };
4403
4510
  }
@@ -4415,8 +4522,7 @@ function toGeminiPart(part) {
4415
4522
  return {
4416
4523
  fileData: {
4417
4524
  fileUri: part.image_url,
4418
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4419
- displayName: part.filename ?? void 0
4525
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4420
4526
  }
4421
4527
  };
4422
4528
  }
@@ -4425,8 +4531,7 @@ function toGeminiPart(part) {
4425
4531
  return {
4426
4532
  fileData: {
4427
4533
  fileUri: buildCanonicalGeminiFileUri(part.file_id),
4428
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4429
- displayName: part.filename ?? void 0
4534
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4430
4535
  }
4431
4536
  };
4432
4537
  }
@@ -4452,8 +4557,7 @@ function toGeminiPart(part) {
4452
4557
  return {
4453
4558
  fileData: {
4454
4559
  fileUri: part.file_url,
4455
- mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4456
- displayName: part.filename ?? void 0
4560
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4457
4561
  }
4458
4562
  };
4459
4563
  }
@@ -4685,7 +4789,7 @@ async function prepareOpenAiPromptContentItem(item) {
4685
4789
  mimeType,
4686
4790
  filename
4687
4791
  });
4688
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4792
+ return { type: "input_file", file_id: uploaded.fileId };
4689
4793
  }
4690
4794
  if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4691
4795
  const parsed = parseDataUrlPayload(item.file_url);
@@ -4700,7 +4804,7 @@ async function prepareOpenAiPromptContentItem(item) {
4700
4804
  guessInlineDataFilename(parsed.mimeType)
4701
4805
  )
4702
4806
  });
4703
- return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4807
+ return { type: "input_file", file_id: uploaded.fileId };
4704
4808
  }
4705
4809
  return item;
4706
4810
  }
@@ -4765,21 +4869,16 @@ async function prepareGeminiPromptContents(contents) {
4765
4869
  for (const part of content.parts ?? []) {
4766
4870
  const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4767
4871
  if (canonicalFileId) {
4768
- const metadata = await getCanonicalFileMetadata(canonicalFileId);
4872
+ await getCanonicalFileMetadata(canonicalFileId);
4769
4873
  if (backend === "api") {
4770
4874
  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);
4875
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
4776
4876
  } else {
4777
4877
  const mirrored = await ensureVertexFileMirror(canonicalFileId);
4778
4878
  parts.push({
4779
4879
  fileData: {
4780
4880
  fileUri: mirrored.fileUri,
4781
- mimeType: mirrored.mimeType,
4782
- displayName: metadata.filename
4881
+ mimeType: mirrored.mimeType
4783
4882
  }
4784
4883
  });
4785
4884
  }
@@ -4798,18 +4897,13 @@ async function prepareGeminiPromptContents(contents) {
4798
4897
  });
4799
4898
  if (backend === "api") {
4800
4899
  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);
4900
+ parts.push(createPartFromUri(mirrored.uri, mirrored.mimeType));
4806
4901
  } else {
4807
4902
  const mirrored = await ensureVertexFileMirror(stored.fileId);
4808
4903
  parts.push({
4809
4904
  fileData: {
4810
4905
  fileUri: mirrored.fileUri,
4811
- mimeType: mirrored.mimeType,
4812
- displayName: filename
4906
+ mimeType: mirrored.mimeType
4813
4907
  }
4814
4908
  });
4815
4909
  }
@@ -5330,7 +5424,7 @@ function toOpenAiInput(contents) {
5330
5424
  ...part.file_id ? { file_id: part.file_id } : {},
5331
5425
  ...part.file_data ? { file_data: part.file_data } : {},
5332
5426
  ...part.file_url ? { file_url: part.file_url } : {},
5333
- ...part.filename ? { filename: part.filename } : {}
5427
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5334
5428
  });
5335
5429
  break;
5336
5430
  default:
@@ -5415,7 +5509,7 @@ function toChatGptInput(contents) {
5415
5509
  ...part.file_id ? { file_id: part.file_id } : {},
5416
5510
  ...part.file_data ? { file_data: part.file_data } : {},
5417
5511
  ...part.file_url ? { file_url: part.file_url } : {},
5418
- ...part.filename ? { filename: part.filename } : {}
5512
+ ...!part.file_id && part.filename ? { filename: part.filename } : {}
5419
5513
  });
5420
5514
  break;
5421
5515
  default:
@@ -6022,8 +6116,7 @@ function buildGeminiToolOutputMediaPart(item) {
6022
6116
  return {
6023
6117
  fileData: {
6024
6118
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6025
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6026
- displayName: item.filename ?? void 0
6119
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6027
6120
  }
6028
6121
  };
6029
6122
  }
@@ -6042,8 +6135,7 @@ function buildGeminiToolOutputMediaPart(item) {
6042
6135
  return {
6043
6136
  fileData: {
6044
6137
  fileUri: item.image_url,
6045
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6046
- displayName: item.filename ?? void 0
6138
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6047
6139
  }
6048
6140
  };
6049
6141
  }
@@ -6052,8 +6144,7 @@ function buildGeminiToolOutputMediaPart(item) {
6052
6144
  return {
6053
6145
  fileData: {
6054
6146
  fileUri: buildCanonicalGeminiFileUri(item.file_id),
6055
- mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6056
- displayName: item.filename ?? void 0
6147
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream"
6057
6148
  }
6058
6149
  };
6059
6150
  }
@@ -6076,12 +6167,7 @@ function buildGeminiToolOutputMediaPart(item) {
6076
6167
  return part;
6077
6168
  }
6078
6169
  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;
6170
+ return createPartFromUri(item.file_url, inferredMimeType);
6085
6171
  }
6086
6172
  }
6087
6173
  return null;