@ljoukov/llm 5.0.4 → 7.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/README.md +68 -38
- package/dist/index.cjs +836 -350
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +841 -353
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -318,11 +318,6 @@ function getGeminiImagePricing(modelId) {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
// src/openai/pricing.ts
|
|
321
|
-
var OPENAI_GPT_52_PRICING = {
|
|
322
|
-
inputRate: 1.75 / 1e6,
|
|
323
|
-
cachedRate: 0.175 / 1e6,
|
|
324
|
-
outputRate: 14 / 1e6
|
|
325
|
-
};
|
|
326
321
|
var OPENAI_GPT_54_PRICING = {
|
|
327
322
|
inputRate: 2.5 / 1e6,
|
|
328
323
|
cachedRate: 0.25 / 1e6,
|
|
@@ -333,37 +328,31 @@ var OPENAI_GPT_54_PRIORITY_PRICING = {
|
|
|
333
328
|
cachedRate: 0.5 / 1e6,
|
|
334
329
|
outputRate: 30 / 1e6
|
|
335
330
|
};
|
|
336
|
-
var
|
|
337
|
-
inputRate: 1.25 / 1e6,
|
|
338
|
-
cachedRate: 0.125 / 1e6,
|
|
339
|
-
outputRate: 10 / 1e6
|
|
340
|
-
};
|
|
341
|
-
var OPENAI_GPT_5_MINI_PRICING = {
|
|
331
|
+
var OPENAI_GPT_54_MINI_PRICING = {
|
|
342
332
|
inputRate: 0.25 / 1e6,
|
|
343
333
|
cachedRate: 0.025 / 1e6,
|
|
344
334
|
outputRate: 2 / 1e6
|
|
345
335
|
};
|
|
336
|
+
var OPENAI_GPT_54_NANO_PRICING = {
|
|
337
|
+
inputRate: 0.05 / 1e6,
|
|
338
|
+
cachedRate: 5e-3 / 1e6,
|
|
339
|
+
outputRate: 0.4 / 1e6
|
|
340
|
+
};
|
|
346
341
|
function getOpenAiPricing(modelId) {
|
|
347
342
|
if (modelId.includes("gpt-5.4-fast")) {
|
|
348
343
|
return OPENAI_GPT_54_PRIORITY_PRICING;
|
|
349
344
|
}
|
|
350
|
-
if (modelId.includes("gpt-5.4")) {
|
|
351
|
-
return
|
|
352
|
-
}
|
|
353
|
-
if (modelId.includes("gpt-5.3-codex-spark")) {
|
|
354
|
-
return OPENAI_GPT_5_MINI_PRICING;
|
|
355
|
-
}
|
|
356
|
-
if (modelId.includes("gpt-5.3-codex")) {
|
|
357
|
-
return OPENAI_GPT_53_CODEX_PRICING;
|
|
345
|
+
if (modelId.includes("gpt-5.4-mini")) {
|
|
346
|
+
return OPENAI_GPT_54_MINI_PRICING;
|
|
358
347
|
}
|
|
359
|
-
if (modelId.includes("gpt-5.
|
|
360
|
-
return
|
|
348
|
+
if (modelId.includes("gpt-5.4-nano")) {
|
|
349
|
+
return OPENAI_GPT_54_NANO_PRICING;
|
|
361
350
|
}
|
|
362
|
-
if (modelId.includes("gpt-5-
|
|
363
|
-
return
|
|
351
|
+
if (modelId.includes("gpt-5.3-codex-spark")) {
|
|
352
|
+
return OPENAI_GPT_54_MINI_PRICING;
|
|
364
353
|
}
|
|
365
|
-
if (modelId.includes("gpt-5.
|
|
366
|
-
return
|
|
354
|
+
if (modelId.includes("gpt-5.4")) {
|
|
355
|
+
return OPENAI_GPT_54_PRICING;
|
|
367
356
|
}
|
|
368
357
|
return void 0;
|
|
369
358
|
}
|
|
@@ -2832,22 +2821,15 @@ async function runOpenAiCall(fn, modelId, runOptions) {
|
|
|
2832
2821
|
}
|
|
2833
2822
|
|
|
2834
2823
|
// src/openai/models.ts
|
|
2835
|
-
var OPENAI_MODEL_IDS = [
|
|
2836
|
-
"gpt-5.4",
|
|
2837
|
-
"gpt-5.3-codex",
|
|
2838
|
-
"gpt-5.2",
|
|
2839
|
-
"gpt-5.1-codex-mini"
|
|
2840
|
-
];
|
|
2824
|
+
var OPENAI_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"];
|
|
2841
2825
|
function isOpenAiModelId(value) {
|
|
2842
2826
|
return OPENAI_MODEL_IDS.includes(value);
|
|
2843
2827
|
}
|
|
2844
2828
|
var CHATGPT_MODEL_IDS = [
|
|
2845
2829
|
"chatgpt-gpt-5.4",
|
|
2846
2830
|
"chatgpt-gpt-5.4-fast",
|
|
2847
|
-
"chatgpt-gpt-5.
|
|
2848
|
-
"chatgpt-gpt-5.3-codex-spark"
|
|
2849
|
-
"chatgpt-gpt-5.2",
|
|
2850
|
-
"chatgpt-gpt-5.1-codex-mini"
|
|
2831
|
+
"chatgpt-gpt-5.4-mini",
|
|
2832
|
+
"chatgpt-gpt-5.3-codex-spark"
|
|
2851
2833
|
];
|
|
2852
2834
|
function isChatGptModelId(value) {
|
|
2853
2835
|
return CHATGPT_MODEL_IDS.includes(value);
|
|
@@ -3376,13 +3358,10 @@ var import_node_fs3 = require("fs");
|
|
|
3376
3358
|
var import_promises2 = require("fs/promises");
|
|
3377
3359
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
3378
3360
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
3379
|
-
var import_node_stream = require("stream");
|
|
3380
3361
|
var import_promises3 = require("stream/promises");
|
|
3381
3362
|
var import_storage = require("@google-cloud/storage");
|
|
3382
3363
|
var import_mime = __toESM(require("mime"), 1);
|
|
3383
3364
|
var DEFAULT_FILE_TTL_SECONDS = 48 * 60 * 60;
|
|
3384
|
-
var OPENAI_FILE_CREATE_MAX_BYTES = 512 * 1024 * 1024;
|
|
3385
|
-
var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
|
|
3386
3365
|
var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
|
|
3387
3366
|
var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
|
|
3388
3367
|
var FILES_TEMP_ROOT = import_node_path4.default.join(import_node_os3.default.tmpdir(), "ljoukov-llm-files");
|
|
@@ -3391,7 +3370,7 @@ var FILES_CACHE_CONTENT_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT,
|
|
|
3391
3370
|
var FILES_CACHE_METADATA_ROOT = import_node_path4.default.join(FILES_CACHE_ROOT, "metadata");
|
|
3392
3371
|
var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
|
|
3393
3372
|
metadataById: /* @__PURE__ */ new Map(),
|
|
3394
|
-
|
|
3373
|
+
canonicalUploadCacheByKey: /* @__PURE__ */ new Map(),
|
|
3395
3374
|
materializedById: /* @__PURE__ */ new Map(),
|
|
3396
3375
|
geminiMirrorById: /* @__PURE__ */ new Map(),
|
|
3397
3376
|
vertexMirrorById: /* @__PURE__ */ new Map(),
|
|
@@ -3472,7 +3451,7 @@ function formatUploadLogLine(event) {
|
|
|
3472
3451
|
}
|
|
3473
3452
|
function recordUploadEvent(event) {
|
|
3474
3453
|
const scope = fileUploadScopeStorage.getStore();
|
|
3475
|
-
const resolvedSource = event.source ?? scope?.source ?? (event.backend === "
|
|
3454
|
+
const resolvedSource = event.source ?? scope?.source ?? (event.backend === "gcs" ? "files_api" : "provider_mirror");
|
|
3476
3455
|
const timestampedEvent = {
|
|
3477
3456
|
...event,
|
|
3478
3457
|
source: resolvedSource,
|
|
@@ -3519,16 +3498,117 @@ async function computeFileSha256Hex(filePath) {
|
|
|
3519
3498
|
}
|
|
3520
3499
|
return hash.digest("hex");
|
|
3521
3500
|
}
|
|
3522
|
-
function
|
|
3501
|
+
function buildCanonicalFileId(filename, mimeType, sha256Hex) {
|
|
3502
|
+
return `file_${(0, import_node_crypto.createHash)("sha256").update(filename).update("\0").update(mimeType).update("\0").update(sha256Hex).digest("hex")}`;
|
|
3503
|
+
}
|
|
3504
|
+
function resolveCanonicalFilesBucket() {
|
|
3505
|
+
const raw = process.env.LLM_FILES_GCS_BUCKET ?? process.env.VERTEX_GCS_BUCKET ?? process.env.LLM_VERTEX_GCS_BUCKET;
|
|
3506
|
+
const trimmed = raw?.trim();
|
|
3507
|
+
if (!trimmed) {
|
|
3508
|
+
throw new Error(
|
|
3509
|
+
"LLM_FILES_GCS_BUCKET (or VERTEX_GCS_BUCKET) must be set to use the canonical files API."
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
return trimmed.replace(/^gs:\/\//u, "").replace(/\/+$/u, "");
|
|
3513
|
+
}
|
|
3514
|
+
function resolveCanonicalFilesPrefix() {
|
|
3515
|
+
const raw = process.env.LLM_FILES_GCS_PREFIX;
|
|
3516
|
+
const trimmed = raw?.trim().replace(/^\/+/u, "").replace(/\/+$/u, "");
|
|
3517
|
+
return trimmed ? `${trimmed}/` : "canonical-files/";
|
|
3518
|
+
}
|
|
3519
|
+
function isLatexLikeFile(filename, mimeType) {
|
|
3520
|
+
const extension = import_node_path4.default.extname(filename).trim().toLowerCase();
|
|
3521
|
+
const normalisedMimeType = mimeType.trim().toLowerCase();
|
|
3522
|
+
return extension === ".tex" || extension === ".ltx" || extension === ".latex" || normalisedMimeType === "application/x-tex" || normalisedMimeType === "text/x-tex";
|
|
3523
|
+
}
|
|
3524
|
+
function resolveCanonicalStorageContentType(filename, mimeType) {
|
|
3525
|
+
if (isLatexLikeFile(filename, mimeType)) {
|
|
3526
|
+
return "text/plain";
|
|
3527
|
+
}
|
|
3528
|
+
return mimeType;
|
|
3529
|
+
}
|
|
3530
|
+
function resolveCanonicalObjectExtension(filename, mimeType) {
|
|
3531
|
+
if (isLatexLikeFile(filename, mimeType)) {
|
|
3532
|
+
return "txt";
|
|
3533
|
+
}
|
|
3534
|
+
const fromFilename = import_node_path4.default.extname(filename).replace(/^\./u, "").trim().toLowerCase();
|
|
3535
|
+
if (fromFilename) {
|
|
3536
|
+
return fromFilename;
|
|
3537
|
+
}
|
|
3538
|
+
const fromMimeType = import_mime.default.getExtension(mimeType)?.trim().toLowerCase();
|
|
3539
|
+
if (fromMimeType) {
|
|
3540
|
+
return fromMimeType;
|
|
3541
|
+
}
|
|
3542
|
+
return "bin";
|
|
3543
|
+
}
|
|
3544
|
+
function buildCanonicalObjectName(fileId, filename, mimeType) {
|
|
3545
|
+
const extension = resolveCanonicalObjectExtension(filename, mimeType);
|
|
3546
|
+
return `${resolveCanonicalFilesPrefix()}${fileId}.${extension}`;
|
|
3547
|
+
}
|
|
3548
|
+
function toSafeStorageFilename(filename) {
|
|
3549
|
+
const normalized = normaliseFilename(filename).replace(/[^\w.-]+/gu, "-");
|
|
3550
|
+
return normalized.length > 0 ? normalized : "attachment.bin";
|
|
3551
|
+
}
|
|
3552
|
+
function parseUnixSeconds(value, fallback) {
|
|
3553
|
+
if (value) {
|
|
3554
|
+
const numeric = Number.parseInt(value, 10);
|
|
3555
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
3556
|
+
return numeric;
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
if (fallback) {
|
|
3560
|
+
const millis = Date.parse(fallback);
|
|
3561
|
+
if (Number.isFinite(millis)) {
|
|
3562
|
+
return Math.floor(millis / 1e3);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
return Math.floor(Date.now() / 1e3);
|
|
3566
|
+
}
|
|
3567
|
+
function parseOptionalUnixSeconds(value) {
|
|
3568
|
+
if (!value) {
|
|
3569
|
+
return void 0;
|
|
3570
|
+
}
|
|
3571
|
+
const millis = Date.parse(value);
|
|
3572
|
+
if (Number.isFinite(millis)) {
|
|
3573
|
+
return Math.floor(millis / 1e3);
|
|
3574
|
+
}
|
|
3575
|
+
const numeric = Number.parseInt(value, 10);
|
|
3576
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : void 0;
|
|
3577
|
+
}
|
|
3578
|
+
function toStoredFileFromCanonicalMetadata(options) {
|
|
3579
|
+
const metadata = options.objectMetadata.metadata;
|
|
3580
|
+
const filenameRaw = typeof metadata?.filename === "string" && metadata.filename.trim().length > 0 ? metadata.filename.trim() : import_node_path4.default.basename(options.objectName);
|
|
3581
|
+
const filename = normaliseFilename(filenameRaw);
|
|
3582
|
+
const bytesRaw = options.objectMetadata.size;
|
|
3583
|
+
const bytes = typeof bytesRaw === "string" ? Number.parseInt(bytesRaw, 10) : typeof bytesRaw === "number" ? bytesRaw : 0;
|
|
3584
|
+
const purpose = metadata?.purpose === "user_data" ? "user_data" : "user_data";
|
|
3585
|
+
const createdAt = parseUnixSeconds(
|
|
3586
|
+
typeof metadata?.createdAtUnix === "string" ? metadata.createdAtUnix : void 0,
|
|
3587
|
+
typeof options.objectMetadata.timeCreated === "string" ? options.objectMetadata.timeCreated : void 0
|
|
3588
|
+
);
|
|
3589
|
+
const expiresAt = parseOptionalUnixSeconds(
|
|
3590
|
+
typeof metadata?.expiresAt === "string" ? metadata.expiresAt : void 0
|
|
3591
|
+
);
|
|
3592
|
+
const mimeType = typeof metadata?.mimeType === "string" && metadata.mimeType.trim().length > 0 ? metadata.mimeType.trim() : typeof options.objectMetadata.contentType === "string" && options.objectMetadata.contentType.trim().length > 0 ? options.objectMetadata.contentType.trim() : resolveMimeType(filename, void 0);
|
|
3593
|
+
const sha256Hex = typeof metadata?.sha256 === "string" && metadata.sha256.trim().length > 0 ? metadata.sha256.trim() : void 0;
|
|
3523
3594
|
return {
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3595
|
+
file: {
|
|
3596
|
+
id: options.fileId,
|
|
3597
|
+
bytes: Number.isFinite(bytes) ? bytes : 0,
|
|
3598
|
+
created_at: createdAt,
|
|
3599
|
+
filename,
|
|
3600
|
+
object: "file",
|
|
3601
|
+
purpose,
|
|
3602
|
+
status: "processed",
|
|
3603
|
+
...expiresAt ? { expires_at: expiresAt } : {}
|
|
3604
|
+
},
|
|
3605
|
+
filename,
|
|
3606
|
+
bytes: Number.isFinite(bytes) ? bytes : 0,
|
|
3607
|
+
mimeType,
|
|
3608
|
+
sha256Hex,
|
|
3609
|
+
localPath: options.localPath,
|
|
3610
|
+
bucketName: options.bucketName,
|
|
3611
|
+
objectName: options.objectName
|
|
3532
3612
|
};
|
|
3533
3613
|
}
|
|
3534
3614
|
function buildCacheKey(filename, mimeType, sha256Hex) {
|
|
@@ -3549,7 +3629,7 @@ function isFresh(file) {
|
|
|
3549
3629
|
function recordMetadata(metadata) {
|
|
3550
3630
|
filesState.metadataById.set(metadata.file.id, metadata);
|
|
3551
3631
|
if (metadata.sha256Hex) {
|
|
3552
|
-
filesState.
|
|
3632
|
+
filesState.canonicalUploadCacheByKey.set(
|
|
3553
3633
|
buildCacheKey(
|
|
3554
3634
|
metadata.filename,
|
|
3555
3635
|
metadata.mimeType ?? "application/octet-stream",
|
|
@@ -3598,7 +3678,9 @@ async function persistMetadataToDisk(metadata) {
|
|
|
3598
3678
|
bytes: metadata.bytes,
|
|
3599
3679
|
mimeType: metadata.mimeType,
|
|
3600
3680
|
sha256Hex: metadata.sha256Hex,
|
|
3601
|
-
localPath: metadata.localPath
|
|
3681
|
+
localPath: metadata.localPath,
|
|
3682
|
+
bucketName: metadata.bucketName,
|
|
3683
|
+
objectName: metadata.objectName
|
|
3602
3684
|
};
|
|
3603
3685
|
await (0, import_promises2.writeFile)(
|
|
3604
3686
|
buildCachedMetadataPath(metadata.file.id),
|
|
@@ -3630,175 +3712,271 @@ async function loadPersistedMetadata(fileId) {
|
|
|
3630
3712
|
bytes: payload.bytes,
|
|
3631
3713
|
mimeType: payload.mimeType,
|
|
3632
3714
|
sha256Hex: payload.sha256Hex,
|
|
3633
|
-
localPath: payload.localPath
|
|
3715
|
+
localPath: payload.localPath,
|
|
3716
|
+
bucketName: payload.bucketName,
|
|
3717
|
+
objectName: payload.objectName
|
|
3634
3718
|
});
|
|
3635
3719
|
} catch {
|
|
3636
3720
|
return void 0;
|
|
3637
3721
|
}
|
|
3638
3722
|
}
|
|
3639
|
-
async function
|
|
3640
|
-
const
|
|
3641
|
-
const
|
|
3642
|
-
|
|
3643
|
-
|
|
3723
|
+
async function writeCanonicalFileFromPath(options) {
|
|
3724
|
+
const file = getStorageClient().bucket(options.bucketName).file(options.objectName);
|
|
3725
|
+
const storageContentType = resolveCanonicalStorageContentType(
|
|
3726
|
+
options.metadata.filename ?? "attachment.bin",
|
|
3727
|
+
options.mimeType
|
|
3728
|
+
);
|
|
3729
|
+
try {
|
|
3730
|
+
await (0, import_promises3.pipeline)(
|
|
3731
|
+
(0, import_node_fs3.createReadStream)(options.filePath),
|
|
3732
|
+
file.createWriteStream({
|
|
3733
|
+
resumable: options.bytes >= 10 * 1024 * 1024,
|
|
3734
|
+
preconditionOpts: { ifGenerationMatch: 0 },
|
|
3735
|
+
metadata: {
|
|
3736
|
+
contentType: storageContentType,
|
|
3737
|
+
contentDisposition: `inline; filename="${toSafeStorageFilename(options.metadata.filename ?? "attachment.bin")}"`,
|
|
3738
|
+
metadata: options.metadata
|
|
3739
|
+
}
|
|
3740
|
+
})
|
|
3741
|
+
);
|
|
3742
|
+
return true;
|
|
3743
|
+
} catch (error) {
|
|
3744
|
+
const code = error.code;
|
|
3745
|
+
if (code === 412 || code === "412") {
|
|
3746
|
+
return false;
|
|
3747
|
+
}
|
|
3748
|
+
throw error;
|
|
3644
3749
|
}
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3750
|
+
}
|
|
3751
|
+
async function writeCanonicalFileFromBytes(options) {
|
|
3752
|
+
const file = getStorageClient().bucket(options.bucketName).file(options.objectName);
|
|
3753
|
+
const storageContentType = resolveCanonicalStorageContentType(
|
|
3754
|
+
options.metadata.filename ?? "attachment.bin",
|
|
3755
|
+
options.mimeType
|
|
3756
|
+
);
|
|
3757
|
+
try {
|
|
3758
|
+
await file.save(options.bytes, {
|
|
3759
|
+
resumable: options.bytes.byteLength >= 10 * 1024 * 1024,
|
|
3760
|
+
preconditionOpts: { ifGenerationMatch: 0 },
|
|
3761
|
+
metadata: {
|
|
3762
|
+
contentType: storageContentType,
|
|
3763
|
+
contentDisposition: `inline; filename="${toSafeStorageFilename(options.metadata.filename ?? "attachment.bin")}"`,
|
|
3764
|
+
metadata: options.metadata
|
|
3659
3765
|
}
|
|
3660
3766
|
});
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
const
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
mime_type: params.mimeType,
|
|
3667
|
-
purpose: params.purpose
|
|
3668
|
-
});
|
|
3669
|
-
const partIds = [];
|
|
3670
|
-
for (let offset = 0; offset < params.bytes.byteLength; offset += OPENAI_UPLOAD_PART_MAX_BYTES) {
|
|
3671
|
-
const chunk = params.bytes.subarray(
|
|
3672
|
-
offset,
|
|
3673
|
-
Math.min(offset + OPENAI_UPLOAD_PART_MAX_BYTES, params.bytes.byteLength)
|
|
3674
|
-
);
|
|
3675
|
-
const uploadPart = await client.uploads.parts.create(upload.id, {
|
|
3676
|
-
data: new import_node_buffer3.File([new Uint8Array(chunk)], `${params.sha256Hex}.part`, {
|
|
3677
|
-
type: params.mimeType
|
|
3678
|
-
})
|
|
3679
|
-
});
|
|
3680
|
-
partIds.push(uploadPart.id);
|
|
3681
|
-
}
|
|
3682
|
-
const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
|
|
3683
|
-
const fileId = completed.file?.id;
|
|
3684
|
-
if (!fileId) {
|
|
3685
|
-
throw new Error("OpenAI upload completed without a file id.");
|
|
3767
|
+
return true;
|
|
3768
|
+
} catch (error) {
|
|
3769
|
+
const code = error.code;
|
|
3770
|
+
if (code === 412 || code === "412") {
|
|
3771
|
+
return false;
|
|
3686
3772
|
}
|
|
3687
|
-
|
|
3773
|
+
throw error;
|
|
3688
3774
|
}
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
filename
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3775
|
+
}
|
|
3776
|
+
async function refreshCanonicalObjectMetadata(options) {
|
|
3777
|
+
const storageContentType = resolveCanonicalStorageContentType(
|
|
3778
|
+
options.metadata.filename ?? "attachment.bin",
|
|
3779
|
+
options.mimeType
|
|
3780
|
+
);
|
|
3781
|
+
await getStorageClient().bucket(options.bucketName).file(options.objectName).setMetadata({
|
|
3782
|
+
contentType: storageContentType,
|
|
3783
|
+
contentDisposition: `inline; filename="${toSafeStorageFilename(options.metadata.filename ?? "attachment.bin")}"`,
|
|
3784
|
+
metadata: options.metadata
|
|
3696
3785
|
});
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3786
|
+
}
|
|
3787
|
+
async function createCanonicalMetadata(options) {
|
|
3788
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
3789
|
+
const expiresAt = createdAt + options.expiresAfterSeconds;
|
|
3790
|
+
const storedFile = {
|
|
3791
|
+
id: options.fileId,
|
|
3792
|
+
bytes: options.bytes,
|
|
3793
|
+
created_at: createdAt,
|
|
3794
|
+
filename: options.filename,
|
|
3795
|
+
object: "file",
|
|
3796
|
+
purpose: options.purpose,
|
|
3797
|
+
status: "processed",
|
|
3798
|
+
expires_at: expiresAt
|
|
3799
|
+
};
|
|
3800
|
+
const metadata = recordMetadata({
|
|
3801
|
+
file: storedFile,
|
|
3802
|
+
filename: options.filename,
|
|
3803
|
+
bytes: options.bytes,
|
|
3804
|
+
mimeType: options.mimeType,
|
|
3805
|
+
sha256Hex: options.sha256Hex,
|
|
3806
|
+
localPath: options.localPath,
|
|
3807
|
+
bucketName: options.bucketName,
|
|
3808
|
+
objectName: options.objectName
|
|
3705
3809
|
});
|
|
3810
|
+
await persistMetadataToDisk(metadata);
|
|
3706
3811
|
return metadata;
|
|
3707
3812
|
}
|
|
3708
|
-
async function
|
|
3813
|
+
async function uploadCanonicalFileFromBytes(params) {
|
|
3709
3814
|
const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
|
|
3710
|
-
const cached = filesState.
|
|
3815
|
+
const cached = filesState.canonicalUploadCacheByKey.get(cacheKey);
|
|
3711
3816
|
if (cached && isFresh(cached.file)) {
|
|
3712
3817
|
return cached;
|
|
3713
3818
|
}
|
|
3714
|
-
const
|
|
3819
|
+
const fileId = buildCanonicalFileId(params.filename, params.mimeType, params.sha256Hex);
|
|
3820
|
+
const bucketName = resolveCanonicalFilesBucket();
|
|
3821
|
+
const objectName = buildCanonicalObjectName(fileId, params.filename, params.mimeType);
|
|
3822
|
+
const metadataFields = {
|
|
3823
|
+
fileId,
|
|
3824
|
+
filename: params.filename,
|
|
3825
|
+
mimeType: params.mimeType,
|
|
3826
|
+
purpose: params.purpose,
|
|
3827
|
+
sha256: params.sha256Hex,
|
|
3828
|
+
createdAtUnix: Math.floor(Date.now() / 1e3).toString(),
|
|
3829
|
+
expiresAt: new Date(Date.now() + params.expiresAfterSeconds * 1e3).toISOString()
|
|
3830
|
+
};
|
|
3715
3831
|
const startedAtMs = Date.now();
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3832
|
+
const uploaded = await writeCanonicalFileFromBytes({
|
|
3833
|
+
bytes: params.bytes,
|
|
3834
|
+
bucketName,
|
|
3835
|
+
objectName,
|
|
3836
|
+
mimeType: params.mimeType,
|
|
3837
|
+
metadata: metadataFields
|
|
3838
|
+
});
|
|
3839
|
+
if (!uploaded) {
|
|
3840
|
+
await refreshCanonicalObjectMetadata({
|
|
3841
|
+
bucketName,
|
|
3842
|
+
objectName,
|
|
3843
|
+
mimeType: params.mimeType,
|
|
3844
|
+
metadata: metadataFields
|
|
3728
3845
|
});
|
|
3729
|
-
}
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3846
|
+
}
|
|
3847
|
+
const localPath = await cacheBufferLocally(params.bytes, params.sha256Hex);
|
|
3848
|
+
const canonical = await createCanonicalMetadata({
|
|
3849
|
+
fileId,
|
|
3850
|
+
filename: params.filename,
|
|
3851
|
+
mimeType: params.mimeType,
|
|
3852
|
+
purpose: params.purpose,
|
|
3853
|
+
expiresAfterSeconds: params.expiresAfterSeconds,
|
|
3854
|
+
sha256Hex: params.sha256Hex,
|
|
3855
|
+
bytes: params.bytes.byteLength,
|
|
3856
|
+
bucketName,
|
|
3857
|
+
objectName,
|
|
3858
|
+
localPath
|
|
3859
|
+
});
|
|
3860
|
+
if (uploaded) {
|
|
3861
|
+
recordUploadEvent({
|
|
3862
|
+
backend: "gcs",
|
|
3863
|
+
mode: "gcs",
|
|
3733
3864
|
filename: params.filename,
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
highWaterMark: OPENAI_UPLOAD_PART_MAX_BYTES
|
|
3865
|
+
bytes: params.bytes.byteLength,
|
|
3866
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
3867
|
+
mimeType: params.mimeType,
|
|
3868
|
+
fileId,
|
|
3869
|
+
fileUri: `gs://${bucketName}/${objectName}`
|
|
3740
3870
|
});
|
|
3741
|
-
let partIndex = 0;
|
|
3742
|
-
for await (const chunk of stream) {
|
|
3743
|
-
const buffer = import_node_buffer3.Buffer.isBuffer(chunk) ? chunk : import_node_buffer3.Buffer.from(chunk);
|
|
3744
|
-
const uploadPart = await client.uploads.parts.create(upload.id, {
|
|
3745
|
-
data: new import_node_buffer3.File(
|
|
3746
|
-
[new Uint8Array(buffer)],
|
|
3747
|
-
`${params.sha256Hex}.${partIndex.toString()}.part`,
|
|
3748
|
-
{
|
|
3749
|
-
type: params.mimeType
|
|
3750
|
-
}
|
|
3751
|
-
)
|
|
3752
|
-
});
|
|
3753
|
-
partIds.push(uploadPart.id);
|
|
3754
|
-
partIndex += 1;
|
|
3755
|
-
}
|
|
3756
|
-
const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
|
|
3757
|
-
const fileId = completed.file?.id;
|
|
3758
|
-
if (!fileId) {
|
|
3759
|
-
throw new Error("OpenAI upload completed without a file id.");
|
|
3760
|
-
}
|
|
3761
|
-
uploaded = await client.files.retrieve(fileId);
|
|
3762
3871
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3872
|
+
return canonical;
|
|
3873
|
+
}
|
|
3874
|
+
async function uploadCanonicalFileFromPath(params) {
|
|
3875
|
+
const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
|
|
3876
|
+
const cached = filesState.canonicalUploadCacheByKey.get(cacheKey);
|
|
3877
|
+
if (cached && isFresh(cached.file)) {
|
|
3878
|
+
return cached;
|
|
3879
|
+
}
|
|
3880
|
+
const fileId = buildCanonicalFileId(params.filename, params.mimeType, params.sha256Hex);
|
|
3881
|
+
const bucketName = resolveCanonicalFilesBucket();
|
|
3882
|
+
const objectName = buildCanonicalObjectName(fileId, params.filename, params.mimeType);
|
|
3883
|
+
const metadataFields = {
|
|
3884
|
+
fileId,
|
|
3885
|
+
filename: params.filename,
|
|
3886
|
+
mimeType: params.mimeType,
|
|
3887
|
+
purpose: params.purpose,
|
|
3888
|
+
sha256: params.sha256Hex,
|
|
3889
|
+
createdAtUnix: Math.floor(Date.now() / 1e3).toString(),
|
|
3890
|
+
expiresAt: new Date(Date.now() + params.expiresAfterSeconds * 1e3).toISOString()
|
|
3891
|
+
};
|
|
3892
|
+
const startedAtMs = Date.now();
|
|
3893
|
+
const uploaded = await writeCanonicalFileFromPath({
|
|
3894
|
+
filePath: params.filePath,
|
|
3895
|
+
bucketName,
|
|
3896
|
+
objectName,
|
|
3897
|
+
bytes: params.bytes,
|
|
3768
3898
|
mimeType: params.mimeType,
|
|
3769
|
-
|
|
3899
|
+
metadata: metadataFields
|
|
3770
3900
|
});
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3901
|
+
if (!uploaded) {
|
|
3902
|
+
await refreshCanonicalObjectMetadata({
|
|
3903
|
+
bucketName,
|
|
3904
|
+
objectName,
|
|
3905
|
+
mimeType: params.mimeType,
|
|
3906
|
+
metadata: metadataFields
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
const localPath = await cacheFileLocally(params.filePath, params.sha256Hex);
|
|
3910
|
+
const canonical = await createCanonicalMetadata({
|
|
3911
|
+
fileId,
|
|
3912
|
+
filename: params.filename,
|
|
3777
3913
|
mimeType: params.mimeType,
|
|
3778
|
-
|
|
3914
|
+
purpose: params.purpose,
|
|
3915
|
+
expiresAfterSeconds: params.expiresAfterSeconds,
|
|
3916
|
+
sha256Hex: params.sha256Hex,
|
|
3917
|
+
bytes: params.bytes,
|
|
3918
|
+
bucketName,
|
|
3919
|
+
objectName,
|
|
3920
|
+
localPath
|
|
3779
3921
|
});
|
|
3780
|
-
|
|
3922
|
+
if (uploaded) {
|
|
3923
|
+
recordUploadEvent({
|
|
3924
|
+
backend: "gcs",
|
|
3925
|
+
mode: "gcs",
|
|
3926
|
+
filename: params.filename,
|
|
3927
|
+
bytes: params.bytes,
|
|
3928
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
3929
|
+
mimeType: params.mimeType,
|
|
3930
|
+
fileId,
|
|
3931
|
+
fileUri: `gs://${bucketName}/${objectName}`
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3934
|
+
return canonical;
|
|
3935
|
+
}
|
|
3936
|
+
async function resolveCanonicalStorageLocation(fileId) {
|
|
3937
|
+
const cached = filesState.metadataById.get(fileId) ?? await loadPersistedMetadata(fileId);
|
|
3938
|
+
if (cached?.bucketName && cached.objectName) {
|
|
3939
|
+
return {
|
|
3940
|
+
bucketName: cached.bucketName,
|
|
3941
|
+
objectName: cached.objectName
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
const bucketName = resolveCanonicalFilesBucket();
|
|
3945
|
+
const [files2] = await getStorageClient().bucket(bucketName).getFiles({
|
|
3946
|
+
prefix: `${resolveCanonicalFilesPrefix()}${fileId}.`,
|
|
3947
|
+
maxResults: 1,
|
|
3948
|
+
autoPaginate: false
|
|
3949
|
+
});
|
|
3950
|
+
const file = files2[0];
|
|
3951
|
+
if (!file) {
|
|
3952
|
+
throw new Error(`Canonical file ${fileId} was not found in GCS.`);
|
|
3953
|
+
}
|
|
3954
|
+
return {
|
|
3955
|
+
bucketName,
|
|
3956
|
+
objectName: file.name
|
|
3957
|
+
};
|
|
3781
3958
|
}
|
|
3782
|
-
async function
|
|
3959
|
+
async function retrieveCanonicalFile(fileId) {
|
|
3783
3960
|
const cached = filesState.metadataById.get(fileId);
|
|
3784
|
-
if (cached && isFresh(cached.file)) {
|
|
3961
|
+
if (cached && isFresh(cached.file) && cached.bucketName && cached.objectName) {
|
|
3785
3962
|
return cached;
|
|
3786
3963
|
}
|
|
3787
3964
|
const persisted = await loadPersistedMetadata(fileId);
|
|
3788
|
-
if (persisted && isFresh(persisted.file)) {
|
|
3965
|
+
if (persisted && isFresh(persisted.file) && persisted.bucketName && persisted.objectName) {
|
|
3789
3966
|
return persisted;
|
|
3790
3967
|
}
|
|
3791
|
-
const
|
|
3792
|
-
const
|
|
3793
|
-
const
|
|
3794
|
-
const metadata = recordMetadata(
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3968
|
+
const existingLocalPath = cached?.localPath ?? persisted?.localPath;
|
|
3969
|
+
const { bucketName, objectName } = await resolveCanonicalStorageLocation(fileId);
|
|
3970
|
+
const [objectMetadata] = await getStorageClient().bucket(bucketName).file(objectName).getMetadata();
|
|
3971
|
+
const metadata = recordMetadata(
|
|
3972
|
+
toStoredFileFromCanonicalMetadata({
|
|
3973
|
+
fileId,
|
|
3974
|
+
bucketName,
|
|
3975
|
+
objectName,
|
|
3976
|
+
objectMetadata,
|
|
3977
|
+
localPath: existingLocalPath
|
|
3978
|
+
})
|
|
3979
|
+
);
|
|
3802
3980
|
await persistMetadataToDisk(metadata);
|
|
3803
3981
|
return metadata;
|
|
3804
3982
|
}
|
|
@@ -3826,7 +4004,7 @@ function resolveVertexMirrorBucket() {
|
|
|
3826
4004
|
const trimmed = raw?.trim();
|
|
3827
4005
|
if (!trimmed) {
|
|
3828
4006
|
throw new Error(
|
|
3829
|
-
"VERTEX_GCS_BUCKET must be set to use
|
|
4007
|
+
"VERTEX_GCS_BUCKET must be set to use canonical file ids with Vertex Gemini models."
|
|
3830
4008
|
);
|
|
3831
4009
|
}
|
|
3832
4010
|
return trimmed.replace(/^gs:\/\//u, "").replace(/\/+$/u, "");
|
|
@@ -3856,61 +4034,41 @@ function getGeminiMirrorClient() {
|
|
|
3856
4034
|
}
|
|
3857
4035
|
return filesState.geminiClientPromise;
|
|
3858
4036
|
}
|
|
3859
|
-
async function
|
|
4037
|
+
async function materializeCanonicalFile(fileId) {
|
|
3860
4038
|
const cachedPromise = filesState.materializedById.get(fileId);
|
|
3861
4039
|
if (cachedPromise) {
|
|
3862
4040
|
return await cachedPromise;
|
|
3863
4041
|
}
|
|
3864
4042
|
const promise = (async () => {
|
|
3865
|
-
const metadata = await
|
|
3866
|
-
if (metadata.localPath && metadata.sha256Hex && metadata.mimeType) {
|
|
4043
|
+
const metadata = await retrieveCanonicalFile(fileId);
|
|
4044
|
+
if (metadata.localPath && metadata.sha256Hex && metadata.mimeType && metadata.bucketName && metadata.objectName) {
|
|
3867
4045
|
return {
|
|
3868
4046
|
file: metadata.file,
|
|
3869
4047
|
filename: metadata.filename,
|
|
3870
4048
|
bytes: metadata.bytes,
|
|
3871
4049
|
mimeType: metadata.mimeType,
|
|
3872
4050
|
sha256Hex: metadata.sha256Hex,
|
|
3873
|
-
localPath: metadata.localPath
|
|
4051
|
+
localPath: metadata.localPath,
|
|
4052
|
+
bucketName: metadata.bucketName,
|
|
4053
|
+
objectName: metadata.objectName
|
|
3874
4054
|
};
|
|
3875
4055
|
}
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
import_node_path4.default.join(FILES_TEMP_ROOT, `${fileId.replace(/[^a-z0-9_-]/giu, "")}-`)
|
|
3879
|
-
);
|
|
3880
|
-
const localPath = import_node_path4.default.join(tempDir, normaliseFilename(metadata.filename, `${fileId}.bin`));
|
|
3881
|
-
const response = await getOpenAiClient().files.content(fileId);
|
|
3882
|
-
if (!response.ok) {
|
|
3883
|
-
throw new Error(
|
|
3884
|
-
`Failed to download OpenAI file ${fileId}: ${response.status} ${response.statusText}`
|
|
3885
|
-
);
|
|
4056
|
+
if (!metadata.bucketName || !metadata.objectName) {
|
|
4057
|
+
throw new Error(`Canonical file ${fileId} is missing GCS location metadata.`);
|
|
3886
4058
|
}
|
|
3887
|
-
const
|
|
3888
|
-
const mimeType = resolveMimeType(metadata.filename,
|
|
3889
|
-
const
|
|
3890
|
-
|
|
3891
|
-
if (response.body) {
|
|
3892
|
-
const source = import_node_stream.Readable.fromWeb(response.body);
|
|
3893
|
-
const writable = (0, import_node_fs3.createWriteStream)(localPath, { flags: "wx" });
|
|
3894
|
-
source.on("data", (chunk) => {
|
|
3895
|
-
const buffer = import_node_buffer3.Buffer.isBuffer(chunk) ? chunk : import_node_buffer3.Buffer.from(chunk);
|
|
3896
|
-
hash.update(buffer);
|
|
3897
|
-
bytes += buffer.byteLength;
|
|
3898
|
-
});
|
|
3899
|
-
await (0, import_promises3.pipeline)(source, writable);
|
|
3900
|
-
} else {
|
|
3901
|
-
const buffer = import_node_buffer3.Buffer.from(await response.arrayBuffer());
|
|
3902
|
-
hash.update(buffer);
|
|
3903
|
-
bytes = buffer.byteLength;
|
|
3904
|
-
await (0, import_promises2.writeFile)(localPath, buffer);
|
|
3905
|
-
}
|
|
3906
|
-
const sha256Hex = hash.digest("hex");
|
|
4059
|
+
const [downloadedBytes] = await getStorageClient().bucket(metadata.bucketName).file(metadata.objectName).download();
|
|
4060
|
+
const mimeType = metadata.mimeType ?? resolveMimeType(metadata.filename, void 0);
|
|
4061
|
+
const sha256Hex = metadata.sha256Hex ?? computeSha256Hex(downloadedBytes);
|
|
4062
|
+
const localPath = await cacheBufferLocally(downloadedBytes, sha256Hex);
|
|
3907
4063
|
const updated = recordMetadata({
|
|
3908
4064
|
file: metadata.file,
|
|
3909
4065
|
filename: metadata.filename,
|
|
3910
|
-
bytes:
|
|
4066
|
+
bytes: downloadedBytes.byteLength || metadata.bytes,
|
|
3911
4067
|
mimeType,
|
|
3912
4068
|
sha256Hex,
|
|
3913
|
-
localPath
|
|
4069
|
+
localPath,
|
|
4070
|
+
bucketName: metadata.bucketName,
|
|
4071
|
+
objectName: metadata.objectName
|
|
3914
4072
|
});
|
|
3915
4073
|
await persistMetadataToDisk(updated);
|
|
3916
4074
|
return {
|
|
@@ -3919,7 +4077,9 @@ async function materializeOpenAiFile(fileId) {
|
|
|
3919
4077
|
bytes: updated.bytes,
|
|
3920
4078
|
mimeType: updated.mimeType ?? mimeType,
|
|
3921
4079
|
sha256Hex,
|
|
3922
|
-
localPath
|
|
4080
|
+
localPath,
|
|
4081
|
+
bucketName: metadata.bucketName,
|
|
4082
|
+
objectName: metadata.objectName
|
|
3923
4083
|
};
|
|
3924
4084
|
})();
|
|
3925
4085
|
filesState.materializedById.set(fileId, promise);
|
|
@@ -3935,14 +4095,14 @@ async function ensureGeminiFileMirror(fileId) {
|
|
|
3935
4095
|
if (cached) {
|
|
3936
4096
|
return cached;
|
|
3937
4097
|
}
|
|
3938
|
-
const materialized = await
|
|
4098
|
+
const materialized = await materializeCanonicalFile(fileId);
|
|
3939
4099
|
const client = await getGeminiMirrorClient();
|
|
3940
4100
|
const name = buildGeminiMirrorName(materialized.sha256Hex);
|
|
3941
4101
|
try {
|
|
3942
4102
|
const existing = await client.files.get({ name });
|
|
3943
4103
|
if (existing.name && existing.uri && existing.mimeType) {
|
|
3944
4104
|
const mirror2 = {
|
|
3945
|
-
|
|
4105
|
+
canonicalFileId: fileId,
|
|
3946
4106
|
name: existing.name,
|
|
3947
4107
|
uri: existing.uri,
|
|
3948
4108
|
mimeType: existing.mimeType,
|
|
@@ -3970,7 +4130,7 @@ async function ensureGeminiFileMirror(fileId) {
|
|
|
3970
4130
|
throw new Error("Gemini file upload completed without a usable URI.");
|
|
3971
4131
|
}
|
|
3972
4132
|
const mirror = {
|
|
3973
|
-
|
|
4133
|
+
canonicalFileId: fileId,
|
|
3974
4134
|
name: resolved.name,
|
|
3975
4135
|
uri: resolved.uri,
|
|
3976
4136
|
mimeType: resolved.mimeType,
|
|
@@ -3995,7 +4155,7 @@ async function ensureVertexFileMirror(fileId) {
|
|
|
3995
4155
|
if (cached) {
|
|
3996
4156
|
return cached;
|
|
3997
4157
|
}
|
|
3998
|
-
const materialized = await
|
|
4158
|
+
const materialized = await materializeCanonicalFile(fileId);
|
|
3999
4159
|
const bucketName = resolveVertexMirrorBucket();
|
|
4000
4160
|
const prefix = resolveVertexMirrorPrefix();
|
|
4001
4161
|
const extension = import_mime.default.getExtension(materialized.mimeType) ?? import_node_path4.default.extname(materialized.filename).replace(/^\./u, "") ?? "bin";
|
|
@@ -4036,7 +4196,7 @@ async function ensureVertexFileMirror(fileId) {
|
|
|
4036
4196
|
}
|
|
4037
4197
|
}
|
|
4038
4198
|
const mirror = {
|
|
4039
|
-
|
|
4199
|
+
canonicalFileId: fileId,
|
|
4040
4200
|
bucket: bucketName,
|
|
4041
4201
|
objectName,
|
|
4042
4202
|
fileUri: `gs://${bucketName}/${objectName}`,
|
|
@@ -4067,7 +4227,7 @@ async function filesCreate(params) {
|
|
|
4067
4227
|
const filename2 = normaliseFilename(params.filename, import_node_path4.default.basename(filePath));
|
|
4068
4228
|
const mimeType2 = resolveMimeType(filename2, params.mimeType);
|
|
4069
4229
|
const sha256Hex2 = await computeFileSha256Hex(filePath);
|
|
4070
|
-
const uploaded2 = await
|
|
4230
|
+
const uploaded2 = await uploadCanonicalFileFromPath({
|
|
4071
4231
|
filePath,
|
|
4072
4232
|
filename: filename2,
|
|
4073
4233
|
mimeType: mimeType2,
|
|
@@ -4076,19 +4236,13 @@ async function filesCreate(params) {
|
|
|
4076
4236
|
sha256Hex: sha256Hex2,
|
|
4077
4237
|
bytes: info.size
|
|
4078
4238
|
});
|
|
4079
|
-
|
|
4080
|
-
const cached2 = recordMetadata({
|
|
4081
|
-
...uploaded2,
|
|
4082
|
-
localPath: localPath2
|
|
4083
|
-
});
|
|
4084
|
-
await persistMetadataToDisk(cached2);
|
|
4085
|
-
return cached2.file;
|
|
4239
|
+
return uploaded2.file;
|
|
4086
4240
|
}
|
|
4087
4241
|
const filename = normaliseFilename(params.filename);
|
|
4088
4242
|
const bytes = toBuffer(params.data);
|
|
4089
4243
|
const mimeType = resolveMimeType(filename, params.mimeType, "text/plain");
|
|
4090
4244
|
const sha256Hex = computeSha256Hex(bytes);
|
|
4091
|
-
const uploaded = await
|
|
4245
|
+
const uploaded = await uploadCanonicalFileFromBytes({
|
|
4092
4246
|
bytes,
|
|
4093
4247
|
filename,
|
|
4094
4248
|
mimeType,
|
|
@@ -4096,16 +4250,10 @@ async function filesCreate(params) {
|
|
|
4096
4250
|
expiresAfterSeconds,
|
|
4097
4251
|
sha256Hex
|
|
4098
4252
|
});
|
|
4099
|
-
|
|
4100
|
-
const cached = recordMetadata({
|
|
4101
|
-
...uploaded,
|
|
4102
|
-
localPath
|
|
4103
|
-
});
|
|
4104
|
-
await persistMetadataToDisk(cached);
|
|
4105
|
-
return cached.file;
|
|
4253
|
+
return uploaded.file;
|
|
4106
4254
|
}
|
|
4107
4255
|
async function filesRetrieve(fileId) {
|
|
4108
|
-
return (await
|
|
4256
|
+
return (await retrieveCanonicalFile(fileId)).file;
|
|
4109
4257
|
}
|
|
4110
4258
|
async function filesDelete(fileId) {
|
|
4111
4259
|
const cachedGemini = filesState.geminiMirrorById.get(fileId);
|
|
@@ -4132,34 +4280,73 @@ async function filesDelete(fileId) {
|
|
|
4132
4280
|
} catch {
|
|
4133
4281
|
}
|
|
4134
4282
|
}
|
|
4135
|
-
|
|
4283
|
+
try {
|
|
4284
|
+
const { bucketName, objectName } = await resolveCanonicalStorageLocation(fileId);
|
|
4285
|
+
await getStorageClient().bucket(bucketName).file(objectName).delete({ ignoreNotFound: true });
|
|
4286
|
+
} catch {
|
|
4287
|
+
}
|
|
4136
4288
|
filesState.metadataById.delete(fileId);
|
|
4289
|
+
filesState.canonicalUploadCacheByKey.forEach((value, key) => {
|
|
4290
|
+
if (value.file.id === fileId) {
|
|
4291
|
+
filesState.canonicalUploadCacheByKey.delete(key);
|
|
4292
|
+
}
|
|
4293
|
+
});
|
|
4137
4294
|
filesState.materializedById.delete(fileId);
|
|
4138
4295
|
try {
|
|
4139
4296
|
await (0, import_promises2.unlink)(buildCachedMetadataPath(fileId));
|
|
4140
4297
|
} catch {
|
|
4141
4298
|
}
|
|
4142
4299
|
return {
|
|
4143
|
-
id:
|
|
4144
|
-
deleted:
|
|
4300
|
+
id: fileId,
|
|
4301
|
+
deleted: true,
|
|
4145
4302
|
object: "file"
|
|
4146
4303
|
};
|
|
4147
4304
|
}
|
|
4148
4305
|
async function filesContent(fileId) {
|
|
4149
|
-
|
|
4306
|
+
const metadata = await retrieveCanonicalFile(fileId);
|
|
4307
|
+
if (!metadata.bucketName || !metadata.objectName) {
|
|
4308
|
+
throw new Error(`Canonical file ${fileId} is missing GCS location metadata.`);
|
|
4309
|
+
}
|
|
4310
|
+
const [bytes] = await getStorageClient().bucket(metadata.bucketName).file(metadata.objectName).download();
|
|
4311
|
+
const headers = new Headers();
|
|
4312
|
+
headers.set("content-type", metadata.mimeType ?? resolveMimeType(metadata.filename, void 0));
|
|
4313
|
+
headers.set("content-length", bytes.byteLength.toString());
|
|
4314
|
+
headers.set(
|
|
4315
|
+
"content-disposition",
|
|
4316
|
+
`inline; filename="${toSafeStorageFilename(metadata.filename)}"`
|
|
4317
|
+
);
|
|
4318
|
+
return new Response(bytes, {
|
|
4319
|
+
status: 200,
|
|
4320
|
+
headers
|
|
4321
|
+
});
|
|
4150
4322
|
}
|
|
4151
4323
|
async function getCanonicalFileMetadata(fileId) {
|
|
4152
|
-
const metadata = await
|
|
4324
|
+
const metadata = await retrieveCanonicalFile(fileId);
|
|
4153
4325
|
const mimeType = metadata.mimeType ?? resolveMimeType(metadata.filename, void 0);
|
|
4154
4326
|
const updated = metadata.mimeType === mimeType ? metadata : recordMetadata({
|
|
4155
4327
|
...metadata,
|
|
4156
4328
|
mimeType
|
|
4157
4329
|
});
|
|
4330
|
+
if (!updated.bucketName || !updated.objectName) {
|
|
4331
|
+
throw new Error(`Canonical file ${fileId} is missing GCS location metadata.`);
|
|
4332
|
+
}
|
|
4158
4333
|
return {
|
|
4159
4334
|
...updated,
|
|
4160
|
-
mimeType
|
|
4335
|
+
mimeType,
|
|
4336
|
+
bucketName: updated.bucketName,
|
|
4337
|
+
objectName: updated.objectName
|
|
4161
4338
|
};
|
|
4162
4339
|
}
|
|
4340
|
+
async function getCanonicalFileSignedUrl(options) {
|
|
4341
|
+
const metadata = await getCanonicalFileMetadata(options.fileId);
|
|
4342
|
+
const [signedUrl] = await getStorageClient().bucket(metadata.bucketName).file(metadata.objectName).getSignedUrl({
|
|
4343
|
+
version: "v4",
|
|
4344
|
+
action: "read",
|
|
4345
|
+
expires: Date.now() + (options.expiresAfterSeconds ?? 15 * 60) * 1e3,
|
|
4346
|
+
responseType: resolveCanonicalStorageContentType(metadata.filename, metadata.mimeType)
|
|
4347
|
+
});
|
|
4348
|
+
return signedUrl;
|
|
4349
|
+
}
|
|
4163
4350
|
var files = {
|
|
4164
4351
|
create: filesCreate,
|
|
4165
4352
|
retrieve: filesRetrieve,
|
|
@@ -4521,6 +4708,7 @@ function isJsonSchemaObject(schema) {
|
|
|
4521
4708
|
return false;
|
|
4522
4709
|
}
|
|
4523
4710
|
var CANONICAL_GEMINI_FILE_URI_PREFIX = "openai://file/";
|
|
4711
|
+
var CANONICAL_LLM_FILE_ID_PATTERN = /^file_[a-f0-9]{64}$/u;
|
|
4524
4712
|
function buildCanonicalGeminiFileUri(fileId) {
|
|
4525
4713
|
return `${CANONICAL_GEMINI_FILE_URI_PREFIX}${fileId}`;
|
|
4526
4714
|
}
|
|
@@ -4531,6 +4719,75 @@ function parseCanonicalGeminiFileId(fileUri) {
|
|
|
4531
4719
|
const fileId = fileUri.slice(CANONICAL_GEMINI_FILE_URI_PREFIX.length).trim();
|
|
4532
4720
|
return fileId.length > 0 ? fileId : void 0;
|
|
4533
4721
|
}
|
|
4722
|
+
function isCanonicalLlmFileId(fileId) {
|
|
4723
|
+
return typeof fileId === "string" && CANONICAL_LLM_FILE_ID_PATTERN.test(fileId.trim());
|
|
4724
|
+
}
|
|
4725
|
+
function isLlmMediaResolution(value) {
|
|
4726
|
+
return value === "auto" || value === "low" || value === "medium" || value === "high" || value === "original";
|
|
4727
|
+
}
|
|
4728
|
+
function resolveEffectiveMediaResolution(detail, fallback) {
|
|
4729
|
+
return detail ?? fallback;
|
|
4730
|
+
}
|
|
4731
|
+
function supportsOpenAiOriginalImageDetail(model) {
|
|
4732
|
+
if (!model) {
|
|
4733
|
+
return false;
|
|
4734
|
+
}
|
|
4735
|
+
const providerModel = isChatGptModelId(model) ? resolveChatGptProviderModel(model) : model;
|
|
4736
|
+
const match = /^gpt-(\d+)(?:\.(\d+))?/u.exec(providerModel);
|
|
4737
|
+
if (!match) {
|
|
4738
|
+
return false;
|
|
4739
|
+
}
|
|
4740
|
+
const major = Number(match[1]);
|
|
4741
|
+
const minor = Number(match[2] ?? "0");
|
|
4742
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor)) {
|
|
4743
|
+
return false;
|
|
4744
|
+
}
|
|
4745
|
+
return major > 5 || major === 5 && minor >= 4;
|
|
4746
|
+
}
|
|
4747
|
+
function toOpenAiImageDetail(mediaResolution, model) {
|
|
4748
|
+
switch (mediaResolution) {
|
|
4749
|
+
case "low":
|
|
4750
|
+
return "low";
|
|
4751
|
+
case "medium":
|
|
4752
|
+
return "high";
|
|
4753
|
+
case "high":
|
|
4754
|
+
return "high";
|
|
4755
|
+
case "original":
|
|
4756
|
+
return supportsOpenAiOriginalImageDetail(model) ? "original" : "high";
|
|
4757
|
+
case "auto":
|
|
4758
|
+
default:
|
|
4759
|
+
return "auto";
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
function toGeminiMediaResolution(mediaResolution) {
|
|
4763
|
+
switch (mediaResolution) {
|
|
4764
|
+
case "low":
|
|
4765
|
+
return import_genai2.MediaResolution.MEDIA_RESOLUTION_LOW;
|
|
4766
|
+
case "medium":
|
|
4767
|
+
return import_genai2.MediaResolution.MEDIA_RESOLUTION_MEDIUM;
|
|
4768
|
+
case "high":
|
|
4769
|
+
case "original":
|
|
4770
|
+
return import_genai2.MediaResolution.MEDIA_RESOLUTION_HIGH;
|
|
4771
|
+
case "auto":
|
|
4772
|
+
default:
|
|
4773
|
+
return void 0;
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
function toGeminiPartMediaResolution(mediaResolution) {
|
|
4777
|
+
switch (mediaResolution) {
|
|
4778
|
+
case "low":
|
|
4779
|
+
return import_genai2.PartMediaResolutionLevel.MEDIA_RESOLUTION_LOW;
|
|
4780
|
+
case "medium":
|
|
4781
|
+
return import_genai2.PartMediaResolutionLevel.MEDIA_RESOLUTION_MEDIUM;
|
|
4782
|
+
case "high":
|
|
4783
|
+
return import_genai2.PartMediaResolutionLevel.MEDIA_RESOLUTION_HIGH;
|
|
4784
|
+
case "original":
|
|
4785
|
+
return import_genai2.PartMediaResolutionLevel.MEDIA_RESOLUTION_ULTRA_HIGH;
|
|
4786
|
+
case "auto":
|
|
4787
|
+
default:
|
|
4788
|
+
return void 0;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4534
4791
|
function cloneContentPart(part) {
|
|
4535
4792
|
switch (part.type) {
|
|
4536
4793
|
case "text":
|
|
@@ -4659,7 +4916,8 @@ function convertGeminiContentToLlmContent(content) {
|
|
|
4659
4916
|
parts: convertGooglePartsToLlmParts(content.parts ?? [])
|
|
4660
4917
|
};
|
|
4661
4918
|
}
|
|
4662
|
-
function toGeminiPart(part) {
|
|
4919
|
+
function toGeminiPart(part, options) {
|
|
4920
|
+
const defaultMediaResolution = options?.defaultMediaResolution;
|
|
4663
4921
|
switch (part.type) {
|
|
4664
4922
|
case "text":
|
|
4665
4923
|
return {
|
|
@@ -4667,6 +4925,18 @@ function toGeminiPart(part) {
|
|
|
4667
4925
|
thought: part.thought === true ? true : void 0
|
|
4668
4926
|
};
|
|
4669
4927
|
case "inlineData": {
|
|
4928
|
+
if (isInlineImageMime(part.mimeType)) {
|
|
4929
|
+
const mimeType = part.mimeType ?? "application/octet-stream";
|
|
4930
|
+
const geminiPart = (0, import_genai2.createPartFromBase64)(
|
|
4931
|
+
part.data,
|
|
4932
|
+
mimeType,
|
|
4933
|
+
toGeminiPartMediaResolution(defaultMediaResolution)
|
|
4934
|
+
);
|
|
4935
|
+
if (part.filename && geminiPart.inlineData) {
|
|
4936
|
+
geminiPart.inlineData.displayName = part.filename;
|
|
4937
|
+
}
|
|
4938
|
+
return geminiPart;
|
|
4939
|
+
}
|
|
4670
4940
|
const inlineData = {
|
|
4671
4941
|
data: part.data,
|
|
4672
4942
|
mimeType: part.mimeType
|
|
@@ -4679,31 +4949,35 @@ function toGeminiPart(part) {
|
|
|
4679
4949
|
};
|
|
4680
4950
|
}
|
|
4681
4951
|
case "input_image": {
|
|
4952
|
+
const mediaResolution = resolveEffectiveMediaResolution(part.detail, defaultMediaResolution);
|
|
4953
|
+
const geminiPartMediaResolution = toGeminiPartMediaResolution(mediaResolution);
|
|
4682
4954
|
if (part.file_id) {
|
|
4683
|
-
return
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
};
|
|
4955
|
+
return (0, import_genai2.createPartFromUri)(
|
|
4956
|
+
buildCanonicalGeminiFileUri(part.file_id),
|
|
4957
|
+
inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
|
|
4958
|
+
geminiPartMediaResolution
|
|
4959
|
+
);
|
|
4689
4960
|
}
|
|
4690
4961
|
if (typeof part.image_url !== "string" || part.image_url.trim().length === 0) {
|
|
4691
4962
|
throw new Error("input_image requires image_url or file_id.");
|
|
4692
4963
|
}
|
|
4693
4964
|
const parsed = parseDataUrlPayload(part.image_url);
|
|
4694
4965
|
if (parsed) {
|
|
4695
|
-
const geminiPart = (0, import_genai2.createPartFromBase64)(
|
|
4966
|
+
const geminiPart = (0, import_genai2.createPartFromBase64)(
|
|
4967
|
+
parsed.dataBase64,
|
|
4968
|
+
parsed.mimeType,
|
|
4969
|
+
geminiPartMediaResolution
|
|
4970
|
+
);
|
|
4696
4971
|
if (part.filename && geminiPart.inlineData) {
|
|
4697
4972
|
geminiPart.inlineData.displayName = part.filename;
|
|
4698
4973
|
}
|
|
4699
4974
|
return geminiPart;
|
|
4700
4975
|
}
|
|
4701
|
-
return
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
};
|
|
4976
|
+
return (0, import_genai2.createPartFromUri)(
|
|
4977
|
+
part.image_url,
|
|
4978
|
+
inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
|
|
4979
|
+
geminiPartMediaResolution
|
|
4980
|
+
);
|
|
4707
4981
|
}
|
|
4708
4982
|
case "input_file": {
|
|
4709
4983
|
if (part.file_id) {
|
|
@@ -4746,11 +5020,11 @@ function toGeminiPart(part) {
|
|
|
4746
5020
|
throw new Error("Unsupported LLM content part");
|
|
4747
5021
|
}
|
|
4748
5022
|
}
|
|
4749
|
-
function convertLlmContentToGeminiContent(content) {
|
|
5023
|
+
function convertLlmContentToGeminiContent(content, options) {
|
|
4750
5024
|
const role = content.role === "assistant" ? "model" : "user";
|
|
4751
5025
|
return {
|
|
4752
5026
|
role,
|
|
4753
|
-
parts: content.parts.map(toGeminiPart)
|
|
5027
|
+
parts: content.parts.map((part) => toGeminiPart(part, options))
|
|
4754
5028
|
};
|
|
4755
5029
|
}
|
|
4756
5030
|
function resolveProvider(model) {
|
|
@@ -4931,11 +5205,25 @@ async function storeCanonicalPromptFile(options) {
|
|
|
4931
5205
|
mimeType: options.mimeType
|
|
4932
5206
|
};
|
|
4933
5207
|
}
|
|
4934
|
-
async function prepareOpenAiPromptContentItem(item) {
|
|
5208
|
+
async function prepareOpenAiPromptContentItem(item, options) {
|
|
4935
5209
|
if (!isOpenAiNativeContentItem(item)) {
|
|
4936
5210
|
return item;
|
|
4937
5211
|
}
|
|
4938
|
-
if (item.type === "input_image"
|
|
5212
|
+
if (item.type === "input_image") {
|
|
5213
|
+
if (isCanonicalLlmFileId(item.file_id)) {
|
|
5214
|
+
const signedUrl2 = await getCanonicalFileSignedUrl({ fileId: item.file_id });
|
|
5215
|
+
return {
|
|
5216
|
+
type: "input_image",
|
|
5217
|
+
image_url: signedUrl2,
|
|
5218
|
+
detail: toOpenAiImageDetail(
|
|
5219
|
+
isLlmMediaResolution(item.detail) ? item.detail : void 0,
|
|
5220
|
+
options?.model
|
|
5221
|
+
)
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5224
|
+
if (options?.offloadInlineData !== true || typeof item.image_url !== "string" || !item.image_url.trim().toLowerCase().startsWith("data:")) {
|
|
5225
|
+
return item;
|
|
5226
|
+
}
|
|
4939
5227
|
const parsed = parseDataUrlPayload(item.image_url);
|
|
4940
5228
|
if (!parsed) {
|
|
4941
5229
|
return item;
|
|
@@ -4948,13 +5236,27 @@ async function prepareOpenAiPromptContentItem(item) {
|
|
|
4948
5236
|
guessInlineDataFilename(parsed.mimeType)
|
|
4949
5237
|
)
|
|
4950
5238
|
});
|
|
5239
|
+
const signedUrl = await getCanonicalFileSignedUrl({ fileId: uploaded.fileId });
|
|
4951
5240
|
return {
|
|
4952
5241
|
type: "input_image",
|
|
4953
|
-
|
|
4954
|
-
|
|
5242
|
+
image_url: signedUrl,
|
|
5243
|
+
detail: toOpenAiImageDetail(
|
|
5244
|
+
isLlmMediaResolution(item.detail) ? item.detail : void 0,
|
|
5245
|
+
options?.model
|
|
5246
|
+
)
|
|
5247
|
+
};
|
|
5248
|
+
}
|
|
5249
|
+
if (item.type !== "input_file") {
|
|
5250
|
+
return item;
|
|
5251
|
+
}
|
|
5252
|
+
if (isCanonicalLlmFileId(item.file_id)) {
|
|
5253
|
+
const signedUrl = await getCanonicalFileSignedUrl({ fileId: item.file_id });
|
|
5254
|
+
return {
|
|
5255
|
+
type: "input_file",
|
|
5256
|
+
file_url: signedUrl
|
|
4955
5257
|
};
|
|
4956
5258
|
}
|
|
4957
|
-
if (
|
|
5259
|
+
if (options?.offloadInlineData !== true) {
|
|
4958
5260
|
return item;
|
|
4959
5261
|
}
|
|
4960
5262
|
if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
|
|
@@ -4968,7 +5270,11 @@ async function prepareOpenAiPromptContentItem(item) {
|
|
|
4968
5270
|
mimeType,
|
|
4969
5271
|
filename
|
|
4970
5272
|
});
|
|
4971
|
-
|
|
5273
|
+
const signedUrl = await getCanonicalFileSignedUrl({ fileId: uploaded.fileId });
|
|
5274
|
+
return {
|
|
5275
|
+
type: "input_file",
|
|
5276
|
+
file_url: signedUrl
|
|
5277
|
+
};
|
|
4972
5278
|
}
|
|
4973
5279
|
if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
|
|
4974
5280
|
const parsed = parseDataUrlPayload(item.file_url);
|
|
@@ -4983,11 +5289,15 @@ async function prepareOpenAiPromptContentItem(item) {
|
|
|
4983
5289
|
guessInlineDataFilename(parsed.mimeType)
|
|
4984
5290
|
)
|
|
4985
5291
|
});
|
|
4986
|
-
|
|
5292
|
+
const signedUrl = await getCanonicalFileSignedUrl({ fileId: uploaded.fileId });
|
|
5293
|
+
return {
|
|
5294
|
+
type: "input_file",
|
|
5295
|
+
file_url: signedUrl
|
|
5296
|
+
};
|
|
4987
5297
|
}
|
|
4988
5298
|
return item;
|
|
4989
5299
|
}
|
|
4990
|
-
async function prepareOpenAiPromptInput(input) {
|
|
5300
|
+
async function prepareOpenAiPromptInput(input, options) {
|
|
4991
5301
|
const prepareItem = async (item) => {
|
|
4992
5302
|
if (!item || typeof item !== "object") {
|
|
4993
5303
|
return item;
|
|
@@ -4997,7 +5307,7 @@ async function prepareOpenAiPromptInput(input) {
|
|
|
4997
5307
|
return {
|
|
4998
5308
|
...record,
|
|
4999
5309
|
content: await Promise.all(
|
|
5000
|
-
record.content.map((part) => prepareOpenAiPromptContentItem(part))
|
|
5310
|
+
record.content.map((part) => prepareOpenAiPromptContentItem(part, options))
|
|
5001
5311
|
)
|
|
5002
5312
|
};
|
|
5003
5313
|
}
|
|
@@ -5005,19 +5315,48 @@ async function prepareOpenAiPromptInput(input) {
|
|
|
5005
5315
|
return {
|
|
5006
5316
|
...record,
|
|
5007
5317
|
output: await Promise.all(
|
|
5008
|
-
record.output.map((part) => prepareOpenAiPromptContentItem(part))
|
|
5318
|
+
record.output.map((part) => prepareOpenAiPromptContentItem(part, options))
|
|
5009
5319
|
)
|
|
5010
5320
|
};
|
|
5011
5321
|
}
|
|
5012
|
-
return await prepareOpenAiPromptContentItem(item);
|
|
5322
|
+
return await prepareOpenAiPromptContentItem(item, options);
|
|
5013
5323
|
};
|
|
5014
5324
|
return await Promise.all(input.map((item) => prepareItem(item)));
|
|
5015
5325
|
}
|
|
5016
|
-
|
|
5017
|
-
|
|
5326
|
+
function hasCanonicalOpenAiFileReferences(input) {
|
|
5327
|
+
let found = false;
|
|
5328
|
+
const visitItems = (items) => {
|
|
5329
|
+
for (const item of items) {
|
|
5330
|
+
if (found || !item || typeof item !== "object") {
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
if (Array.isArray(item.content)) {
|
|
5334
|
+
visitItems(item.content);
|
|
5335
|
+
}
|
|
5336
|
+
if (Array.isArray(item.output)) {
|
|
5337
|
+
visitItems(item.output);
|
|
5338
|
+
}
|
|
5339
|
+
if (!isOpenAiNativeContentItem(item)) {
|
|
5340
|
+
continue;
|
|
5341
|
+
}
|
|
5342
|
+
if ((item.type === "input_image" || item.type === "input_file") && isCanonicalLlmFileId(item.file_id)) {
|
|
5343
|
+
found = true;
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
};
|
|
5348
|
+
visitItems(input);
|
|
5349
|
+
return found;
|
|
5350
|
+
}
|
|
5351
|
+
async function maybePrepareOpenAiPromptInput(input, options) {
|
|
5352
|
+
const offloadInlineData = estimateOpenAiInlinePromptBytes(input) > INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES;
|
|
5353
|
+
if (!offloadInlineData && !hasCanonicalOpenAiFileReferences(input)) {
|
|
5018
5354
|
return Array.from(input);
|
|
5019
5355
|
}
|
|
5020
|
-
return await prepareOpenAiPromptInput(input
|
|
5356
|
+
return await prepareOpenAiPromptInput(input, {
|
|
5357
|
+
...options,
|
|
5358
|
+
offloadInlineData
|
|
5359
|
+
});
|
|
5021
5360
|
}
|
|
5022
5361
|
function estimateGeminiInlinePromptBytes(contents) {
|
|
5023
5362
|
let total = 0;
|
|
@@ -5048,22 +5387,25 @@ async function prepareGeminiPromptContents(contents) {
|
|
|
5048
5387
|
for (const part of content.parts ?? []) {
|
|
5049
5388
|
const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
|
|
5050
5389
|
if (canonicalFileId) {
|
|
5390
|
+
const mediaResolution = part.mediaResolution?.level;
|
|
5051
5391
|
await getCanonicalFileMetadata(canonicalFileId);
|
|
5052
5392
|
if (backend === "api") {
|
|
5053
5393
|
const mirrored = await ensureGeminiFileMirror(canonicalFileId);
|
|
5054
|
-
parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
|
|
5394
|
+
parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType, mediaResolution));
|
|
5055
5395
|
} else {
|
|
5056
5396
|
const mirrored = await ensureVertexFileMirror(canonicalFileId);
|
|
5057
5397
|
parts.push({
|
|
5058
5398
|
fileData: {
|
|
5059
5399
|
fileUri: mirrored.fileUri,
|
|
5060
5400
|
mimeType: mirrored.mimeType
|
|
5061
|
-
}
|
|
5401
|
+
},
|
|
5402
|
+
...mediaResolution ? { mediaResolution: { level: mediaResolution } } : {}
|
|
5062
5403
|
});
|
|
5063
5404
|
}
|
|
5064
5405
|
continue;
|
|
5065
5406
|
}
|
|
5066
5407
|
if (part.inlineData?.data) {
|
|
5408
|
+
const mediaResolution = part.mediaResolution?.level;
|
|
5067
5409
|
const mimeType = part.inlineData.mimeType ?? "application/octet-stream";
|
|
5068
5410
|
const filename = normaliseAttachmentFilename(
|
|
5069
5411
|
getInlineAttachmentFilename(part.inlineData) ?? part.inlineData.displayName ?? guessInlineDataFilename(mimeType),
|
|
@@ -5076,14 +5418,15 @@ async function prepareGeminiPromptContents(contents) {
|
|
|
5076
5418
|
});
|
|
5077
5419
|
if (backend === "api") {
|
|
5078
5420
|
const mirrored = await ensureGeminiFileMirror(stored.fileId);
|
|
5079
|
-
parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType));
|
|
5421
|
+
parts.push((0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType, mediaResolution));
|
|
5080
5422
|
} else {
|
|
5081
5423
|
const mirrored = await ensureVertexFileMirror(stored.fileId);
|
|
5082
5424
|
parts.push({
|
|
5083
5425
|
fileData: {
|
|
5084
5426
|
fileUri: mirrored.fileUri,
|
|
5085
5427
|
mimeType: mirrored.mimeType
|
|
5086
|
-
}
|
|
5428
|
+
},
|
|
5429
|
+
...mediaResolution ? { mediaResolution: { level: mediaResolution } } : {}
|
|
5087
5430
|
});
|
|
5088
5431
|
}
|
|
5089
5432
|
continue;
|
|
@@ -5546,7 +5889,7 @@ function resolveTextContents(input) {
|
|
|
5546
5889
|
}
|
|
5547
5890
|
return contents;
|
|
5548
5891
|
}
|
|
5549
|
-
function toOpenAiInput(contents) {
|
|
5892
|
+
function toOpenAiInput(contents, options) {
|
|
5550
5893
|
const OPENAI_ROLE_FROM_LLM = {
|
|
5551
5894
|
user: "user",
|
|
5552
5895
|
assistant: "assistant",
|
|
@@ -5554,6 +5897,8 @@ function toOpenAiInput(contents) {
|
|
|
5554
5897
|
developer: "developer",
|
|
5555
5898
|
tool: "assistant"
|
|
5556
5899
|
};
|
|
5900
|
+
const defaultMediaResolution = options?.defaultMediaResolution;
|
|
5901
|
+
const model = options?.model;
|
|
5557
5902
|
return contents.map((content) => {
|
|
5558
5903
|
const parts = [];
|
|
5559
5904
|
for (const part of content.parts) {
|
|
@@ -5568,7 +5913,7 @@ function toOpenAiInput(contents) {
|
|
|
5568
5913
|
const imagePart = {
|
|
5569
5914
|
type: "input_image",
|
|
5570
5915
|
image_url: dataUrl,
|
|
5571
|
-
detail:
|
|
5916
|
+
detail: toOpenAiImageDetail(defaultMediaResolution, model)
|
|
5572
5917
|
};
|
|
5573
5918
|
setInlineAttachmentFilename(
|
|
5574
5919
|
imagePart,
|
|
@@ -5585,11 +5930,15 @@ function toOpenAiInput(contents) {
|
|
|
5585
5930
|
break;
|
|
5586
5931
|
}
|
|
5587
5932
|
case "input_image": {
|
|
5933
|
+
const mediaResolution = resolveEffectiveMediaResolution(
|
|
5934
|
+
part.detail,
|
|
5935
|
+
defaultMediaResolution
|
|
5936
|
+
);
|
|
5588
5937
|
const imagePart = {
|
|
5589
5938
|
type: "input_image",
|
|
5590
5939
|
...part.file_id ? { file_id: part.file_id } : {},
|
|
5591
5940
|
...part.image_url ? { image_url: part.image_url } : {},
|
|
5592
|
-
detail:
|
|
5941
|
+
detail: toOpenAiImageDetail(mediaResolution, model)
|
|
5593
5942
|
};
|
|
5594
5943
|
if (part.filename) {
|
|
5595
5944
|
setInlineAttachmentFilename(imagePart, part.filename);
|
|
@@ -5622,9 +5971,11 @@ function toOpenAiInput(contents) {
|
|
|
5622
5971
|
};
|
|
5623
5972
|
});
|
|
5624
5973
|
}
|
|
5625
|
-
function toChatGptInput(contents) {
|
|
5974
|
+
function toChatGptInput(contents, options) {
|
|
5626
5975
|
const instructionsParts = [];
|
|
5627
5976
|
const input = [];
|
|
5977
|
+
const defaultMediaResolution = options?.defaultMediaResolution;
|
|
5978
|
+
const model = options?.model;
|
|
5628
5979
|
for (const content of contents) {
|
|
5629
5980
|
if (content.role === "system" || content.role === "developer") {
|
|
5630
5981
|
for (const part of content.parts) {
|
|
@@ -5660,7 +6011,7 @@ function toChatGptInput(contents) {
|
|
|
5660
6011
|
parts.push({
|
|
5661
6012
|
type: "input_image",
|
|
5662
6013
|
image_url: dataUrl,
|
|
5663
|
-
detail:
|
|
6014
|
+
detail: toOpenAiImageDetail(defaultMediaResolution, model)
|
|
5664
6015
|
});
|
|
5665
6016
|
} else {
|
|
5666
6017
|
parts.push({
|
|
@@ -5674,14 +6025,19 @@ function toChatGptInput(contents) {
|
|
|
5674
6025
|
}
|
|
5675
6026
|
break;
|
|
5676
6027
|
}
|
|
5677
|
-
case "input_image":
|
|
6028
|
+
case "input_image": {
|
|
6029
|
+
const mediaResolution = resolveEffectiveMediaResolution(
|
|
6030
|
+
part.detail,
|
|
6031
|
+
defaultMediaResolution
|
|
6032
|
+
);
|
|
5678
6033
|
parts.push({
|
|
5679
6034
|
type: "input_image",
|
|
5680
6035
|
...part.file_id ? { file_id: part.file_id } : {},
|
|
5681
6036
|
...part.image_url ? { image_url: part.image_url } : {},
|
|
5682
|
-
detail:
|
|
6037
|
+
detail: toOpenAiImageDetail(mediaResolution, model)
|
|
5683
6038
|
});
|
|
5684
6039
|
break;
|
|
6040
|
+
}
|
|
5685
6041
|
case "input_file":
|
|
5686
6042
|
parts.push({
|
|
5687
6043
|
type: "input_file",
|
|
@@ -6074,6 +6430,9 @@ function isLlmToolOutputContentItem(value) {
|
|
|
6074
6430
|
return false;
|
|
6075
6431
|
}
|
|
6076
6432
|
}
|
|
6433
|
+
if (value.detail !== void 0 && value.detail !== null && !isLlmMediaResolution(value.detail)) {
|
|
6434
|
+
return false;
|
|
6435
|
+
}
|
|
6077
6436
|
return value.image_url !== void 0 || value.file_id !== void 0;
|
|
6078
6437
|
}
|
|
6079
6438
|
if (itemType === "input_file") {
|
|
@@ -6088,17 +6447,30 @@ function isLlmToolOutputContentItem(value) {
|
|
|
6088
6447
|
}
|
|
6089
6448
|
return false;
|
|
6090
6449
|
}
|
|
6091
|
-
function toOpenAiToolOutput(value) {
|
|
6450
|
+
function toOpenAiToolOutput(value, options) {
|
|
6451
|
+
const normalizeImageItem = (item) => {
|
|
6452
|
+
if (item.type !== "input_image") {
|
|
6453
|
+
return item;
|
|
6454
|
+
}
|
|
6455
|
+
const mediaResolution = resolveEffectiveMediaResolution(
|
|
6456
|
+
item.detail,
|
|
6457
|
+
options?.defaultMediaResolution
|
|
6458
|
+
);
|
|
6459
|
+
return {
|
|
6460
|
+
...item,
|
|
6461
|
+
detail: toOpenAiImageDetail(mediaResolution, options?.model)
|
|
6462
|
+
};
|
|
6463
|
+
};
|
|
6092
6464
|
if (isLlmToolOutputContentItem(value)) {
|
|
6093
|
-
return [value];
|
|
6465
|
+
return [normalizeImageItem(value)];
|
|
6094
6466
|
}
|
|
6095
6467
|
if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
|
|
6096
|
-
return value;
|
|
6468
|
+
return value.map((item) => normalizeImageItem(item));
|
|
6097
6469
|
}
|
|
6098
6470
|
return mergeToolOutput(value);
|
|
6099
6471
|
}
|
|
6100
|
-
function toChatGptToolOutput(value) {
|
|
6101
|
-
const toolOutput = toOpenAiToolOutput(value);
|
|
6472
|
+
function toChatGptToolOutput(value, options) {
|
|
6473
|
+
const toolOutput = toOpenAiToolOutput(value, options);
|
|
6102
6474
|
if (typeof toolOutput === "string") {
|
|
6103
6475
|
return toolOutput;
|
|
6104
6476
|
}
|
|
@@ -6110,7 +6482,12 @@ function toChatGptToolOutput(value) {
|
|
|
6110
6482
|
type: "input_image",
|
|
6111
6483
|
...item.file_id ? { file_id: item.file_id } : {},
|
|
6112
6484
|
...item.image_url ? { image_url: item.image_url } : {},
|
|
6113
|
-
...item.detail ? {
|
|
6485
|
+
...item.detail ? {
|
|
6486
|
+
detail: toOpenAiImageDetail(
|
|
6487
|
+
resolveEffectiveMediaResolution(item.detail, options?.defaultMediaResolution),
|
|
6488
|
+
options?.model
|
|
6489
|
+
)
|
|
6490
|
+
} : {}
|
|
6114
6491
|
};
|
|
6115
6492
|
});
|
|
6116
6493
|
}
|
|
@@ -6281,9 +6658,6 @@ async function maybeSpillToolOutputItem(item, toolName, options) {
|
|
|
6281
6658
|
return item;
|
|
6282
6659
|
}
|
|
6283
6660
|
async function maybeSpillToolOutput(value, toolName, options) {
|
|
6284
|
-
if (options?.provider === "chatgpt") {
|
|
6285
|
-
return value;
|
|
6286
|
-
}
|
|
6287
6661
|
if (typeof value === "string") {
|
|
6288
6662
|
if (options?.force !== true && import_node_buffer4.Buffer.byteLength(value, "utf8") <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
|
|
6289
6663
|
return value;
|
|
@@ -6369,34 +6743,41 @@ async function maybeSpillCombinedToolCallOutputs(callResults, options) {
|
|
|
6369
6743
|
})
|
|
6370
6744
|
);
|
|
6371
6745
|
}
|
|
6372
|
-
function buildGeminiToolOutputMediaPart(item) {
|
|
6746
|
+
function buildGeminiToolOutputMediaPart(item, options) {
|
|
6373
6747
|
if (item.type === "input_image") {
|
|
6748
|
+
const mediaResolution = resolveEffectiveMediaResolution(
|
|
6749
|
+
item.detail,
|
|
6750
|
+
options?.defaultMediaResolution
|
|
6751
|
+
);
|
|
6752
|
+
const geminiPartMediaResolution = toGeminiPartMediaResolution(mediaResolution);
|
|
6374
6753
|
if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
|
|
6375
|
-
return
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
};
|
|
6754
|
+
return (0, import_genai2.createPartFromUri)(
|
|
6755
|
+
buildCanonicalGeminiFileUri(item.file_id),
|
|
6756
|
+
inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
|
|
6757
|
+
geminiPartMediaResolution
|
|
6758
|
+
);
|
|
6381
6759
|
}
|
|
6382
6760
|
if (typeof item.image_url !== "string" || item.image_url.trim().length === 0) {
|
|
6383
6761
|
return null;
|
|
6384
6762
|
}
|
|
6385
6763
|
const parsed = parseDataUrlPayload(item.image_url);
|
|
6386
6764
|
if (parsed) {
|
|
6387
|
-
const part = (0, import_genai2.createPartFromBase64)(
|
|
6765
|
+
const part = (0, import_genai2.createPartFromBase64)(
|
|
6766
|
+
parsed.dataBase64,
|
|
6767
|
+
parsed.mimeType,
|
|
6768
|
+
geminiPartMediaResolution
|
|
6769
|
+
);
|
|
6388
6770
|
const displayName = item.filename?.trim();
|
|
6389
6771
|
if (displayName && part.inlineData) {
|
|
6390
6772
|
part.inlineData.displayName = displayName;
|
|
6391
6773
|
}
|
|
6392
6774
|
return part;
|
|
6393
6775
|
}
|
|
6394
|
-
return
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
};
|
|
6776
|
+
return (0, import_genai2.createPartFromUri)(
|
|
6777
|
+
item.image_url,
|
|
6778
|
+
inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
|
|
6779
|
+
geminiPartMediaResolution
|
|
6780
|
+
);
|
|
6400
6781
|
}
|
|
6401
6782
|
if (item.type === "input_file") {
|
|
6402
6783
|
if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
|
|
@@ -6474,7 +6855,9 @@ function buildGeminiFunctionResponseParts(options) {
|
|
|
6474
6855
|
}
|
|
6475
6856
|
const responseOutput = outputItems.map((item) => toGeminiToolOutputPlaceholder(item));
|
|
6476
6857
|
const responseParts = outputItems.flatMap((item) => {
|
|
6477
|
-
const mediaPart = buildGeminiToolOutputMediaPart(item
|
|
6858
|
+
const mediaPart = buildGeminiToolOutputMediaPart(item, {
|
|
6859
|
+
defaultMediaResolution: options.defaultMediaResolution
|
|
6860
|
+
});
|
|
6478
6861
|
return mediaPart ? [mediaPart] : [];
|
|
6479
6862
|
});
|
|
6480
6863
|
const responsePayload = { output: responseOutput };
|
|
@@ -7241,6 +7624,7 @@ function startLlmCallLoggerFromContents(options) {
|
|
|
7241
7624
|
...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
|
|
7242
7625
|
...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
|
|
7243
7626
|
...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
|
|
7627
|
+
...options.request.mediaResolution ? { mediaResolution: options.request.mediaResolution } : {},
|
|
7244
7628
|
...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
|
|
7245
7629
|
...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
|
|
7246
7630
|
},
|
|
@@ -7351,7 +7735,13 @@ async function runTextCall(params) {
|
|
|
7351
7735
|
const { result } = await collectFileUploadMetrics(async () => {
|
|
7352
7736
|
try {
|
|
7353
7737
|
if (provider === "openai") {
|
|
7354
|
-
const openAiInput = await maybePrepareOpenAiPromptInput(
|
|
7738
|
+
const openAiInput = await maybePrepareOpenAiPromptInput(
|
|
7739
|
+
toOpenAiInput(contents, {
|
|
7740
|
+
defaultMediaResolution: request.mediaResolution,
|
|
7741
|
+
model: request.model
|
|
7742
|
+
}),
|
|
7743
|
+
{ model: request.model, provider: "openai" }
|
|
7744
|
+
);
|
|
7355
7745
|
const openAiTools = toOpenAiTools(request.tools);
|
|
7356
7746
|
const reasoningEffort = resolveOpenAiReasoningEffort(
|
|
7357
7747
|
modelForProvider,
|
|
@@ -7425,7 +7815,14 @@ async function runTextCall(params) {
|
|
|
7425
7815
|
}
|
|
7426
7816
|
}, modelForProvider);
|
|
7427
7817
|
} else if (provider === "chatgpt") {
|
|
7428
|
-
const chatGptInput = toChatGptInput(contents
|
|
7818
|
+
const chatGptInput = toChatGptInput(contents, {
|
|
7819
|
+
defaultMediaResolution: request.mediaResolution,
|
|
7820
|
+
model: request.model
|
|
7821
|
+
});
|
|
7822
|
+
const preparedChatGptInput = await maybePrepareOpenAiPromptInput(chatGptInput.input, {
|
|
7823
|
+
model: request.model,
|
|
7824
|
+
provider: "chatgpt"
|
|
7825
|
+
});
|
|
7429
7826
|
const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
|
|
7430
7827
|
const openAiTools = toOpenAiTools(request.tools);
|
|
7431
7828
|
const requestPayload = {
|
|
@@ -7434,7 +7831,7 @@ async function runTextCall(params) {
|
|
|
7434
7831
|
stream: true,
|
|
7435
7832
|
...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
|
|
7436
7833
|
instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
|
|
7437
|
-
input:
|
|
7834
|
+
input: preparedChatGptInput,
|
|
7438
7835
|
include: ["reasoning.encrypted_content"],
|
|
7439
7836
|
reasoning: {
|
|
7440
7837
|
effort: toOpenAiReasoningEffort(reasoningEffort),
|
|
@@ -7522,12 +7919,18 @@ async function runTextCall(params) {
|
|
|
7522
7919
|
}, modelForProvider);
|
|
7523
7920
|
} else {
|
|
7524
7921
|
const geminiContents = await maybePrepareGeminiPromptContents(
|
|
7525
|
-
contents.map(
|
|
7922
|
+
contents.map(
|
|
7923
|
+
(content2) => convertLlmContentToGeminiContent(content2, {
|
|
7924
|
+
defaultMediaResolution: request.mediaResolution
|
|
7925
|
+
})
|
|
7926
|
+
)
|
|
7526
7927
|
);
|
|
7527
7928
|
const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
|
|
7929
|
+
const mediaResolution = toGeminiMediaResolution(request.mediaResolution);
|
|
7528
7930
|
const config = {
|
|
7529
7931
|
maxOutputTokens: 32e3,
|
|
7530
7932
|
...thinkingConfig ? { thinkingConfig } : {},
|
|
7933
|
+
...mediaResolution ? { mediaResolution } : {},
|
|
7531
7934
|
...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
|
|
7532
7935
|
...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
|
|
7533
7936
|
...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
|
|
@@ -8205,7 +8608,10 @@ async function runToolLoop(request) {
|
|
|
8205
8608
|
summary: "detailed"
|
|
8206
8609
|
};
|
|
8207
8610
|
let previousResponseId;
|
|
8208
|
-
let input = toOpenAiInput(contents
|
|
8611
|
+
let input = toOpenAiInput(contents, {
|
|
8612
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8613
|
+
model: request.model
|
|
8614
|
+
});
|
|
8209
8615
|
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
8210
8616
|
const turn = stepIndex + 1;
|
|
8211
8617
|
const stepStartedAtMs = Date.now();
|
|
@@ -8232,7 +8638,10 @@ async function runToolLoop(request) {
|
|
|
8232
8638
|
let reasoningSummary = "";
|
|
8233
8639
|
let stepToolCallText;
|
|
8234
8640
|
let stepToolCallPayload;
|
|
8235
|
-
const preparedInput = await maybePrepareOpenAiPromptInput(input
|
|
8641
|
+
const preparedInput = await maybePrepareOpenAiPromptInput(input, {
|
|
8642
|
+
model: request.model,
|
|
8643
|
+
provider: "openai"
|
|
8644
|
+
});
|
|
8236
8645
|
const stepRequestPayload = {
|
|
8237
8646
|
model: providerInfo.model,
|
|
8238
8647
|
input: preparedInput,
|
|
@@ -8363,7 +8772,10 @@ async function runToolLoop(request) {
|
|
|
8363
8772
|
const stepToolCalls = [];
|
|
8364
8773
|
if (responseToolCalls.length === 0) {
|
|
8365
8774
|
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
8366
|
-
const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2
|
|
8775
|
+
const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2, {
|
|
8776
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8777
|
+
model: request.model
|
|
8778
|
+
}) : [];
|
|
8367
8779
|
finalText = responseText;
|
|
8368
8780
|
finalThoughts = reasoningSummary;
|
|
8369
8781
|
const stepCompletedAtMs2 = Date.now();
|
|
@@ -8494,13 +8906,19 @@ async function runToolLoop(request) {
|
|
|
8494
8906
|
toolOutputs.push({
|
|
8495
8907
|
type: "custom_tool_call_output",
|
|
8496
8908
|
call_id: entry.call.call_id,
|
|
8497
|
-
output: toOpenAiToolOutput(outputPayload
|
|
8909
|
+
output: toOpenAiToolOutput(outputPayload, {
|
|
8910
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8911
|
+
model: request.model
|
|
8912
|
+
})
|
|
8498
8913
|
});
|
|
8499
8914
|
} else {
|
|
8500
8915
|
toolOutputs.push({
|
|
8501
8916
|
type: "function_call_output",
|
|
8502
8917
|
call_id: entry.call.call_id,
|
|
8503
|
-
output: toOpenAiToolOutput(outputPayload
|
|
8918
|
+
output: toOpenAiToolOutput(outputPayload, {
|
|
8919
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8920
|
+
model: request.model
|
|
8921
|
+
})
|
|
8504
8922
|
});
|
|
8505
8923
|
}
|
|
8506
8924
|
}
|
|
@@ -8525,7 +8943,10 @@ async function runToolLoop(request) {
|
|
|
8525
8943
|
timing
|
|
8526
8944
|
});
|
|
8527
8945
|
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
8528
|
-
const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput
|
|
8946
|
+
const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput, {
|
|
8947
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8948
|
+
model: request.model
|
|
8949
|
+
}) : [];
|
|
8529
8950
|
stepCallLogger?.complete({
|
|
8530
8951
|
responseText,
|
|
8531
8952
|
toolCallText: stepToolCallText,
|
|
@@ -8570,7 +8991,10 @@ async function runToolLoop(request) {
|
|
|
8570
8991
|
const openAiNativeTools = toOpenAiTools(request.modelTools);
|
|
8571
8992
|
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
|
|
8572
8993
|
const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
|
|
8573
|
-
const toolLoopInput = toChatGptInput(contents
|
|
8994
|
+
const toolLoopInput = toChatGptInput(contents, {
|
|
8995
|
+
defaultMediaResolution: request.mediaResolution,
|
|
8996
|
+
model: request.model
|
|
8997
|
+
});
|
|
8574
8998
|
const conversationId = `tool-loop-${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`;
|
|
8575
8999
|
const promptCacheKey = conversationId;
|
|
8576
9000
|
let input = [...toolLoopInput.input];
|
|
@@ -8586,6 +9010,10 @@ async function runToolLoop(request) {
|
|
|
8586
9010
|
let reasoningSummaryText = "";
|
|
8587
9011
|
let stepToolCallText;
|
|
8588
9012
|
let stepToolCallPayload;
|
|
9013
|
+
const preparedInput = await maybePrepareOpenAiPromptInput(input, {
|
|
9014
|
+
model: request.model,
|
|
9015
|
+
provider: "chatgpt"
|
|
9016
|
+
});
|
|
8589
9017
|
const markFirstModelEvent = () => {
|
|
8590
9018
|
if (firstModelEventAtMs === void 0) {
|
|
8591
9019
|
firstModelEventAtMs = Date.now();
|
|
@@ -8597,7 +9025,7 @@ async function runToolLoop(request) {
|
|
|
8597
9025
|
stream: true,
|
|
8598
9026
|
...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
|
|
8599
9027
|
instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
|
|
8600
|
-
input,
|
|
9028
|
+
input: preparedInput,
|
|
8601
9029
|
prompt_cache_key: promptCacheKey,
|
|
8602
9030
|
include: ["reasoning.encrypted_content"],
|
|
8603
9031
|
tools: openAiTools,
|
|
@@ -8674,7 +9102,10 @@ async function runToolLoop(request) {
|
|
|
8674
9102
|
stepToolCallText = serialiseLogArtifactText(stepToolCallPayload);
|
|
8675
9103
|
if (responseToolCalls.length === 0) {
|
|
8676
9104
|
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
8677
|
-
const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2
|
|
9105
|
+
const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2, {
|
|
9106
|
+
defaultMediaResolution: request.mediaResolution,
|
|
9107
|
+
model: request.model
|
|
9108
|
+
}).input : [];
|
|
8678
9109
|
finalText = responseText;
|
|
8679
9110
|
finalThoughts = reasoningSummaryText;
|
|
8680
9111
|
const stepCompletedAtMs2 = Date.now();
|
|
@@ -8806,7 +9237,10 @@ async function runToolLoop(request) {
|
|
|
8806
9237
|
toolOutputs.push({
|
|
8807
9238
|
type: "custom_tool_call_output",
|
|
8808
9239
|
call_id: entry.ids.callId,
|
|
8809
|
-
output: toChatGptToolOutput(outputPayload
|
|
9240
|
+
output: toChatGptToolOutput(outputPayload, {
|
|
9241
|
+
defaultMediaResolution: request.mediaResolution,
|
|
9242
|
+
model: request.model
|
|
9243
|
+
})
|
|
8810
9244
|
});
|
|
8811
9245
|
} else {
|
|
8812
9246
|
toolOutputs.push({
|
|
@@ -8820,7 +9254,10 @@ async function runToolLoop(request) {
|
|
|
8820
9254
|
toolOutputs.push({
|
|
8821
9255
|
type: "function_call_output",
|
|
8822
9256
|
call_id: entry.ids.callId,
|
|
8823
|
-
output: toChatGptToolOutput(outputPayload
|
|
9257
|
+
output: toChatGptToolOutput(outputPayload, {
|
|
9258
|
+
defaultMediaResolution: request.mediaResolution,
|
|
9259
|
+
model: request.model
|
|
9260
|
+
})
|
|
8824
9261
|
});
|
|
8825
9262
|
}
|
|
8826
9263
|
}
|
|
@@ -8844,7 +9281,10 @@ async function runToolLoop(request) {
|
|
|
8844
9281
|
timing
|
|
8845
9282
|
});
|
|
8846
9283
|
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
8847
|
-
const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput
|
|
9284
|
+
const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput, {
|
|
9285
|
+
defaultMediaResolution: request.mediaResolution,
|
|
9286
|
+
model: request.model
|
|
9287
|
+
}).input : [];
|
|
8848
9288
|
stepCallLogger?.complete({
|
|
8849
9289
|
responseText,
|
|
8850
9290
|
toolCallText: stepToolCallText,
|
|
@@ -9175,7 +9615,11 @@ async function runToolLoop(request) {
|
|
|
9175
9615
|
const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
|
|
9176
9616
|
const geminiNativeTools = toGeminiTools(request.modelTools);
|
|
9177
9617
|
const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
|
|
9178
|
-
const geminiContents = contents.map(
|
|
9618
|
+
const geminiContents = contents.map(
|
|
9619
|
+
(content) => convertLlmContentToGeminiContent(content, {
|
|
9620
|
+
defaultMediaResolution: request.mediaResolution
|
|
9621
|
+
})
|
|
9622
|
+
);
|
|
9179
9623
|
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
9180
9624
|
const turn = stepIndex + 1;
|
|
9181
9625
|
const stepStartedAtMs = Date.now();
|
|
@@ -9193,6 +9637,7 @@ async function runToolLoop(request) {
|
|
|
9193
9637
|
}
|
|
9194
9638
|
};
|
|
9195
9639
|
const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
|
|
9640
|
+
const mediaResolution = toGeminiMediaResolution(request.mediaResolution);
|
|
9196
9641
|
const config = {
|
|
9197
9642
|
maxOutputTokens: 32e3,
|
|
9198
9643
|
tools: geminiTools,
|
|
@@ -9201,7 +9646,8 @@ async function runToolLoop(request) {
|
|
|
9201
9646
|
mode: import_genai2.FunctionCallingConfigMode.VALIDATED
|
|
9202
9647
|
}
|
|
9203
9648
|
},
|
|
9204
|
-
...thinkingConfig ? { thinkingConfig } : {}
|
|
9649
|
+
...thinkingConfig ? { thinkingConfig } : {},
|
|
9650
|
+
...mediaResolution ? { mediaResolution } : {}
|
|
9205
9651
|
};
|
|
9206
9652
|
const onEvent = request.onEvent;
|
|
9207
9653
|
const preparedGeminiContents = await maybePrepareGeminiPromptContents(geminiContents);
|
|
@@ -9357,7 +9803,13 @@ async function runToolLoop(request) {
|
|
|
9357
9803
|
} else if (response.responseText.length > 0) {
|
|
9358
9804
|
geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
|
|
9359
9805
|
}
|
|
9360
|
-
geminiContents.push(
|
|
9806
|
+
geminiContents.push(
|
|
9807
|
+
...steeringInput2.map(
|
|
9808
|
+
(content) => convertLlmContentToGeminiContent(content, {
|
|
9809
|
+
defaultMediaResolution: request.mediaResolution
|
|
9810
|
+
})
|
|
9811
|
+
)
|
|
9812
|
+
);
|
|
9361
9813
|
continue;
|
|
9362
9814
|
}
|
|
9363
9815
|
const toolCalls = [];
|
|
@@ -9449,7 +9901,8 @@ async function runToolLoop(request) {
|
|
|
9449
9901
|
...buildGeminiFunctionResponseParts({
|
|
9450
9902
|
toolName: entry.toolName,
|
|
9451
9903
|
callId: entry.call.id,
|
|
9452
|
-
outputPayload
|
|
9904
|
+
outputPayload,
|
|
9905
|
+
defaultMediaResolution: request.mediaResolution
|
|
9453
9906
|
})
|
|
9454
9907
|
);
|
|
9455
9908
|
}
|
|
@@ -9494,7 +9947,13 @@ async function runToolLoop(request) {
|
|
|
9494
9947
|
geminiContents.push({ role: "user", parts: responseParts });
|
|
9495
9948
|
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
9496
9949
|
if (steeringInput.length > 0) {
|
|
9497
|
-
geminiContents.push(
|
|
9950
|
+
geminiContents.push(
|
|
9951
|
+
...steeringInput.map(
|
|
9952
|
+
(content) => convertLlmContentToGeminiContent(content, {
|
|
9953
|
+
defaultMediaResolution: request.mediaResolution
|
|
9954
|
+
})
|
|
9955
|
+
)
|
|
9956
|
+
);
|
|
9498
9957
|
}
|
|
9499
9958
|
} catch (error) {
|
|
9500
9959
|
stepCallLogger?.fail(error, {
|
|
@@ -9750,7 +10209,7 @@ async function generateImages(request) {
|
|
|
9750
10209
|
}
|
|
9751
10210
|
return image;
|
|
9752
10211
|
})(),
|
|
9753
|
-
model: "gpt-5.
|
|
10212
|
+
model: "gpt-5.4-mini"
|
|
9754
10213
|
})
|
|
9755
10214
|
)
|
|
9756
10215
|
);
|
|
@@ -10056,7 +10515,6 @@ function resolveSubagentToolConfig(selection, currentDepth) {
|
|
|
10056
10515
|
maxWaitTimeoutMs,
|
|
10057
10516
|
promptPattern,
|
|
10058
10517
|
...instructions ? { instructions } : {},
|
|
10059
|
-
...config.model ? { model: config.model } : {},
|
|
10060
10518
|
...maxSteps ? { maxSteps } : {},
|
|
10061
10519
|
inheritTools: config.inheritTools !== false,
|
|
10062
10520
|
inheritFilesystemTool: config.inheritFilesystemTool !== false
|
|
@@ -10108,7 +10566,6 @@ function createSubagentToolController(options) {
|
|
|
10108
10566
|
`Subagent depth limit reached (${options.config.maxDepth}). Cannot spawn at depth ${childDepth}.`
|
|
10109
10567
|
);
|
|
10110
10568
|
}
|
|
10111
|
-
const model = options.config.model ?? options.parentModel;
|
|
10112
10569
|
const id = `agent_${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
|
|
10113
10570
|
const now = Date.now();
|
|
10114
10571
|
const { roleName, roleInstructions } = resolveAgentType(input.agent_type);
|
|
@@ -10128,7 +10585,7 @@ function createSubagentToolController(options) {
|
|
|
10128
10585
|
const agent = {
|
|
10129
10586
|
id,
|
|
10130
10587
|
depth: childDepth,
|
|
10131
|
-
model,
|
|
10588
|
+
model: options.parentModel,
|
|
10132
10589
|
...nickname ? { nickname } : {},
|
|
10133
10590
|
agentRole: roleName,
|
|
10134
10591
|
status: "idle",
|
|
@@ -11883,7 +12340,8 @@ async function viewImageCodex(input, options) {
|
|
|
11883
12340
|
return [
|
|
11884
12341
|
{
|
|
11885
12342
|
type: "input_image",
|
|
11886
|
-
image_url: `data:${mimeType};base64,${bytes.toString("base64")}
|
|
12343
|
+
image_url: `data:${mimeType};base64,${bytes.toString("base64")}`,
|
|
12344
|
+
...options.mediaResolution ? { detail: options.mediaResolution } : {}
|
|
11887
12345
|
}
|
|
11888
12346
|
];
|
|
11889
12347
|
}
|
|
@@ -12563,7 +13021,11 @@ async function runAgentLoopInternal(request, context) {
|
|
|
12563
13021
|
const toolLoopRequestWithSteering = toolLoopRequest.steering === steeringChannel ? toolLoopRequest : { ...toolLoopRequest, steering: steeringChannel };
|
|
12564
13022
|
const filesystemSelection = filesystemTool ?? filesystem_tool;
|
|
12565
13023
|
const subagentSelection = subagentTool ?? subagent_tool ?? subagents;
|
|
12566
|
-
const filesystemTools = resolveFilesystemTools(
|
|
13024
|
+
const filesystemTools = resolveFilesystemTools(
|
|
13025
|
+
request.model,
|
|
13026
|
+
filesystemSelection,
|
|
13027
|
+
request.mediaResolution
|
|
13028
|
+
);
|
|
12567
13029
|
const resolvedSubagentConfig = resolveSubagentToolConfig(subagentSelection, context.depth);
|
|
12568
13030
|
const subagentController = createSubagentController({
|
|
12569
13031
|
runId,
|
|
@@ -12715,24 +13177,47 @@ async function runAgentLoopInternal(request, context) {
|
|
|
12715
13177
|
await subagentController?.closeAll();
|
|
12716
13178
|
}
|
|
12717
13179
|
}
|
|
12718
|
-
function resolveFilesystemTools(model, selection) {
|
|
13180
|
+
function resolveFilesystemTools(model, selection, defaultMediaResolution) {
|
|
13181
|
+
const withDefaultMediaResolution = (options) => {
|
|
13182
|
+
if (defaultMediaResolution === void 0) {
|
|
13183
|
+
return options;
|
|
13184
|
+
}
|
|
13185
|
+
return {
|
|
13186
|
+
mediaResolution: defaultMediaResolution,
|
|
13187
|
+
...options ?? {}
|
|
13188
|
+
};
|
|
13189
|
+
};
|
|
12719
13190
|
if (selection === void 0 || selection === false) {
|
|
12720
13191
|
return {};
|
|
12721
13192
|
}
|
|
12722
13193
|
if (selection === true) {
|
|
12723
|
-
return createFilesystemToolSetForModel(model,
|
|
13194
|
+
return createFilesystemToolSetForModel(model, withDefaultMediaResolution(void 0) ?? {});
|
|
12724
13195
|
}
|
|
12725
13196
|
if (typeof selection === "string") {
|
|
12726
|
-
return createFilesystemToolSetForModel(model, selection);
|
|
13197
|
+
return createFilesystemToolSetForModel(model, selection, withDefaultMediaResolution(void 0));
|
|
12727
13198
|
}
|
|
12728
13199
|
if (selection.enabled === false) {
|
|
12729
13200
|
return {};
|
|
12730
13201
|
}
|
|
12731
13202
|
if (selection.options && selection.profile !== void 0) {
|
|
12732
|
-
return createFilesystemToolSetForModel(
|
|
13203
|
+
return createFilesystemToolSetForModel(
|
|
13204
|
+
model,
|
|
13205
|
+
selection.profile,
|
|
13206
|
+
withDefaultMediaResolution(selection.options)
|
|
13207
|
+
);
|
|
12733
13208
|
}
|
|
12734
13209
|
if (selection.options) {
|
|
12735
|
-
return createFilesystemToolSetForModel(
|
|
13210
|
+
return createFilesystemToolSetForModel(
|
|
13211
|
+
model,
|
|
13212
|
+
withDefaultMediaResolution(selection.options) ?? {}
|
|
13213
|
+
);
|
|
13214
|
+
}
|
|
13215
|
+
if (defaultMediaResolution !== void 0) {
|
|
13216
|
+
return createFilesystemToolSetForModel(
|
|
13217
|
+
model,
|
|
13218
|
+
selection.profile ?? "auto",
|
|
13219
|
+
withDefaultMediaResolution(void 0)
|
|
13220
|
+
);
|
|
12736
13221
|
}
|
|
12737
13222
|
return createFilesystemToolSetForModel(model, selection.profile ?? "auto");
|
|
12738
13223
|
}
|
|
@@ -12755,7 +13240,7 @@ function createSubagentController(params) {
|
|
|
12755
13240
|
return createSubagentToolController({
|
|
12756
13241
|
config: params.resolvedSubagentConfig,
|
|
12757
13242
|
parentDepth: params.depth,
|
|
12758
|
-
parentModel: params.
|
|
13243
|
+
parentModel: params.model,
|
|
12759
13244
|
forkContextMessages: normalizeForkContextMessages(params.toolLoopRequest.input),
|
|
12760
13245
|
onBackgroundMessage: (message) => {
|
|
12761
13246
|
params.steering?.append({ role: "user", content: message });
|
|
@@ -12775,6 +13260,7 @@ function createSubagentController(params) {
|
|
|
12775
13260
|
modelTools: params.toolLoopRequest.modelTools,
|
|
12776
13261
|
maxSteps: subagentRequest.maxSteps,
|
|
12777
13262
|
thinkingLevel: params.toolLoopRequest.thinkingLevel,
|
|
13263
|
+
mediaResolution: params.toolLoopRequest.mediaResolution,
|
|
12778
13264
|
signal: subagentRequest.signal
|
|
12779
13265
|
},
|
|
12780
13266
|
{
|