@ljoukov/llm 4.0.12 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION: () => CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
35
35
  CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION: () => CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
36
36
  CODEX_APPLY_PATCH_LARK_GRAMMAR: () => CODEX_APPLY_PATCH_LARK_GRAMMAR,
37
+ DEFAULT_FILE_TTL_SECONDS: () => DEFAULT_FILE_TTL_SECONDS,
37
38
  FIREWORKS_DEFAULT_GLM_MODEL: () => FIREWORKS_DEFAULT_GLM_MODEL,
38
39
  FIREWORKS_DEFAULT_GPT_OSS_120B_MODEL: () => FIREWORKS_DEFAULT_GPT_OSS_120B_MODEL,
39
40
  FIREWORKS_DEFAULT_KIMI_MODEL: () => FIREWORKS_DEFAULT_KIMI_MODEL,
@@ -74,10 +75,12 @@ __export(index_exports, {
74
75
  createViewImageTool: () => createViewImageTool,
75
76
  createWriteFileTool: () => createWriteFileTool,
76
77
  customTool: () => customTool,
78
+ emptyFileUploadMetrics: () => emptyFileUploadMetrics,
77
79
  encodeChatGptAuthJson: () => encodeChatGptAuthJson,
78
80
  encodeChatGptAuthJsonB64: () => encodeChatGptAuthJsonB64,
79
81
  estimateCallCostUsd: () => estimateCallCostUsd,
80
82
  exchangeChatGptOauthCode: () => exchangeChatGptOauthCode,
83
+ files: () => files,
81
84
  generateImageInBatches: () => generateImageInBatches,
82
85
  generateImages: () => generateImages,
83
86
  generateJson: () => generateJson,
@@ -115,9 +118,10 @@ __export(index_exports, {
115
118
  module.exports = __toCommonJS(index_exports);
116
119
 
117
120
  // src/llm.ts
118
- var import_node_buffer3 = require("buffer");
119
- var import_node_async_hooks2 = require("async_hooks");
120
- var import_node_crypto = require("crypto");
121
+ var import_node_buffer4 = require("buffer");
122
+ var import_node_async_hooks3 = require("async_hooks");
123
+ var import_node_crypto2 = require("crypto");
124
+ var import_node_path5 = __toESM(require("path"), 1);
121
125
  var import_genai2 = require("@google/genai");
122
126
  var import_zod_to_json_schema = require("@alcyone-labs/zod-to-json-schema");
123
127
  var import_zod3 = require("zod");
@@ -2357,6 +2361,14 @@ function normaliseConfigValue(value) {
2357
2361
  const trimmed = value.trim();
2358
2362
  return trimmed.length > 0 ? trimmed : void 0;
2359
2363
  }
2364
+ function resolveGeminiApiKey() {
2365
+ loadLocalEnv();
2366
+ const raw = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
2367
+ return normaliseConfigValue(raw);
2368
+ }
2369
+ function getGeminiBackend() {
2370
+ return resolveGeminiApiKey() ? "api" : "vertex";
2371
+ }
2360
2372
  function configureGemini(options = {}) {
2361
2373
  const nextProjectId = normaliseConfigValue(options.projectId);
2362
2374
  const nextLocation = normaliseConfigValue(options.location);
@@ -2384,6 +2396,10 @@ function resolveLocation() {
2384
2396
  async function getGeminiClient() {
2385
2397
  if (!geminiClientState.clientPromise) {
2386
2398
  geminiClientState.clientPromise = Promise.resolve().then(() => {
2399
+ const apiKey = resolveGeminiApiKey();
2400
+ if (apiKey) {
2401
+ return new import_genai.GoogleGenAI({ apiKey });
2402
+ }
2387
2403
  const projectId = resolveProjectId();
2388
2404
  const location = resolveLocation();
2389
2405
  const googleAuthOptions = getGoogleAuthOptions(CLOUD_PLATFORM_SCOPE);
@@ -3350,14 +3366,708 @@ function getCurrentAgentLoggingSession() {
3350
3366
  return loggingSessionStorage.getStore();
3351
3367
  }
3352
3368
 
3369
+ // src/files.ts
3370
+ var import_node_async_hooks2 = require("async_hooks");
3371
+ var import_node_buffer3 = require("buffer");
3372
+ var import_node_crypto = require("crypto");
3373
+ var import_node_fs3 = require("fs");
3374
+ var import_promises2 = require("fs/promises");
3375
+ var import_node_os3 = __toESM(require("os"), 1);
3376
+ var import_node_path4 = __toESM(require("path"), 1);
3377
+ var import_node_stream = require("stream");
3378
+ var import_promises3 = require("stream/promises");
3379
+ var import_storage = require("@google-cloud/storage");
3380
+ var import_mime = __toESM(require("mime"), 1);
3381
+ var DEFAULT_FILE_TTL_SECONDS = 48 * 60 * 60;
3382
+ var OPENAI_FILE_CREATE_MAX_BYTES = 512 * 1024 * 1024;
3383
+ var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
3384
+ var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
3385
+ var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
3386
+ var FILES_TEMP_ROOT = import_node_path4.default.join(import_node_os3.default.tmpdir(), "ljoukov-llm-files");
3387
+ var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
3388
+ metadataById: /* @__PURE__ */ new Map(),
3389
+ openAiUploadCacheByKey: /* @__PURE__ */ new Map(),
3390
+ materializedById: /* @__PURE__ */ new Map(),
3391
+ geminiMirrorById: /* @__PURE__ */ new Map(),
3392
+ vertexMirrorById: /* @__PURE__ */ new Map(),
3393
+ storageClient: void 0,
3394
+ geminiClientPromise: void 0
3395
+ }));
3396
+ var fileUploadScopeStorage = getRuntimeSingleton(
3397
+ /* @__PURE__ */ Symbol.for("@ljoukov/llm.fileUploadScopeStorage"),
3398
+ () => new import_node_async_hooks2.AsyncLocalStorage()
3399
+ );
3400
+ function summarizeUploadEvents(events) {
3401
+ let totalBytes = 0;
3402
+ let totalLatencyMs = 0;
3403
+ for (const event of events) {
3404
+ totalBytes += Math.max(0, event.bytes);
3405
+ totalLatencyMs += Math.max(0, event.durationMs);
3406
+ }
3407
+ return {
3408
+ count: events.length,
3409
+ totalBytes,
3410
+ totalLatencyMs,
3411
+ events: Array.from(events)
3412
+ };
3413
+ }
3414
+ function emptyFileUploadMetrics() {
3415
+ return summarizeUploadEvents([]);
3416
+ }
3417
+ function getCurrentFileUploadMetrics() {
3418
+ const collector = fileUploadScopeStorage.getStore()?.collectors.at(-1);
3419
+ return summarizeUploadEvents(collector?.events ?? []);
3420
+ }
3421
+ async function collectFileUploadMetrics(fn) {
3422
+ const parent = fileUploadScopeStorage.getStore();
3423
+ const collector = { events: [] };
3424
+ const scope = {
3425
+ collectors: [...parent?.collectors ?? [], collector],
3426
+ source: parent?.source
3427
+ };
3428
+ return await fileUploadScopeStorage.run(scope, async () => {
3429
+ const result = await fn();
3430
+ return {
3431
+ result,
3432
+ uploads: summarizeUploadEvents(collector.events)
3433
+ };
3434
+ });
3435
+ }
3436
+ async function runWithFileUploadSource(source, fn) {
3437
+ const parent = fileUploadScopeStorage.getStore();
3438
+ const scope = {
3439
+ collectors: parent?.collectors ?? [],
3440
+ source
3441
+ };
3442
+ return await fileUploadScopeStorage.run(scope, fn);
3443
+ }
3444
+ function formatUploadLogLine(event) {
3445
+ const parts = [
3446
+ "[upload]",
3447
+ `source=${event.source}`,
3448
+ `backend=${event.backend}`,
3449
+ `mode=${event.mode}`,
3450
+ `filename=${JSON.stringify(event.filename)}`,
3451
+ `bytes=${event.bytes.toString()}`,
3452
+ `durationMs=${event.durationMs.toString()}`
3453
+ ];
3454
+ if (event.mimeType) {
3455
+ parts.push(`mimeType=${event.mimeType}`);
3456
+ }
3457
+ if (event.fileId) {
3458
+ parts.push(`fileId=${event.fileId}`);
3459
+ }
3460
+ if (event.mirrorId) {
3461
+ parts.push(`mirrorId=${event.mirrorId}`);
3462
+ }
3463
+ if (event.fileUri) {
3464
+ parts.push(`fileUri=${JSON.stringify(event.fileUri)}`);
3465
+ }
3466
+ return parts.join(" ");
3467
+ }
3468
+ function recordUploadEvent(event) {
3469
+ const scope = fileUploadScopeStorage.getStore();
3470
+ const resolvedSource = event.source ?? scope?.source ?? (event.backend === "openai" ? "files_api" : "provider_mirror");
3471
+ const timestampedEvent = {
3472
+ ...event,
3473
+ source: resolvedSource,
3474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3475
+ };
3476
+ for (const collector of scope?.collectors ?? []) {
3477
+ collector.events.push(timestampedEvent);
3478
+ }
3479
+ getCurrentAgentLoggingSession()?.logLine(formatUploadLogLine(timestampedEvent));
3480
+ }
3481
+ function normaliseFilename(filename, fallback = "attachment.bin") {
3482
+ const trimmed = filename?.trim();
3483
+ if (!trimmed) {
3484
+ return fallback;
3485
+ }
3486
+ const basename = import_node_path4.default.basename(trimmed);
3487
+ return basename.length > 0 ? basename : fallback;
3488
+ }
3489
+ function resolveMimeType(filename, explicitMimeType, fallback = "application/octet-stream") {
3490
+ const trimmed = explicitMimeType?.trim();
3491
+ if (trimmed) {
3492
+ return trimmed;
3493
+ }
3494
+ const inferred = import_mime.default.getType(filename);
3495
+ return typeof inferred === "string" && inferred.length > 0 ? inferred : fallback;
3496
+ }
3497
+ function toBuffer(data) {
3498
+ if (typeof data === "string") {
3499
+ return import_node_buffer3.Buffer.from(data, "utf8");
3500
+ }
3501
+ if (ArrayBuffer.isView(data)) {
3502
+ return import_node_buffer3.Buffer.from(data.buffer, data.byteOffset, data.byteLength);
3503
+ }
3504
+ return import_node_buffer3.Buffer.from(data);
3505
+ }
3506
+ function computeSha256Hex(buffer) {
3507
+ return (0, import_node_crypto.createHash)("sha256").update(buffer).digest("hex");
3508
+ }
3509
+ async function computeFileSha256Hex(filePath) {
3510
+ const hash = (0, import_node_crypto.createHash)("sha256");
3511
+ const stream = (0, import_node_fs3.createReadStream)(filePath);
3512
+ for await (const chunk of stream) {
3513
+ hash.update(chunk);
3514
+ }
3515
+ return hash.digest("hex");
3516
+ }
3517
+ function toStoredFile(file) {
3518
+ return {
3519
+ id: file.id,
3520
+ bytes: file.bytes,
3521
+ created_at: file.created_at,
3522
+ filename: file.filename,
3523
+ object: "file",
3524
+ purpose: file.purpose,
3525
+ status: file.status,
3526
+ expires_at: file.expires_at
3527
+ };
3528
+ }
3529
+ function buildCacheKey(filename, mimeType, sha256Hex) {
3530
+ return `${sha256Hex}\0${filename}\0${mimeType}`;
3531
+ }
3532
+ function isFresh(file) {
3533
+ if (!file.expires_at) {
3534
+ return true;
3535
+ }
3536
+ return file.expires_at * 1e3 > Date.now() + 3e4;
3537
+ }
3538
+ function recordMetadata(metadata) {
3539
+ filesState.metadataById.set(metadata.file.id, metadata);
3540
+ if (metadata.sha256Hex) {
3541
+ filesState.openAiUploadCacheByKey.set(
3542
+ buildCacheKey(
3543
+ metadata.filename,
3544
+ metadata.mimeType ?? "application/octet-stream",
3545
+ metadata.sha256Hex
3546
+ ),
3547
+ metadata
3548
+ );
3549
+ }
3550
+ return metadata;
3551
+ }
3552
+ async function uploadOpenAiFileFromBytes(params) {
3553
+ const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3554
+ const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
3555
+ if (cached && isFresh(cached.file)) {
3556
+ return cached;
3557
+ }
3558
+ const client = getOpenAiClient();
3559
+ const startedAtMs = Date.now();
3560
+ let uploaded;
3561
+ let mode;
3562
+ if (params.bytes.byteLength <= OPENAI_FILE_CREATE_MAX_BYTES) {
3563
+ mode = "files.create";
3564
+ uploaded = await client.files.create({
3565
+ file: new import_node_buffer3.File([new Uint8Array(params.bytes)], params.filename, {
3566
+ type: params.mimeType
3567
+ }),
3568
+ purpose: params.purpose,
3569
+ expires_after: {
3570
+ anchor: "created_at",
3571
+ seconds: params.expiresAfterSeconds
3572
+ }
3573
+ });
3574
+ } else {
3575
+ mode = "uploads";
3576
+ const upload = await client.uploads.create({
3577
+ bytes: params.bytes.byteLength,
3578
+ filename: params.filename,
3579
+ mime_type: params.mimeType,
3580
+ purpose: params.purpose
3581
+ });
3582
+ const partIds = [];
3583
+ for (let offset = 0; offset < params.bytes.byteLength; offset += OPENAI_UPLOAD_PART_MAX_BYTES) {
3584
+ const chunk = params.bytes.subarray(
3585
+ offset,
3586
+ Math.min(offset + OPENAI_UPLOAD_PART_MAX_BYTES, params.bytes.byteLength)
3587
+ );
3588
+ const uploadPart = await client.uploads.parts.create(upload.id, {
3589
+ data: new import_node_buffer3.File([new Uint8Array(chunk)], `${params.sha256Hex}.part`, {
3590
+ type: params.mimeType
3591
+ })
3592
+ });
3593
+ partIds.push(uploadPart.id);
3594
+ }
3595
+ const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
3596
+ const fileId = completed.file?.id;
3597
+ if (!fileId) {
3598
+ throw new Error("OpenAI upload completed without a file id.");
3599
+ }
3600
+ uploaded = await client.files.retrieve(fileId);
3601
+ }
3602
+ const file = toStoredFile(uploaded);
3603
+ const metadata = recordMetadata({
3604
+ file,
3605
+ filename: file.filename,
3606
+ bytes: file.bytes,
3607
+ mimeType: params.mimeType,
3608
+ sha256Hex: params.sha256Hex
3609
+ });
3610
+ recordUploadEvent({
3611
+ backend: "openai",
3612
+ mode,
3613
+ filename: metadata.filename,
3614
+ bytes: metadata.bytes,
3615
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3616
+ mimeType: params.mimeType,
3617
+ fileId: metadata.file.id
3618
+ });
3619
+ return metadata;
3620
+ }
3621
+ async function uploadOpenAiFileFromPath(params) {
3622
+ const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3623
+ const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
3624
+ if (cached && isFresh(cached.file)) {
3625
+ return cached;
3626
+ }
3627
+ const client = getOpenAiClient();
3628
+ const startedAtMs = Date.now();
3629
+ let uploaded;
3630
+ let mode;
3631
+ if (params.bytes <= OPENAI_FILE_CREATE_MAX_BYTES) {
3632
+ mode = "files.create";
3633
+ const blob = await (0, import_node_fs3.openAsBlob)(params.filePath, { type: params.mimeType });
3634
+ uploaded = await client.files.create({
3635
+ file: new import_node_buffer3.File([blob], params.filename, { type: params.mimeType }),
3636
+ purpose: params.purpose,
3637
+ expires_after: {
3638
+ anchor: "created_at",
3639
+ seconds: params.expiresAfterSeconds
3640
+ }
3641
+ });
3642
+ } else {
3643
+ mode = "uploads";
3644
+ const upload = await client.uploads.create({
3645
+ bytes: params.bytes,
3646
+ filename: params.filename,
3647
+ mime_type: params.mimeType,
3648
+ purpose: params.purpose
3649
+ });
3650
+ const partIds = [];
3651
+ const stream = (0, import_node_fs3.createReadStream)(params.filePath, {
3652
+ highWaterMark: OPENAI_UPLOAD_PART_MAX_BYTES
3653
+ });
3654
+ let partIndex = 0;
3655
+ for await (const chunk of stream) {
3656
+ const buffer = import_node_buffer3.Buffer.isBuffer(chunk) ? chunk : import_node_buffer3.Buffer.from(chunk);
3657
+ const uploadPart = await client.uploads.parts.create(upload.id, {
3658
+ data: new import_node_buffer3.File(
3659
+ [new Uint8Array(buffer)],
3660
+ `${params.sha256Hex}.${partIndex.toString()}.part`,
3661
+ {
3662
+ type: params.mimeType
3663
+ }
3664
+ )
3665
+ });
3666
+ partIds.push(uploadPart.id);
3667
+ partIndex += 1;
3668
+ }
3669
+ const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
3670
+ const fileId = completed.file?.id;
3671
+ if (!fileId) {
3672
+ throw new Error("OpenAI upload completed without a file id.");
3673
+ }
3674
+ uploaded = await client.files.retrieve(fileId);
3675
+ }
3676
+ const file = toStoredFile(uploaded);
3677
+ const metadata = recordMetadata({
3678
+ file,
3679
+ filename: file.filename,
3680
+ bytes: file.bytes,
3681
+ mimeType: params.mimeType,
3682
+ sha256Hex: params.sha256Hex
3683
+ });
3684
+ recordUploadEvent({
3685
+ backend: "openai",
3686
+ mode,
3687
+ filename: metadata.filename,
3688
+ bytes: metadata.bytes,
3689
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3690
+ mimeType: params.mimeType,
3691
+ fileId: metadata.file.id
3692
+ });
3693
+ return metadata;
3694
+ }
3695
+ async function retrieveOpenAiFile(fileId) {
3696
+ const cached = filesState.metadataById.get(fileId);
3697
+ if (cached && isFresh(cached.file)) {
3698
+ return cached;
3699
+ }
3700
+ const client = getOpenAiClient();
3701
+ const retrieved = await client.files.retrieve(fileId);
3702
+ const file = toStoredFile(retrieved);
3703
+ return recordMetadata({
3704
+ file,
3705
+ filename: file.filename,
3706
+ bytes: file.bytes,
3707
+ mimeType: cached?.mimeType ?? resolveMimeType(file.filename, void 0),
3708
+ sha256Hex: cached?.sha256Hex,
3709
+ localPath: cached?.localPath
3710
+ });
3711
+ }
3712
+ function buildGeminiMirrorName(sha256Hex) {
3713
+ return `files/${sha256Hex.slice(0, 40)}`;
3714
+ }
3715
+ async function waitForGeminiFileActive(client, name) {
3716
+ const startedAt = Date.now();
3717
+ while (true) {
3718
+ const file = await client.files.get({ name });
3719
+ if (!file.state || file.state === "ACTIVE") {
3720
+ return;
3721
+ }
3722
+ if (file.state === "FAILED") {
3723
+ throw new Error(file.error?.message ?? `Gemini file ${name} failed processing.`);
3724
+ }
3725
+ if (Date.now() - startedAt >= GEMINI_FILE_POLL_TIMEOUT_MS) {
3726
+ throw new Error(`Timed out waiting for Gemini file ${name} to become active.`);
3727
+ }
3728
+ await new Promise((resolve) => setTimeout(resolve, GEMINI_FILE_POLL_INTERVAL_MS));
3729
+ }
3730
+ }
3731
+ function resolveVertexMirrorBucket() {
3732
+ const raw = process.env.VERTEX_GCS_BUCKET ?? process.env.LLM_VERTEX_GCS_BUCKET;
3733
+ const trimmed = raw?.trim();
3734
+ if (!trimmed) {
3735
+ throw new Error(
3736
+ "VERTEX_GCS_BUCKET must be set to use OpenAI-backed file ids with Vertex Gemini models."
3737
+ );
3738
+ }
3739
+ return trimmed.replace(/^gs:\/\//u, "").replace(/\/+$/u, "");
3740
+ }
3741
+ function resolveVertexMirrorPrefix() {
3742
+ const raw = process.env.VERTEX_GCS_PREFIX ?? process.env.LLM_VERTEX_GCS_PREFIX;
3743
+ const trimmed = raw?.trim().replace(/^\/+/u, "").replace(/\/+$/u, "");
3744
+ return trimmed ? `${trimmed}/` : "";
3745
+ }
3746
+ function getStorageClient() {
3747
+ if (filesState.storageClient) {
3748
+ return filesState.storageClient;
3749
+ }
3750
+ const serviceAccount = getGoogleServiceAccount();
3751
+ filesState.storageClient = new import_storage.Storage({
3752
+ projectId: serviceAccount.projectId,
3753
+ credentials: {
3754
+ client_email: serviceAccount.clientEmail,
3755
+ private_key: serviceAccount.privateKey
3756
+ }
3757
+ });
3758
+ return filesState.storageClient;
3759
+ }
3760
+ function getGeminiMirrorClient() {
3761
+ if (!filesState.geminiClientPromise) {
3762
+ filesState.geminiClientPromise = getGeminiClient();
3763
+ }
3764
+ return filesState.geminiClientPromise;
3765
+ }
3766
+ async function materializeOpenAiFile(fileId) {
3767
+ const cachedPromise = filesState.materializedById.get(fileId);
3768
+ if (cachedPromise) {
3769
+ return await cachedPromise;
3770
+ }
3771
+ const promise = (async () => {
3772
+ const metadata = await retrieveOpenAiFile(fileId);
3773
+ if (metadata.localPath && metadata.sha256Hex && metadata.mimeType) {
3774
+ return {
3775
+ file: metadata.file,
3776
+ filename: metadata.filename,
3777
+ bytes: metadata.bytes,
3778
+ mimeType: metadata.mimeType,
3779
+ sha256Hex: metadata.sha256Hex,
3780
+ localPath: metadata.localPath
3781
+ };
3782
+ }
3783
+ await (0, import_promises2.mkdir)(FILES_TEMP_ROOT, { recursive: true });
3784
+ const tempDir = await (0, import_promises2.mkdtemp)(
3785
+ import_node_path4.default.join(FILES_TEMP_ROOT, `${fileId.replace(/[^a-z0-9_-]/giu, "")}-`)
3786
+ );
3787
+ const localPath = import_node_path4.default.join(tempDir, normaliseFilename(metadata.filename, `${fileId}.bin`));
3788
+ const response = await getOpenAiClient().files.content(fileId);
3789
+ if (!response.ok) {
3790
+ throw new Error(
3791
+ `Failed to download OpenAI file ${fileId}: ${response.status} ${response.statusText}`
3792
+ );
3793
+ }
3794
+ const responseMimeType = response.headers.get("content-type")?.trim() || void 0;
3795
+ const mimeType = resolveMimeType(metadata.filename, responseMimeType);
3796
+ const hash = (0, import_node_crypto.createHash)("sha256");
3797
+ let bytes = 0;
3798
+ if (response.body) {
3799
+ const source = import_node_stream.Readable.fromWeb(response.body);
3800
+ const writable = (0, import_node_fs3.createWriteStream)(localPath, { flags: "wx" });
3801
+ source.on("data", (chunk) => {
3802
+ const buffer = import_node_buffer3.Buffer.isBuffer(chunk) ? chunk : import_node_buffer3.Buffer.from(chunk);
3803
+ hash.update(buffer);
3804
+ bytes += buffer.byteLength;
3805
+ });
3806
+ await (0, import_promises3.pipeline)(source, writable);
3807
+ } else {
3808
+ const buffer = import_node_buffer3.Buffer.from(await response.arrayBuffer());
3809
+ hash.update(buffer);
3810
+ bytes = buffer.byteLength;
3811
+ await (0, import_promises2.writeFile)(localPath, buffer);
3812
+ }
3813
+ const sha256Hex = hash.digest("hex");
3814
+ const updated = recordMetadata({
3815
+ file: metadata.file,
3816
+ filename: metadata.filename,
3817
+ bytes: bytes || metadata.bytes,
3818
+ mimeType,
3819
+ sha256Hex,
3820
+ localPath
3821
+ });
3822
+ return {
3823
+ file: updated.file,
3824
+ filename: updated.filename,
3825
+ bytes: updated.bytes,
3826
+ mimeType: updated.mimeType ?? mimeType,
3827
+ sha256Hex,
3828
+ localPath
3829
+ };
3830
+ })();
3831
+ filesState.materializedById.set(fileId, promise);
3832
+ try {
3833
+ return await promise;
3834
+ } catch (error) {
3835
+ filesState.materializedById.delete(fileId);
3836
+ throw error;
3837
+ }
3838
+ }
3839
+ async function ensureGeminiFileMirror(fileId) {
3840
+ const cached = filesState.geminiMirrorById.get(fileId);
3841
+ if (cached) {
3842
+ return cached;
3843
+ }
3844
+ const materialized = await materializeOpenAiFile(fileId);
3845
+ const client = await getGeminiMirrorClient();
3846
+ const name = buildGeminiMirrorName(materialized.sha256Hex);
3847
+ try {
3848
+ const existing = await client.files.get({ name });
3849
+ if (existing.name && existing.uri && existing.mimeType) {
3850
+ const mirror2 = {
3851
+ openAiFileId: fileId,
3852
+ name: existing.name,
3853
+ uri: existing.uri,
3854
+ mimeType: existing.mimeType,
3855
+ displayName: existing.displayName ?? materialized.filename
3856
+ };
3857
+ filesState.geminiMirrorById.set(fileId, mirror2);
3858
+ return mirror2;
3859
+ }
3860
+ } catch {
3861
+ }
3862
+ const startedAtMs = Date.now();
3863
+ const uploaded = await client.files.upload({
3864
+ file: materialized.localPath,
3865
+ config: {
3866
+ name,
3867
+ mimeType: materialized.mimeType,
3868
+ displayName: materialized.filename
3869
+ }
3870
+ });
3871
+ if (uploaded.name && uploaded.state && uploaded.state !== "ACTIVE") {
3872
+ await waitForGeminiFileActive(client, uploaded.name);
3873
+ }
3874
+ const resolved = await client.files.get({ name: uploaded.name ?? name });
3875
+ if (!resolved.name || !resolved.uri || !resolved.mimeType) {
3876
+ throw new Error("Gemini file upload completed without a usable URI.");
3877
+ }
3878
+ const mirror = {
3879
+ openAiFileId: fileId,
3880
+ name: resolved.name,
3881
+ uri: resolved.uri,
3882
+ mimeType: resolved.mimeType,
3883
+ displayName: resolved.displayName ?? materialized.filename
3884
+ };
3885
+ filesState.geminiMirrorById.set(fileId, mirror);
3886
+ recordUploadEvent({
3887
+ backend: "gemini",
3888
+ mode: "mirror",
3889
+ filename: materialized.filename,
3890
+ bytes: materialized.bytes,
3891
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3892
+ mimeType: materialized.mimeType,
3893
+ fileId,
3894
+ mirrorId: mirror.name,
3895
+ fileUri: mirror.uri
3896
+ });
3897
+ return mirror;
3898
+ }
3899
+ async function ensureVertexFileMirror(fileId) {
3900
+ const cached = filesState.vertexMirrorById.get(fileId);
3901
+ if (cached) {
3902
+ return cached;
3903
+ }
3904
+ const materialized = await materializeOpenAiFile(fileId);
3905
+ const bucketName = resolveVertexMirrorBucket();
3906
+ const prefix = resolveVertexMirrorPrefix();
3907
+ const extension = import_mime.default.getExtension(materialized.mimeType) ?? import_node_path4.default.extname(materialized.filename).replace(/^\./u, "") ?? "bin";
3908
+ const objectName = `${prefix}${materialized.sha256Hex}.${extension}`;
3909
+ const file = getStorageClient().bucket(bucketName).file(objectName);
3910
+ let uploaded = false;
3911
+ const startedAtMs = Date.now();
3912
+ try {
3913
+ await file.getMetadata();
3914
+ } catch (error) {
3915
+ const code = error.code;
3916
+ if (code !== 404 && code !== "404") {
3917
+ throw error;
3918
+ }
3919
+ try {
3920
+ await (0, import_promises3.pipeline)(
3921
+ (0, import_node_fs3.createReadStream)(materialized.localPath),
3922
+ file.createWriteStream({
3923
+ resumable: materialized.bytes >= 10 * 1024 * 1024,
3924
+ preconditionOpts: { ifGenerationMatch: 0 },
3925
+ metadata: {
3926
+ contentType: materialized.mimeType,
3927
+ customTime: (/* @__PURE__ */ new Date()).toISOString(),
3928
+ metadata: {
3929
+ filename: materialized.filename,
3930
+ sha256: materialized.sha256Hex,
3931
+ expiresAt: new Date(Date.now() + DEFAULT_FILE_TTL_SECONDS * 1e3).toISOString()
3932
+ }
3933
+ }
3934
+ })
3935
+ );
3936
+ uploaded = true;
3937
+ } catch (uploadError) {
3938
+ const uploadCode = uploadError.code;
3939
+ if (uploadCode !== 412 && uploadCode !== "412") {
3940
+ throw uploadError;
3941
+ }
3942
+ }
3943
+ }
3944
+ const mirror = {
3945
+ openAiFileId: fileId,
3946
+ bucket: bucketName,
3947
+ objectName,
3948
+ fileUri: `gs://${bucketName}/${objectName}`,
3949
+ mimeType: materialized.mimeType
3950
+ };
3951
+ filesState.vertexMirrorById.set(fileId, mirror);
3952
+ if (uploaded) {
3953
+ recordUploadEvent({
3954
+ backend: "vertex",
3955
+ mode: "mirror",
3956
+ filename: materialized.filename,
3957
+ bytes: materialized.bytes,
3958
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3959
+ mimeType: materialized.mimeType,
3960
+ fileId,
3961
+ mirrorId: mirror.objectName,
3962
+ fileUri: mirror.fileUri
3963
+ });
3964
+ }
3965
+ return mirror;
3966
+ }
3967
+ async function filesCreate(params) {
3968
+ const purpose = params.purpose ?? "user_data";
3969
+ const expiresAfterSeconds = params.expiresAfterSeconds ?? DEFAULT_FILE_TTL_SECONDS;
3970
+ if ("path" in params) {
3971
+ const filePath = import_node_path4.default.resolve(params.path);
3972
+ const info = await (0, import_promises2.stat)(filePath);
3973
+ const filename2 = normaliseFilename(params.filename, import_node_path4.default.basename(filePath));
3974
+ const mimeType2 = resolveMimeType(filename2, params.mimeType);
3975
+ const sha256Hex2 = await computeFileSha256Hex(filePath);
3976
+ const uploaded2 = await uploadOpenAiFileFromPath({
3977
+ filePath,
3978
+ filename: filename2,
3979
+ mimeType: mimeType2,
3980
+ purpose,
3981
+ expiresAfterSeconds,
3982
+ sha256Hex: sha256Hex2,
3983
+ bytes: info.size
3984
+ });
3985
+ return uploaded2.file;
3986
+ }
3987
+ const filename = normaliseFilename(params.filename);
3988
+ const bytes = toBuffer(params.data);
3989
+ const mimeType = resolveMimeType(filename, params.mimeType, "text/plain");
3990
+ const sha256Hex = computeSha256Hex(bytes);
3991
+ const uploaded = await uploadOpenAiFileFromBytes({
3992
+ bytes,
3993
+ filename,
3994
+ mimeType,
3995
+ purpose,
3996
+ expiresAfterSeconds,
3997
+ sha256Hex
3998
+ });
3999
+ return uploaded.file;
4000
+ }
4001
+ async function filesRetrieve(fileId) {
4002
+ return (await retrieveOpenAiFile(fileId)).file;
4003
+ }
4004
+ async function filesDelete(fileId) {
4005
+ const cachedGemini = filesState.geminiMirrorById.get(fileId);
4006
+ if (cachedGemini) {
4007
+ try {
4008
+ const client = await getGeminiMirrorClient();
4009
+ await client.files.delete({ name: cachedGemini.name });
4010
+ } catch {
4011
+ }
4012
+ filesState.geminiMirrorById.delete(fileId);
4013
+ }
4014
+ const cachedVertex = filesState.vertexMirrorById.get(fileId);
4015
+ if (cachedVertex) {
4016
+ try {
4017
+ await getStorageClient().bucket(cachedVertex.bucket).file(cachedVertex.objectName).delete({ ignoreNotFound: true });
4018
+ } catch {
4019
+ }
4020
+ filesState.vertexMirrorById.delete(fileId);
4021
+ }
4022
+ const cachedMaterialized = filesState.metadataById.get(fileId)?.localPath;
4023
+ if (cachedMaterialized) {
4024
+ try {
4025
+ await (0, import_promises2.unlink)(cachedMaterialized);
4026
+ } catch {
4027
+ }
4028
+ }
4029
+ const response = await getOpenAiClient().files.delete(fileId);
4030
+ filesState.metadataById.delete(fileId);
4031
+ filesState.materializedById.delete(fileId);
4032
+ return {
4033
+ id: response.id,
4034
+ deleted: response.deleted,
4035
+ object: "file"
4036
+ };
4037
+ }
4038
+ async function filesContent(fileId) {
4039
+ return await getOpenAiClient().files.content(fileId);
4040
+ }
4041
+ async function getCanonicalFileMetadata(fileId) {
4042
+ const metadata = await retrieveOpenAiFile(fileId);
4043
+ const mimeType = metadata.mimeType ?? resolveMimeType(metadata.filename, void 0);
4044
+ const updated = metadata.mimeType === mimeType ? metadata : recordMetadata({
4045
+ ...metadata,
4046
+ mimeType
4047
+ });
4048
+ return {
4049
+ ...updated,
4050
+ mimeType
4051
+ };
4052
+ }
4053
+ var files = {
4054
+ create: filesCreate,
4055
+ retrieve: filesRetrieve,
4056
+ delete: filesDelete,
4057
+ content: filesContent
4058
+ };
4059
+
3353
4060
  // src/llm.ts
3354
4061
  var toolCallContextStorage = getRuntimeSingleton(
3355
4062
  /* @__PURE__ */ Symbol.for("@ljoukov/llm.toolCallContextStorage"),
3356
- () => new import_node_async_hooks2.AsyncLocalStorage()
4063
+ () => new import_node_async_hooks3.AsyncLocalStorage()
3357
4064
  );
3358
4065
  function getCurrentToolCallContext() {
3359
4066
  return toolCallContextStorage.getStore() ?? null;
3360
4067
  }
4068
+ var INLINE_ATTACHMENT_FILENAME_SYMBOL = /* @__PURE__ */ Symbol.for("@ljoukov/llm.inlineAttachmentFilename");
4069
+ var INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES = 20 * 1024 * 1024;
4070
+ var TOOL_OUTPUT_SPILL_THRESHOLD_BYTES = 1 * 1024 * 1024;
3361
4071
  var LLM_TEXT_MODEL_IDS = [
3362
4072
  ...OPENAI_MODEL_IDS,
3363
4073
  ...CHATGPT_MODEL_IDS,
@@ -3635,6 +4345,52 @@ function isJsonSchemaObject(schema) {
3635
4345
  }
3636
4346
  return false;
3637
4347
  }
4348
+ var CANONICAL_GEMINI_FILE_URI_PREFIX = "openai://file/";
4349
+ function buildCanonicalGeminiFileUri(fileId) {
4350
+ return `${CANONICAL_GEMINI_FILE_URI_PREFIX}${fileId}`;
4351
+ }
4352
+ function parseCanonicalGeminiFileId(fileUri) {
4353
+ if (!fileUri?.startsWith(CANONICAL_GEMINI_FILE_URI_PREFIX)) {
4354
+ return void 0;
4355
+ }
4356
+ const fileId = fileUri.slice(CANONICAL_GEMINI_FILE_URI_PREFIX.length).trim();
4357
+ return fileId.length > 0 ? fileId : void 0;
4358
+ }
4359
+ function cloneContentPart(part) {
4360
+ switch (part.type) {
4361
+ case "text":
4362
+ return {
4363
+ type: "text",
4364
+ text: part.text,
4365
+ thought: part.thought === true ? true : void 0
4366
+ };
4367
+ case "inlineData":
4368
+ return {
4369
+ type: "inlineData",
4370
+ data: part.data,
4371
+ mimeType: part.mimeType,
4372
+ filename: part.filename
4373
+ };
4374
+ case "input_image":
4375
+ return {
4376
+ type: "input_image",
4377
+ image_url: part.image_url ?? void 0,
4378
+ file_id: part.file_id ?? void 0,
4379
+ detail: part.detail,
4380
+ filename: part.filename ?? void 0
4381
+ };
4382
+ case "input_file":
4383
+ return {
4384
+ type: "input_file",
4385
+ file_data: part.file_data ?? void 0,
4386
+ file_id: part.file_id ?? void 0,
4387
+ file_url: part.file_url ?? void 0,
4388
+ filename: part.filename ?? void 0
4389
+ };
4390
+ default:
4391
+ return part;
4392
+ }
4393
+ }
3638
4394
  function sanitisePartForLogging(part) {
3639
4395
  switch (part.type) {
3640
4396
  case "text":
@@ -3646,16 +4402,33 @@ function sanitisePartForLogging(part) {
3646
4402
  case "inlineData": {
3647
4403
  let omittedBytes;
3648
4404
  try {
3649
- omittedBytes = import_node_buffer3.Buffer.from(part.data, "base64").byteLength;
4405
+ omittedBytes = import_node_buffer4.Buffer.from(part.data, "base64").byteLength;
3650
4406
  } catch {
3651
- omittedBytes = import_node_buffer3.Buffer.byteLength(part.data, "utf8");
4407
+ omittedBytes = import_node_buffer4.Buffer.byteLength(part.data, "utf8");
3652
4408
  }
3653
4409
  return {
3654
4410
  type: "inlineData",
3655
4411
  mimeType: part.mimeType,
4412
+ filename: part.filename,
3656
4413
  data: `[omitted:${omittedBytes}b]`
3657
4414
  };
3658
4415
  }
4416
+ case "input_image":
4417
+ return {
4418
+ type: "input_image",
4419
+ file_id: part.file_id ?? void 0,
4420
+ filename: part.filename ?? void 0,
4421
+ detail: part.detail ?? void 0,
4422
+ image_url: typeof part.image_url === "string" ? part.image_url.startsWith("data:") ? "[omitted:data-url]" : part.image_url : void 0
4423
+ };
4424
+ case "input_file":
4425
+ return {
4426
+ type: "input_file",
4427
+ file_id: part.file_id ?? void 0,
4428
+ filename: part.filename ?? void 0,
4429
+ file_url: typeof part.file_url === "string" ? part.file_url.startsWith("data:") ? "[omitted:data-url]" : part.file_url : void 0,
4430
+ file_data: typeof part.file_data === "string" ? `[omitted:${import_node_buffer4.Buffer.byteLength(part.file_data, "utf8")}b]` : void 0
4431
+ };
3659
4432
  default:
3660
4433
  return "[unknown part]";
3661
4434
  }
@@ -3676,12 +4449,17 @@ function convertGooglePartsToLlmParts(parts) {
3676
4449
  result.push({
3677
4450
  type: "inlineData",
3678
4451
  data: inline.data,
3679
- mimeType: inline.mimeType
4452
+ mimeType: inline.mimeType,
4453
+ filename: inline.displayName
3680
4454
  });
3681
4455
  continue;
3682
4456
  }
3683
4457
  if (part.fileData?.fileUri) {
3684
- throw new Error("fileData parts are not supported");
4458
+ result.push({
4459
+ type: "input_file",
4460
+ file_url: part.fileData.fileUri,
4461
+ filename: part.fileData.displayName
4462
+ });
3685
4463
  }
3686
4464
  }
3687
4465
  return result;
@@ -3713,37 +4491,110 @@ function toGeminiPart(part) {
3713
4491
  text: part.text,
3714
4492
  thought: part.thought === true ? true : void 0
3715
4493
  };
3716
- case "inlineData":
4494
+ case "inlineData": {
4495
+ const inlineData = {
4496
+ data: part.data,
4497
+ mimeType: part.mimeType
4498
+ };
4499
+ setInlineAttachmentFilename(inlineData, part.filename);
3717
4500
  return {
3718
4501
  inlineData: {
3719
- data: part.data,
3720
- mimeType: part.mimeType
4502
+ ...inlineData
3721
4503
  }
3722
4504
  };
3723
- default:
3724
- throw new Error("Unsupported LLM content part");
3725
- }
3726
- }
3727
- function convertLlmContentToGeminiContent(content) {
3728
- const role = content.role === "assistant" ? "model" : "user";
3729
- return {
3730
- role,
3731
- parts: content.parts.map(toGeminiPart)
3732
- };
3733
- }
3734
- function resolveProvider(model) {
3735
- if (isChatGptModelId(model)) {
3736
- return {
3737
- provider: "chatgpt",
3738
- model: resolveChatGptProviderModel(model),
3739
- serviceTier: resolveChatGptServiceTier(model)
3740
- };
3741
- }
3742
- if (isGeminiTextModelId(model) || isGeminiImageModelId(model)) {
3743
- return { provider: "gemini", model };
3744
- }
3745
- if (isFireworksModelId(model)) {
3746
- const fireworksModel = resolveFireworksModelId(model);
4505
+ }
4506
+ case "input_image": {
4507
+ if (part.file_id) {
4508
+ return {
4509
+ fileData: {
4510
+ fileUri: buildCanonicalGeminiFileUri(part.file_id),
4511
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4512
+ displayName: part.filename ?? void 0
4513
+ }
4514
+ };
4515
+ }
4516
+ if (typeof part.image_url !== "string" || part.image_url.trim().length === 0) {
4517
+ throw new Error("input_image requires image_url or file_id.");
4518
+ }
4519
+ const parsed = parseDataUrlPayload(part.image_url);
4520
+ if (parsed) {
4521
+ const geminiPart = (0, import_genai2.createPartFromBase64)(parsed.dataBase64, parsed.mimeType);
4522
+ if (part.filename && geminiPart.inlineData) {
4523
+ geminiPart.inlineData.displayName = part.filename;
4524
+ }
4525
+ return geminiPart;
4526
+ }
4527
+ return {
4528
+ fileData: {
4529
+ fileUri: part.image_url,
4530
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4531
+ displayName: part.filename ?? void 0
4532
+ }
4533
+ };
4534
+ }
4535
+ case "input_file": {
4536
+ if (part.file_id) {
4537
+ return {
4538
+ fileData: {
4539
+ fileUri: buildCanonicalGeminiFileUri(part.file_id),
4540
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4541
+ displayName: part.filename ?? void 0
4542
+ }
4543
+ };
4544
+ }
4545
+ if (typeof part.file_data === "string" && part.file_data.trim().length > 0) {
4546
+ const geminiPart = (0, import_genai2.createPartFromBase64)(
4547
+ part.file_data,
4548
+ inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4549
+ );
4550
+ if (part.filename && geminiPart.inlineData) {
4551
+ geminiPart.inlineData.displayName = part.filename;
4552
+ }
4553
+ return geminiPart;
4554
+ }
4555
+ if (typeof part.file_url === "string" && part.file_url.trim().length > 0) {
4556
+ const parsed = parseDataUrlPayload(part.file_url);
4557
+ if (parsed) {
4558
+ const geminiPart = (0, import_genai2.createPartFromBase64)(parsed.dataBase64, parsed.mimeType);
4559
+ if (part.filename && geminiPart.inlineData) {
4560
+ geminiPart.inlineData.displayName = part.filename;
4561
+ }
4562
+ return geminiPart;
4563
+ }
4564
+ return {
4565
+ fileData: {
4566
+ fileUri: part.file_url,
4567
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4568
+ displayName: part.filename ?? void 0
4569
+ }
4570
+ };
4571
+ }
4572
+ throw new Error("input_file requires file_id, file_data, or file_url.");
4573
+ }
4574
+ default:
4575
+ throw new Error("Unsupported LLM content part");
4576
+ }
4577
+ }
4578
+ function convertLlmContentToGeminiContent(content) {
4579
+ const role = content.role === "assistant" ? "model" : "user";
4580
+ return {
4581
+ role,
4582
+ parts: content.parts.map(toGeminiPart)
4583
+ };
4584
+ }
4585
+ function resolveProvider(model) {
4586
+ if (isChatGptModelId(model)) {
4587
+ return {
4588
+ provider: "chatgpt",
4589
+ model: resolveChatGptProviderModel(model),
4590
+ serviceTier: resolveChatGptServiceTier(model)
4591
+ };
4592
+ }
4593
+ if (isGeminiTextModelId(model) || isGeminiImageModelId(model)) {
4594
+ return { provider: "gemini", model };
4595
+ }
4596
+ if (isFireworksModelId(model)) {
4597
+ const fireworksModel = resolveFireworksModelId(model);
3747
4598
  if (fireworksModel) {
3748
4599
  return { provider: "fireworks", model: fireworksModel };
3749
4600
  }
@@ -3816,6 +4667,14 @@ function isInlineImageMime(mimeType) {
3816
4667
  }
3817
4668
  function guessInlineDataFilename(mimeType) {
3818
4669
  switch (mimeType) {
4670
+ case "image/jpeg":
4671
+ return "image.jpg";
4672
+ case "image/png":
4673
+ return "image.png";
4674
+ case "image/webp":
4675
+ return "image.webp";
4676
+ case "image/gif":
4677
+ return "image.gif";
3819
4678
  case "application/pdf":
3820
4679
  return "document.pdf";
3821
4680
  case "application/json":
@@ -3828,6 +4687,261 @@ function guessInlineDataFilename(mimeType) {
3828
4687
  return "attachment.bin";
3829
4688
  }
3830
4689
  }
4690
+ function normaliseAttachmentFilename(value, fallback) {
4691
+ const trimmed = value?.trim();
4692
+ if (!trimmed) {
4693
+ return fallback;
4694
+ }
4695
+ const basename = import_node_path5.default.basename(trimmed).replace(/[^\w.-]+/g, "-");
4696
+ return basename.length > 0 ? basename : fallback;
4697
+ }
4698
+ function setInlineAttachmentFilename(target, filename) {
4699
+ const normalized = filename?.trim();
4700
+ if (!normalized) {
4701
+ return;
4702
+ }
4703
+ target[INLINE_ATTACHMENT_FILENAME_SYMBOL] = normalized;
4704
+ }
4705
+ function getInlineAttachmentFilename(target) {
4706
+ if (!target || typeof target !== "object") {
4707
+ return void 0;
4708
+ }
4709
+ const value = target[INLINE_ATTACHMENT_FILENAME_SYMBOL];
4710
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
4711
+ }
4712
+ function estimateInlinePayloadBytes(value) {
4713
+ return import_node_buffer4.Buffer.byteLength(value, "utf8");
4714
+ }
4715
+ function isOpenAiNativeContentItem(value) {
4716
+ return !!value && typeof value === "object" && typeof value.type === "string";
4717
+ }
4718
+ function estimateOpenAiInlinePromptBytes(input) {
4719
+ let total = 0;
4720
+ const visitItems = (items) => {
4721
+ for (const item of items) {
4722
+ if (!item || typeof item !== "object") {
4723
+ continue;
4724
+ }
4725
+ if (Array.isArray(item.content)) {
4726
+ visitItems(item.content);
4727
+ }
4728
+ if (Array.isArray(item.output)) {
4729
+ visitItems(item.output);
4730
+ }
4731
+ if (!isOpenAiNativeContentItem(item)) {
4732
+ continue;
4733
+ }
4734
+ if (item.type === "input_image" && typeof item.image_url === "string" && item.image_url.trim().toLowerCase().startsWith("data:")) {
4735
+ total += estimateInlinePayloadBytes(item.image_url);
4736
+ }
4737
+ if (item.type === "input_file" && typeof item.file_data === "string" && item.file_data.trim().length > 0) {
4738
+ total += estimateInlinePayloadBytes(item.file_data);
4739
+ }
4740
+ if (item.type === "input_file" && typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4741
+ total += estimateInlinePayloadBytes(item.file_url);
4742
+ }
4743
+ }
4744
+ };
4745
+ visitItems(input);
4746
+ return total;
4747
+ }
4748
+ async function storeCanonicalPromptFile(options) {
4749
+ const file = await runWithFileUploadSource("prompt_inline_offload", async () => {
4750
+ return await filesCreate({
4751
+ data: options.bytes,
4752
+ filename: options.filename,
4753
+ mimeType: options.mimeType,
4754
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
4755
+ });
4756
+ });
4757
+ return {
4758
+ fileId: file.id,
4759
+ filename: file.filename,
4760
+ mimeType: options.mimeType
4761
+ };
4762
+ }
4763
+ async function prepareOpenAiPromptContentItem(item) {
4764
+ if (!isOpenAiNativeContentItem(item)) {
4765
+ return item;
4766
+ }
4767
+ if (item.type === "input_image" && typeof item.image_url === "string" && item.image_url.trim().toLowerCase().startsWith("data:")) {
4768
+ const parsed = parseDataUrlPayload(item.image_url);
4769
+ if (!parsed) {
4770
+ return item;
4771
+ }
4772
+ const uploaded = await storeCanonicalPromptFile({
4773
+ bytes: parsed.bytes,
4774
+ mimeType: parsed.mimeType ?? "application/octet-stream",
4775
+ filename: normaliseAttachmentFilename(
4776
+ getInlineAttachmentFilename(item),
4777
+ guessInlineDataFilename(parsed.mimeType)
4778
+ )
4779
+ });
4780
+ return {
4781
+ type: "input_image",
4782
+ detail: item.detail === "high" || item.detail === "low" ? item.detail : "auto",
4783
+ file_id: uploaded.fileId
4784
+ };
4785
+ }
4786
+ if (item.type !== "input_file" || item.file_id) {
4787
+ return item;
4788
+ }
4789
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
4790
+ const filename = normaliseAttachmentFilename(
4791
+ typeof item.filename === "string" ? item.filename : void 0,
4792
+ guessInlineDataFilename(void 0)
4793
+ );
4794
+ const mimeType = inferToolOutputMimeTypeFromFilename(filename) ?? "application/octet-stream";
4795
+ const uploaded = await storeCanonicalPromptFile({
4796
+ bytes: decodeInlineDataBuffer(item.file_data),
4797
+ mimeType,
4798
+ filename
4799
+ });
4800
+ return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4801
+ }
4802
+ if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4803
+ const parsed = parseDataUrlPayload(item.file_url);
4804
+ if (!parsed) {
4805
+ return item;
4806
+ }
4807
+ const uploaded = await storeCanonicalPromptFile({
4808
+ bytes: parsed.bytes,
4809
+ mimeType: parsed.mimeType ?? "application/octet-stream",
4810
+ filename: normaliseAttachmentFilename(
4811
+ typeof item.filename === "string" ? item.filename : void 0,
4812
+ guessInlineDataFilename(parsed.mimeType)
4813
+ )
4814
+ });
4815
+ return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4816
+ }
4817
+ return item;
4818
+ }
4819
+ async function prepareOpenAiPromptInput(input) {
4820
+ const prepareItem = async (item) => {
4821
+ if (!item || typeof item !== "object") {
4822
+ return item;
4823
+ }
4824
+ const record = item;
4825
+ if (Array.isArray(record.content)) {
4826
+ return {
4827
+ ...record,
4828
+ content: await Promise.all(
4829
+ record.content.map((part) => prepareOpenAiPromptContentItem(part))
4830
+ )
4831
+ };
4832
+ }
4833
+ if (Array.isArray(record.output)) {
4834
+ return {
4835
+ ...record,
4836
+ output: await Promise.all(
4837
+ record.output.map((part) => prepareOpenAiPromptContentItem(part))
4838
+ )
4839
+ };
4840
+ }
4841
+ return await prepareOpenAiPromptContentItem(item);
4842
+ };
4843
+ return await Promise.all(input.map((item) => prepareItem(item)));
4844
+ }
4845
+ async function maybePrepareOpenAiPromptInput(input) {
4846
+ if (estimateOpenAiInlinePromptBytes(input) <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
4847
+ return Array.from(input);
4848
+ }
4849
+ return await prepareOpenAiPromptInput(input);
4850
+ }
4851
+ function estimateGeminiInlinePromptBytes(contents) {
4852
+ let total = 0;
4853
+ for (const content of contents) {
4854
+ for (const part of content.parts ?? []) {
4855
+ if (part.inlineData?.data) {
4856
+ total += estimateInlinePayloadBytes(part.inlineData.data);
4857
+ }
4858
+ }
4859
+ }
4860
+ return total;
4861
+ }
4862
+ function hasCanonicalGeminiFileReferences(contents) {
4863
+ for (const content of contents) {
4864
+ for (const part of content.parts ?? []) {
4865
+ if (parseCanonicalGeminiFileId(part.fileData?.fileUri)) {
4866
+ return true;
4867
+ }
4868
+ }
4869
+ }
4870
+ return false;
4871
+ }
4872
+ async function prepareGeminiPromptContents(contents) {
4873
+ const backend = getGeminiBackend();
4874
+ const preparedContents = [];
4875
+ for (const content of contents) {
4876
+ const parts = [];
4877
+ for (const part of content.parts ?? []) {
4878
+ const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4879
+ if (canonicalFileId) {
4880
+ const metadata = await getCanonicalFileMetadata(canonicalFileId);
4881
+ if (backend === "api") {
4882
+ const mirrored = await ensureGeminiFileMirror(canonicalFileId);
4883
+ const mirroredPart = (0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType);
4884
+ if (metadata.filename && mirroredPart.fileData) {
4885
+ mirroredPart.fileData.displayName = metadata.filename;
4886
+ }
4887
+ parts.push(mirroredPart);
4888
+ } else {
4889
+ const mirrored = await ensureVertexFileMirror(canonicalFileId);
4890
+ parts.push({
4891
+ fileData: {
4892
+ fileUri: mirrored.fileUri,
4893
+ mimeType: mirrored.mimeType,
4894
+ displayName: metadata.filename
4895
+ }
4896
+ });
4897
+ }
4898
+ continue;
4899
+ }
4900
+ if (part.inlineData?.data) {
4901
+ const mimeType = part.inlineData.mimeType ?? "application/octet-stream";
4902
+ const filename = normaliseAttachmentFilename(
4903
+ getInlineAttachmentFilename(part.inlineData) ?? part.inlineData.displayName ?? guessInlineDataFilename(mimeType),
4904
+ guessInlineDataFilename(mimeType)
4905
+ );
4906
+ const stored = await storeCanonicalPromptFile({
4907
+ bytes: decodeInlineDataBuffer(part.inlineData.data),
4908
+ mimeType,
4909
+ filename
4910
+ });
4911
+ if (backend === "api") {
4912
+ const mirrored = await ensureGeminiFileMirror(stored.fileId);
4913
+ const mirroredPart = (0, import_genai2.createPartFromUri)(mirrored.uri, mirrored.mimeType);
4914
+ if (filename && mirroredPart.fileData) {
4915
+ mirroredPart.fileData.displayName = filename;
4916
+ }
4917
+ parts.push(mirroredPart);
4918
+ } else {
4919
+ const mirrored = await ensureVertexFileMirror(stored.fileId);
4920
+ parts.push({
4921
+ fileData: {
4922
+ fileUri: mirrored.fileUri,
4923
+ mimeType: mirrored.mimeType,
4924
+ displayName: filename
4925
+ }
4926
+ });
4927
+ }
4928
+ continue;
4929
+ }
4930
+ parts.push(part);
4931
+ }
4932
+ preparedContents.push({
4933
+ ...content,
4934
+ parts
4935
+ });
4936
+ }
4937
+ return preparedContents;
4938
+ }
4939
+ async function maybePrepareGeminiPromptContents(contents) {
4940
+ if (!hasCanonicalGeminiFileReferences(contents) && estimateGeminiInlinePromptBytes(contents) <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
4941
+ return Array.from(contents);
4942
+ }
4943
+ return await prepareGeminiPromptContents(contents);
4944
+ }
3831
4945
  function mergeConsecutiveTextParts(parts) {
3832
4946
  if (parts.length === 0) {
3833
4947
  return [];
@@ -3835,7 +4949,7 @@ function mergeConsecutiveTextParts(parts) {
3835
4949
  const merged = [];
3836
4950
  for (const part of parts) {
3837
4951
  if (part.type !== "text") {
3838
- merged.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
4952
+ merged.push(cloneContentPart(part));
3839
4953
  continue;
3840
4954
  }
3841
4955
  const isThought = part.thought === true;
@@ -4266,13 +5380,7 @@ function resolveTextContents(input) {
4266
5380
  const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
4267
5381
  contents.push({
4268
5382
  role: message.role,
4269
- parts: parts.map(
4270
- (part) => part.type === "text" ? {
4271
- type: "text",
4272
- text: part.text,
4273
- thought: "thought" in part && part.thought === true ? true : void 0
4274
- } : { type: "inlineData", data: part.data, mimeType: part.mimeType }
4275
- )
5383
+ parts: parts.map((part) => cloneContentPart(part))
4276
5384
  });
4277
5385
  }
4278
5386
  return contents;
@@ -4288,22 +5396,58 @@ function toOpenAiInput(contents) {
4288
5396
  return contents.map((content) => {
4289
5397
  const parts = [];
4290
5398
  for (const part of content.parts) {
4291
- if (part.type === "text") {
4292
- parts.push({ type: "input_text", text: part.text });
4293
- continue;
4294
- }
4295
- const mimeType = part.mimeType;
4296
- if (isInlineImageMime(mimeType)) {
4297
- const dataUrl = `data:${mimeType};base64,${part.data}`;
4298
- parts.push({ type: "input_image", image_url: dataUrl, detail: "auto" });
4299
- continue;
5399
+ switch (part.type) {
5400
+ case "text":
5401
+ parts.push({ type: "input_text", text: part.text });
5402
+ break;
5403
+ case "inlineData": {
5404
+ const mimeType = part.mimeType;
5405
+ if (isInlineImageMime(mimeType)) {
5406
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
5407
+ const imagePart = {
5408
+ type: "input_image",
5409
+ image_url: dataUrl,
5410
+ detail: "auto"
5411
+ };
5412
+ setInlineAttachmentFilename(
5413
+ imagePart,
5414
+ normaliseAttachmentFilename(part.filename, guessInlineDataFilename(mimeType))
5415
+ );
5416
+ parts.push(imagePart);
5417
+ break;
5418
+ }
5419
+ parts.push({
5420
+ type: "input_file",
5421
+ filename: normaliseAttachmentFilename(part.filename, guessInlineDataFilename(mimeType)),
5422
+ file_data: part.data
5423
+ });
5424
+ break;
5425
+ }
5426
+ case "input_image": {
5427
+ const imagePart = {
5428
+ type: "input_image",
5429
+ ...part.file_id ? { file_id: part.file_id } : {},
5430
+ ...part.image_url ? { image_url: part.image_url } : {},
5431
+ detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
5432
+ };
5433
+ if (part.filename) {
5434
+ setInlineAttachmentFilename(imagePart, part.filename);
5435
+ }
5436
+ parts.push(imagePart);
5437
+ break;
5438
+ }
5439
+ case "input_file":
5440
+ parts.push({
5441
+ type: "input_file",
5442
+ ...part.file_id ? { file_id: part.file_id } : {},
5443
+ ...part.file_data ? { file_data: part.file_data } : {},
5444
+ ...part.file_url ? { file_url: part.file_url } : {},
5445
+ ...part.filename ? { filename: part.filename } : {}
5446
+ });
5447
+ break;
5448
+ default:
5449
+ throw new Error("Unsupported LLM content part");
4300
5450
  }
4301
- const fileData = decodeInlineDataBuffer(part.data).toString("base64");
4302
- parts.push({
4303
- type: "input_file",
4304
- filename: guessInlineDataFilename(mimeType),
4305
- file_data: fileData
4306
- });
4307
5451
  }
4308
5452
  if (parts.length === 1 && parts[0]?.type === "input_text" && typeof parts[0].text === "string") {
4309
5453
  return {
@@ -4340,28 +5484,54 @@ function toChatGptInput(contents) {
4340
5484
  continue;
4341
5485
  }
4342
5486
  if (isAssistant) {
4343
- const mimeType = part.mimeType ?? "application/octet-stream";
5487
+ const mimeType = part.type === "inlineData" ? part.mimeType ?? "application/octet-stream" : inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream";
4344
5488
  parts.push({
4345
5489
  type: "output_text",
4346
- text: isInlineImageMime(part.mimeType) ? `[image:${mimeType}]` : `[file:${mimeType}]`
5490
+ text: part.type === "input_image" || isInlineImageMime(part.mimeType) ? `[image:${mimeType}]` : `[file:${mimeType}]`
4347
5491
  });
4348
- } else {
4349
- if (isInlineImageMime(part.mimeType)) {
4350
- const mimeType = part.mimeType ?? "application/octet-stream";
4351
- const dataUrl = `data:${mimeType};base64,${part.data}`;
5492
+ continue;
5493
+ }
5494
+ switch (part.type) {
5495
+ case "inlineData": {
5496
+ if (isInlineImageMime(part.mimeType)) {
5497
+ const mimeType = part.mimeType ?? "application/octet-stream";
5498
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
5499
+ parts.push({
5500
+ type: "input_image",
5501
+ image_url: dataUrl,
5502
+ detail: "auto"
5503
+ });
5504
+ } else {
5505
+ parts.push({
5506
+ type: "input_file",
5507
+ filename: normaliseAttachmentFilename(
5508
+ part.filename,
5509
+ guessInlineDataFilename(part.mimeType)
5510
+ ),
5511
+ file_data: part.data
5512
+ });
5513
+ }
5514
+ break;
5515
+ }
5516
+ case "input_image":
4352
5517
  parts.push({
4353
5518
  type: "input_image",
4354
- image_url: dataUrl,
4355
- detail: "auto"
5519
+ ...part.file_id ? { file_id: part.file_id } : {},
5520
+ ...part.image_url ? { image_url: part.image_url } : {},
5521
+ detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
4356
5522
  });
4357
- } else {
4358
- const fileData = decodeInlineDataBuffer(part.data).toString("base64");
5523
+ break;
5524
+ case "input_file":
4359
5525
  parts.push({
4360
5526
  type: "input_file",
4361
- filename: guessInlineDataFilename(part.mimeType),
4362
- file_data: fileData
5527
+ ...part.file_id ? { file_id: part.file_id } : {},
5528
+ ...part.file_data ? { file_data: part.file_data } : {},
5529
+ ...part.file_url ? { file_url: part.file_url } : {},
5530
+ ...part.filename ? { filename: part.filename } : {}
4363
5531
  });
4364
- }
5532
+ break;
5533
+ default:
5534
+ throw new Error("Unsupported LLM content part");
4365
5535
  }
4366
5536
  }
4367
5537
  if (parts.length === 0) {
@@ -4405,8 +5575,8 @@ ${JSON.stringify(options.responseJsonSchema)}`);
4405
5575
  if (part.type === "text") {
4406
5576
  return part.text;
4407
5577
  }
4408
- const mimeType = part.mimeType ?? "application/octet-stream";
4409
- if (isInlineImageMime(mimeType)) {
5578
+ const mimeType = part.type === "inlineData" ? part.mimeType ?? "application/octet-stream" : inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream";
5579
+ if (part.type === "input_image" || isInlineImageMime(mimeType)) {
4410
5580
  return `[image:${mimeType}]`;
4411
5581
  }
4412
5582
  return `[file:${mimeType}]`;
@@ -4677,7 +5847,14 @@ function isLlmToolOutputContentItem(value) {
4677
5847
  return typeof value.text === "string";
4678
5848
  }
4679
5849
  if (itemType === "input_image") {
4680
- return typeof value.image_url === "string";
5850
+ const keys = ["image_url", "file_id", "filename"];
5851
+ for (const key of keys) {
5852
+ const part = value[key];
5853
+ if (part !== void 0 && part !== null && typeof part !== "string") {
5854
+ return false;
5855
+ }
5856
+ }
5857
+ return value.image_url !== void 0 || value.file_id !== void 0;
4681
5858
  }
4682
5859
  if (itemType === "input_file") {
4683
5860
  const keys = ["file_data", "file_id", "file_url", "filename"];
@@ -4746,15 +5923,252 @@ function inferToolOutputMimeTypeFromFilename(filename) {
4746
5923
  }
4747
5924
  return void 0;
4748
5925
  }
4749
- function buildGeminiToolOutputMediaPart(item) {
5926
+ function estimateToolOutputItemBytes(item) {
5927
+ if (item.type === "input_text") {
5928
+ return import_node_buffer4.Buffer.byteLength(item.text, "utf8");
5929
+ }
4750
5930
  if (item.type === "input_image") {
5931
+ return typeof item.image_url === "string" ? estimateInlinePayloadBytes(item.image_url) : 0;
5932
+ }
5933
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
5934
+ return estimateInlinePayloadBytes(item.file_data);
5935
+ }
5936
+ if (typeof item.file_url === "string" && item.file_url.trim().length > 0) {
5937
+ return estimateInlinePayloadBytes(item.file_url);
5938
+ }
5939
+ return 0;
5940
+ }
5941
+ async function spillTextToolOutputToFile(options) {
5942
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5943
+ return await filesCreate({
5944
+ data: options.text,
5945
+ filename: options.filename,
5946
+ mimeType: options.mimeType,
5947
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5948
+ });
5949
+ });
5950
+ return [
5951
+ {
5952
+ type: "input_text",
5953
+ text: `Tool output was attached as ${stored.filename} (${stored.id}) because it exceeded the inline payload threshold.`
5954
+ },
5955
+ {
5956
+ type: "input_file",
5957
+ file_id: stored.id,
5958
+ filename: stored.filename
5959
+ }
5960
+ ];
5961
+ }
5962
+ async function maybeSpillToolOutputItem(item, toolName, options) {
5963
+ if (options?.force !== true && estimateToolOutputItemBytes(item) <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
5964
+ return item;
5965
+ }
5966
+ if (item.type === "input_text") {
5967
+ return await spillTextToolOutputToFile({
5968
+ text: item.text,
5969
+ filename: normaliseAttachmentFilename(`${toolName}.txt`, "tool-output.txt"),
5970
+ mimeType: "text/plain"
5971
+ });
5972
+ }
5973
+ if (item.type === "input_image") {
5974
+ if (item.file_id || !item.image_url) {
5975
+ return item;
5976
+ }
4751
5977
  const parsed = parseDataUrlPayload(item.image_url);
4752
5978
  if (!parsed) {
5979
+ return item;
5980
+ }
5981
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5982
+ return await filesCreate({
5983
+ data: parsed.bytes,
5984
+ filename: normaliseAttachmentFilename(
5985
+ item.filename ?? guessInlineDataFilename(parsed.mimeType),
5986
+ guessInlineDataFilename(parsed.mimeType)
5987
+ ),
5988
+ mimeType: parsed.mimeType,
5989
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5990
+ });
5991
+ });
5992
+ return {
5993
+ type: "input_image",
5994
+ file_id: stored.id,
5995
+ detail: item.detail ?? "auto",
5996
+ filename: stored.filename
5997
+ };
5998
+ }
5999
+ if (item.file_id) {
6000
+ return item;
6001
+ }
6002
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
6003
+ const fileData = item.file_data;
6004
+ const filename = normaliseAttachmentFilename(
6005
+ item.filename ?? `${toolName}.bin`,
6006
+ `${toolName}.bin`
6007
+ );
6008
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
6009
+ return await filesCreate({
6010
+ data: decodeInlineDataBuffer(fileData),
6011
+ filename,
6012
+ mimeType: inferToolOutputMimeTypeFromFilename(filename) ?? "application/octet-stream",
6013
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
6014
+ });
6015
+ });
6016
+ return {
6017
+ type: "input_file",
6018
+ file_id: stored.id,
6019
+ filename: stored.filename
6020
+ };
6021
+ }
6022
+ if (typeof item.file_url === "string" && item.file_url.trim().length > 0) {
6023
+ const parsed = parseDataUrlPayload(item.file_url);
6024
+ if (!parsed) {
6025
+ return item;
6026
+ }
6027
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
6028
+ return await filesCreate({
6029
+ data: parsed.bytes,
6030
+ filename: normaliseAttachmentFilename(
6031
+ item.filename ?? guessInlineDataFilename(parsed.mimeType),
6032
+ guessInlineDataFilename(parsed.mimeType)
6033
+ ),
6034
+ mimeType: parsed.mimeType,
6035
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
6036
+ });
6037
+ });
6038
+ return {
6039
+ type: "input_file",
6040
+ file_id: stored.id,
6041
+ filename: stored.filename
6042
+ };
6043
+ }
6044
+ return item;
6045
+ }
6046
+ async function maybeSpillToolOutput(value, toolName, options) {
6047
+ if (typeof value === "string") {
6048
+ if (options?.force !== true && import_node_buffer4.Buffer.byteLength(value, "utf8") <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
6049
+ return value;
6050
+ }
6051
+ return await spillTextToolOutputToFile({
6052
+ text: value,
6053
+ filename: normaliseAttachmentFilename(`${toolName}.txt`, "tool-output.txt"),
6054
+ mimeType: "text/plain"
6055
+ });
6056
+ }
6057
+ if (isLlmToolOutputContentItem(value)) {
6058
+ return await maybeSpillToolOutputItem(value, toolName, options);
6059
+ }
6060
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
6061
+ const spilledItems = [];
6062
+ for (const item of value) {
6063
+ const maybeSpilled = await maybeSpillToolOutputItem(item, toolName, options);
6064
+ if (Array.isArray(maybeSpilled)) {
6065
+ spilledItems.push(...maybeSpilled);
6066
+ } else {
6067
+ spilledItems.push(maybeSpilled);
6068
+ }
6069
+ }
6070
+ return spilledItems;
6071
+ }
6072
+ try {
6073
+ const serialized = JSON.stringify(value, null, 2);
6074
+ if (options?.force !== true && import_node_buffer4.Buffer.byteLength(serialized, "utf8") <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
6075
+ return value;
6076
+ }
6077
+ return await spillTextToolOutputToFile({
6078
+ text: serialized,
6079
+ filename: normaliseAttachmentFilename(`${toolName}.json`, "tool-output.json"),
6080
+ mimeType: "application/json"
6081
+ });
6082
+ } catch {
6083
+ return value;
6084
+ }
6085
+ }
6086
+ function estimateToolOutputPayloadBytes(value) {
6087
+ if (typeof value === "string") {
6088
+ return import_node_buffer4.Buffer.byteLength(value, "utf8");
6089
+ }
6090
+ if (isLlmToolOutputContentItem(value)) {
6091
+ return value.type === "input_text" ? 0 : estimateToolOutputItemBytes(value);
6092
+ }
6093
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
6094
+ return value.reduce((total, item) => {
6095
+ return total + (item.type === "input_text" ? 0 : estimateToolOutputItemBytes(item));
6096
+ }, 0);
6097
+ }
6098
+ return 0;
6099
+ }
6100
+ async function maybeSpillCombinedToolCallOutputs(callResults) {
6101
+ const totalBytes = callResults.reduce(
6102
+ (sum, callResult) => sum + estimateToolOutputPayloadBytes(callResult.outputPayload),
6103
+ 0
6104
+ );
6105
+ if (totalBytes <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
6106
+ return Array.from(callResults);
6107
+ }
6108
+ return await Promise.all(
6109
+ callResults.map(async (callResult) => {
6110
+ if (estimateToolOutputPayloadBytes(callResult.outputPayload) === 0) {
6111
+ return callResult;
6112
+ }
6113
+ const outputPayload = await maybeSpillToolOutput(
6114
+ callResult.outputPayload,
6115
+ callResult.entry.toolName,
6116
+ {
6117
+ force: true
6118
+ }
6119
+ );
6120
+ return {
6121
+ ...callResult,
6122
+ outputPayload,
6123
+ result: {
6124
+ ...callResult.result,
6125
+ output: outputPayload
6126
+ }
6127
+ };
6128
+ })
6129
+ );
6130
+ }
6131
+ function buildGeminiToolOutputMediaPart(item) {
6132
+ if (item.type === "input_image") {
6133
+ if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
6134
+ return {
6135
+ fileData: {
6136
+ fileUri: buildCanonicalGeminiFileUri(item.file_id),
6137
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6138
+ displayName: item.filename ?? void 0
6139
+ }
6140
+ };
6141
+ }
6142
+ if (typeof item.image_url !== "string" || item.image_url.trim().length === 0) {
4753
6143
  return null;
4754
6144
  }
4755
- return (0, import_genai2.createPartFromBase64)(parsed.dataBase64, parsed.mimeType);
6145
+ const parsed = parseDataUrlPayload(item.image_url);
6146
+ if (parsed) {
6147
+ const part = (0, import_genai2.createPartFromBase64)(parsed.dataBase64, parsed.mimeType);
6148
+ const displayName = item.filename?.trim();
6149
+ if (displayName && part.inlineData) {
6150
+ part.inlineData.displayName = displayName;
6151
+ }
6152
+ return part;
6153
+ }
6154
+ return {
6155
+ fileData: {
6156
+ fileUri: item.image_url,
6157
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6158
+ displayName: item.filename ?? void 0
6159
+ }
6160
+ };
4756
6161
  }
4757
6162
  if (item.type === "input_file") {
6163
+ if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
6164
+ return {
6165
+ fileData: {
6166
+ fileUri: buildCanonicalGeminiFileUri(item.file_id),
6167
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6168
+ displayName: item.filename ?? void 0
6169
+ }
6170
+ };
6171
+ }
4758
6172
  const dataUrl = typeof item.file_url === "string" ? parseDataUrlPayload(item.file_url) : null;
4759
6173
  if (dataUrl) {
4760
6174
  const part = (0, import_genai2.createPartFromBase64)(dataUrl.dataBase64, dataUrl.mimeType);
@@ -4792,11 +6206,12 @@ function toGeminiToolOutputPlaceholder(item) {
4792
6206
  };
4793
6207
  }
4794
6208
  if (item.type === "input_image") {
4795
- const parsed = parseDataUrlPayload(item.image_url);
6209
+ const parsed = typeof item.image_url === "string" ? parseDataUrlPayload(item.image_url) : null;
4796
6210
  return {
4797
6211
  type: item.type,
6212
+ fileId: item.file_id ?? void 0,
4798
6213
  mimeType: parsed?.mimeType ?? void 0,
4799
- media: "attached-inline-data"
6214
+ media: item.file_id ? "attached-file-id" : parsed ? "attached-inline-data" : item.image_url ? "attached-file-data" : void 0
4800
6215
  };
4801
6216
  }
4802
6217
  const dataUrl = typeof item.file_url === "string" ? parseDataUrlPayload(item.file_url) : null;
@@ -4805,7 +6220,7 @@ function toGeminiToolOutputPlaceholder(item) {
4805
6220
  filename: item.filename ?? void 0,
4806
6221
  fileId: item.file_id ?? void 0,
4807
6222
  mimeType: dataUrl?.mimeType ?? inferToolOutputMimeTypeFromFilename(item.filename) ?? void 0,
4808
- media: dataUrl || typeof item.file_data === "string" && item.file_data.trim().length > 0 ? "attached-inline-data" : typeof item.file_url === "string" && item.file_url.trim().length > 0 ? "attached-file-data" : void 0
6223
+ media: item.file_id ? "attached-file-id" : dataUrl || typeof item.file_data === "string" && item.file_data.trim().length > 0 ? "attached-inline-data" : typeof item.file_url === "string" && item.file_url.trim().length > 0 ? "attached-file-data" : void 0
4809
6224
  };
4810
6225
  }
4811
6226
  function buildGeminiFunctionResponseParts(options) {
@@ -4853,8 +6268,8 @@ function parseOpenAiToolArguments(raw) {
4853
6268
  function formatZodIssues(issues) {
4854
6269
  const messages = [];
4855
6270
  for (const issue of issues) {
4856
- const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4857
- messages.push(`${path8}: ${issue.message}`);
6271
+ const path10 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
6272
+ messages.push(`${path10}: ${issue.message}`);
4858
6273
  }
4859
6274
  return messages.join("; ");
4860
6275
  }
@@ -4972,8 +6387,9 @@ async function executeToolCall(params) {
4972
6387
  const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
4973
6388
  try {
4974
6389
  const output = await tool2.execute(input);
4975
- const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4976
- return finalize({ toolName, input, output }, output, metrics);
6390
+ const outputPayload = await maybeSpillToolOutput(output, toolName);
6391
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(outputPayload) : void 0;
6392
+ return finalize({ toolName, input, output: outputPayload }, outputPayload, metrics);
4977
6393
  } catch (error) {
4978
6394
  const message = error instanceof Error ? error.message : String(error);
4979
6395
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
@@ -5007,8 +6423,13 @@ async function executeToolCall(params) {
5007
6423
  }
5008
6424
  try {
5009
6425
  const output = await tool2.execute(parsed.data);
5010
- const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
5011
- return finalize({ toolName, input: parsed.data, output }, output, metrics);
6426
+ const outputPayload = await maybeSpillToolOutput(output, toolName);
6427
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(outputPayload) : void 0;
6428
+ return finalize(
6429
+ { toolName, input: parsed.data, output: outputPayload },
6430
+ outputPayload,
6431
+ metrics
6432
+ );
5012
6433
  } catch (error) {
5013
6434
  const message = error instanceof Error ? error.message : String(error);
5014
6435
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
@@ -5024,7 +6445,7 @@ function buildToolLogId(turn, toolIndex) {
5024
6445
  function sanitizeChatGptToolId(value) {
5025
6446
  const cleaned = value.replace(/[^A-Za-z0-9_-]/gu, "");
5026
6447
  if (cleaned.length === 0) {
5027
- return (0, import_node_crypto.randomBytes)(8).toString("hex");
6448
+ return (0, import_node_crypto2.randomBytes)(8).toString("hex");
5028
6449
  }
5029
6450
  return cleaned.slice(0, 64);
5030
6451
  }
@@ -5042,7 +6463,7 @@ function normalizeChatGptToolIds(params) {
5042
6463
  rawCallId = nextCallId ?? rawCallId;
5043
6464
  rawItemId = nextItemId ?? rawItemId;
5044
6465
  }
5045
- const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || (0, import_node_crypto.randomBytes)(8).toString("hex"));
6466
+ const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || (0, import_node_crypto2.randomBytes)(8).toString("hex"));
5046
6467
  let itemValue = sanitizeChatGptToolId(rawItemId || callValue);
5047
6468
  if (params.callKind === "custom") {
5048
6469
  if (!itemValue.startsWith("ctc")) {
@@ -5209,7 +6630,7 @@ function toGemini25ProThinkingBudget(thinkingLevel) {
5209
6630
  }
5210
6631
  }
5211
6632
  function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
5212
- if (isGeminiImageModelId(modelId)) {
6633
+ if (isGeminiImageModelId(modelId) || modelId === "gemini-flash-lite-latest") {
5213
6634
  return void 0;
5214
6635
  }
5215
6636
  if (thinkingLevel) {
@@ -5240,9 +6661,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
5240
6661
  }
5241
6662
  function decodeInlineDataBuffer(base64) {
5242
6663
  try {
5243
- return import_node_buffer3.Buffer.from(base64, "base64");
6664
+ return import_node_buffer4.Buffer.from(base64, "base64");
5244
6665
  } catch {
5245
- return import_node_buffer3.Buffer.from(base64, "base64url");
6666
+ return import_node_buffer4.Buffer.from(base64, "base64url");
5246
6667
  }
5247
6668
  }
5248
6669
  function extractImages(content) {
@@ -5312,7 +6733,7 @@ function parseDataUrlPayload(value) {
5312
6733
  const isBase64 = /;base64(?:;|$)/iu.test(header);
5313
6734
  const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
5314
6735
  try {
5315
- const bytes = isBase64 ? import_node_buffer3.Buffer.from(payload, "base64") : import_node_buffer3.Buffer.from(decodeURIComponent(payload), "utf8");
6736
+ const bytes = isBase64 ? import_node_buffer4.Buffer.from(payload, "base64") : import_node_buffer4.Buffer.from(decodeURIComponent(payload), "utf8");
5316
6737
  return {
5317
6738
  mimeType,
5318
6739
  dataBase64: bytes.toString("base64"),
@@ -5415,7 +6836,10 @@ function collectLoggedAttachmentsFromLlmParts(parts, prefix) {
5415
6836
  continue;
5416
6837
  }
5417
6838
  attachments.push({
5418
- filename: buildLoggedAttachmentFilename(prefix, index, part.mimeType),
6839
+ filename: normaliseAttachmentFilename(
6840
+ part.filename,
6841
+ buildLoggedAttachmentFilename(prefix, index, part.mimeType)
6842
+ ),
5419
6843
  bytes: decodeInlineDataBuffer(part.data)
5420
6844
  });
5421
6845
  index += 1;
@@ -5544,15 +6968,19 @@ function startLlmCallLoggerFromContents(options) {
5544
6968
  sections.push(part.text);
5545
6969
  continue;
5546
6970
  }
5547
- const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
5548
- attachments.push({
5549
- filename,
5550
- bytes: decodeInlineDataBuffer(part.data)
5551
- });
5552
- attachmentIndex += 1;
5553
- sections.push(
5554
- `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5555
- );
6971
+ if (part.type === "inlineData") {
6972
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
6973
+ attachments.push({
6974
+ filename,
6975
+ bytes: decodeInlineDataBuffer(part.data)
6976
+ });
6977
+ attachmentIndex += 1;
6978
+ sections.push(
6979
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
6980
+ );
6981
+ continue;
6982
+ }
6983
+ sections.push(`[${part.type}] file_id=${"file_id" in part ? part.file_id ?? "" : ""}`);
5556
6984
  }
5557
6985
  sections.push("");
5558
6986
  }
@@ -5683,307 +7111,317 @@ async function runTextCall(params) {
5683
7111
  return abortController.signal;
5684
7112
  };
5685
7113
  const signal = resolveAbortSignal();
5686
- try {
5687
- if (provider === "openai") {
5688
- const openAiInput = toOpenAiInput(contents);
5689
- const openAiTools = toOpenAiTools(request.tools);
5690
- const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5691
- const openAiTextConfig = {
5692
- format: request.openAiTextFormat ?? { type: "text" },
5693
- verbosity: resolveOpenAiVerbosity(modelForProvider)
5694
- };
5695
- const reasoning = {
5696
- effort: toOpenAiReasoningEffort(reasoningEffort),
5697
- summary: "detailed"
5698
- };
5699
- await runOpenAiCall(async (client) => {
5700
- const stream = client.responses.stream(
5701
- {
5702
- model: modelForProvider,
5703
- input: openAiInput,
5704
- reasoning,
5705
- text: openAiTextConfig,
5706
- ...openAiTools ? { tools: openAiTools } : {},
5707
- include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5708
- },
5709
- { signal }
7114
+ const { result } = await collectFileUploadMetrics(async () => {
7115
+ try {
7116
+ if (provider === "openai") {
7117
+ const openAiInput = await maybePrepareOpenAiPromptInput(toOpenAiInput(contents));
7118
+ const openAiTools = toOpenAiTools(request.tools);
7119
+ const reasoningEffort = resolveOpenAiReasoningEffort(
7120
+ modelForProvider,
7121
+ request.thinkingLevel
5710
7122
  );
5711
- for await (const event of stream) {
5712
- switch (event.type) {
5713
- case "response.output_text.delta": {
5714
- const delta = event.delta ?? "";
5715
- pushDelta("response", typeof delta === "string" ? delta : "");
5716
- break;
5717
- }
5718
- case "response.reasoning_summary_text.delta": {
5719
- const delta = event.delta ?? "";
5720
- pushDelta("thought", typeof delta === "string" ? delta : "");
5721
- break;
5722
- }
5723
- case "response.refusal.delta": {
5724
- blocked = true;
5725
- queue.push({ type: "blocked" });
5726
- break;
5727
- }
5728
- default:
5729
- break;
5730
- }
5731
- }
5732
- const finalResponse = await stream.finalResponse();
5733
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5734
- queue.push({ type: "model", modelVersion });
5735
- if (finalResponse.error) {
5736
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5737
- throw new Error(message);
5738
- }
5739
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5740
- const detail = finalResponse.incomplete_details?.reason;
5741
- throw new Error(
5742
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
7123
+ const openAiTextConfig = {
7124
+ format: request.openAiTextFormat ?? { type: "text" },
7125
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
7126
+ };
7127
+ const reasoning = {
7128
+ effort: toOpenAiReasoningEffort(reasoningEffort),
7129
+ summary: "detailed"
7130
+ };
7131
+ await runOpenAiCall(async (client) => {
7132
+ const stream = client.responses.stream(
7133
+ {
7134
+ model: modelForProvider,
7135
+ input: openAiInput,
7136
+ reasoning,
7137
+ text: openAiTextConfig,
7138
+ ...openAiTools ? { tools: openAiTools } : {},
7139
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
7140
+ },
7141
+ { signal }
5743
7142
  );
5744
- }
5745
- latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5746
- if (responseParts.length === 0) {
5747
- const fallback = extractOpenAiResponseParts(finalResponse);
5748
- blocked = blocked || fallback.blocked;
5749
- for (const part of fallback.parts) {
5750
- if (part.type === "text") {
5751
- pushDelta(part.thought === true ? "thought" : "response", part.text);
5752
- } else {
5753
- pushInline(part.data, part.mimeType);
7143
+ for await (const event of stream) {
7144
+ switch (event.type) {
7145
+ case "response.output_text.delta": {
7146
+ const delta = event.delta ?? "";
7147
+ pushDelta("response", typeof delta === "string" ? delta : "");
7148
+ break;
7149
+ }
7150
+ case "response.reasoning_summary_text.delta": {
7151
+ const delta = event.delta ?? "";
7152
+ pushDelta("thought", typeof delta === "string" ? delta : "");
7153
+ break;
7154
+ }
7155
+ case "response.refusal.delta": {
7156
+ blocked = true;
7157
+ queue.push({ type: "blocked" });
7158
+ break;
7159
+ }
7160
+ default:
7161
+ break;
5754
7162
  }
5755
7163
  }
5756
- }
5757
- }, modelForProvider);
5758
- } else if (provider === "chatgpt") {
5759
- const chatGptInput = toChatGptInput(contents);
5760
- const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5761
- const openAiTools = toOpenAiTools(request.tools);
5762
- const requestPayload = {
5763
- model: modelForProvider,
5764
- store: false,
5765
- stream: true,
5766
- ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
5767
- instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5768
- input: chatGptInput.input,
5769
- include: ["reasoning.encrypted_content"],
5770
- reasoning: {
5771
- effort: toOpenAiReasoningEffort(reasoningEffort),
5772
- summary: "detailed"
5773
- },
5774
- text: {
5775
- format: request.openAiTextFormat ?? { type: "text" },
5776
- verbosity: resolveOpenAiVerbosity(request.model)
5777
- },
5778
- ...openAiTools ? { tools: openAiTools } : {}
5779
- };
5780
- let sawResponseDelta = false;
5781
- let sawThoughtDelta = false;
5782
- const result = await collectChatGptCodexResponseWithRetry({
5783
- request: requestPayload,
5784
- signal,
5785
- onDelta: (delta) => {
5786
- if (delta.thoughtDelta) {
5787
- sawThoughtDelta = true;
5788
- pushDelta("thought", delta.thoughtDelta);
7164
+ const finalResponse = await stream.finalResponse();
7165
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
7166
+ queue.push({ type: "model", modelVersion });
7167
+ if (finalResponse.error) {
7168
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
7169
+ throw new Error(message);
5789
7170
  }
5790
- if (delta.textDelta) {
5791
- sawResponseDelta = true;
5792
- pushDelta("response", delta.textDelta);
7171
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
7172
+ const detail = finalResponse.incomplete_details?.reason;
7173
+ throw new Error(
7174
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
7175
+ );
5793
7176
  }
5794
- }
5795
- });
5796
- blocked = blocked || result.blocked;
5797
- if (blocked) {
5798
- queue.push({ type: "blocked" });
5799
- }
5800
- if (result.model) {
5801
- modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result.model}`;
5802
- queue.push({ type: "model", modelVersion });
5803
- }
5804
- latestUsage = extractChatGptUsageTokens(result.usage);
5805
- const fallbackText = typeof result.text === "string" ? result.text : "";
5806
- const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5807
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5808
- pushDelta("thought", fallbackThoughts);
5809
- }
5810
- if (!sawResponseDelta && fallbackText.length > 0) {
5811
- pushDelta("response", fallbackText);
5812
- }
5813
- } else if (provider === "fireworks") {
5814
- if (request.tools && request.tools.length > 0) {
5815
- throw new Error(
5816
- "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
5817
- );
5818
- }
5819
- const fireworksMessages = toFireworksMessages(contents, {
5820
- responseMimeType: request.responseMimeType,
5821
- responseJsonSchema: request.responseJsonSchema
5822
- });
5823
- await runFireworksCall(async (client) => {
5824
- const responseFormat = request.responseJsonSchema ? {
5825
- type: "json_schema",
5826
- json_schema: {
5827
- name: "llm-response",
5828
- schema: request.responseJsonSchema
7177
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
7178
+ if (responseParts.length === 0) {
7179
+ const fallback = extractOpenAiResponseParts(finalResponse);
7180
+ blocked = blocked || fallback.blocked;
7181
+ for (const part of fallback.parts) {
7182
+ if (part.type === "text") {
7183
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
7184
+ } else if (part.type === "inlineData") {
7185
+ pushInline(part.data, part.mimeType);
7186
+ }
7187
+ }
5829
7188
  }
5830
- } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5831
- const response = await client.chat.completions.create(
5832
- {
5833
- model: modelForProvider,
5834
- messages: fireworksMessages,
5835
- ...responseFormat ? { response_format: responseFormat } : {}
7189
+ }, modelForProvider);
7190
+ } else if (provider === "chatgpt") {
7191
+ const chatGptInput = toChatGptInput(contents);
7192
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
7193
+ const openAiTools = toOpenAiTools(request.tools);
7194
+ const requestPayload = {
7195
+ model: modelForProvider,
7196
+ store: false,
7197
+ stream: true,
7198
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
7199
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
7200
+ input: chatGptInput.input,
7201
+ include: ["reasoning.encrypted_content"],
7202
+ reasoning: {
7203
+ effort: toOpenAiReasoningEffort(reasoningEffort),
7204
+ summary: "detailed"
5836
7205
  },
5837
- { signal }
5838
- );
5839
- modelVersion = typeof response.model === "string" ? response.model : request.model;
5840
- queue.push({ type: "model", modelVersion });
5841
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5842
- if (choice?.finish_reason === "content_filter") {
5843
- blocked = true;
7206
+ text: {
7207
+ format: request.openAiTextFormat ?? { type: "text" },
7208
+ verbosity: resolveOpenAiVerbosity(request.model)
7209
+ },
7210
+ ...openAiTools ? { tools: openAiTools } : {}
7211
+ };
7212
+ let sawResponseDelta = false;
7213
+ let sawThoughtDelta = false;
7214
+ const result2 = await collectChatGptCodexResponseWithRetry({
7215
+ request: requestPayload,
7216
+ signal,
7217
+ onDelta: (delta) => {
7218
+ if (delta.thoughtDelta) {
7219
+ sawThoughtDelta = true;
7220
+ pushDelta("thought", delta.thoughtDelta);
7221
+ }
7222
+ if (delta.textDelta) {
7223
+ sawResponseDelta = true;
7224
+ pushDelta("response", delta.textDelta);
7225
+ }
7226
+ }
7227
+ });
7228
+ blocked = blocked || result2.blocked;
7229
+ if (blocked) {
5844
7230
  queue.push({ type: "blocked" });
5845
7231
  }
5846
- const textOutput = extractFireworksMessageText(
5847
- choice?.message
5848
- );
5849
- if (textOutput.length > 0) {
5850
- pushDelta("response", textOutput);
7232
+ if (result2.model) {
7233
+ modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result2.model}`;
7234
+ queue.push({ type: "model", modelVersion });
5851
7235
  }
5852
- latestUsage = extractFireworksUsageTokens(response.usage);
5853
- }, modelForProvider);
5854
- } else {
5855
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5856
- const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5857
- const config = {
5858
- maxOutputTokens: 32e3,
5859
- ...thinkingConfig ? { thinkingConfig } : {},
5860
- ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5861
- ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5862
- ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5863
- ...request.imageAspectRatio || request.imageSize ? {
5864
- imageConfig: {
5865
- ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5866
- ...request.imageSize ? { imageSize: request.imageSize } : {}
5867
- }
5868
- } : {}
5869
- };
5870
- const geminiTools = toGeminiTools(request.tools);
5871
- if (geminiTools) {
5872
- config.tools = geminiTools;
5873
- }
5874
- await runGeminiCall(async (client) => {
5875
- const stream = await client.models.generateContentStream({
5876
- model: modelForProvider,
5877
- contents: geminiContents,
5878
- config
7236
+ latestUsage = extractChatGptUsageTokens(result2.usage);
7237
+ const fallbackText = typeof result2.text === "string" ? result2.text : "";
7238
+ const fallbackThoughts = typeof result2.reasoningSummaryText === "string" && result2.reasoningSummaryText.length > 0 ? result2.reasoningSummaryText : typeof result2.reasoningText === "string" ? result2.reasoningText : "";
7239
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
7240
+ pushDelta("thought", fallbackThoughts);
7241
+ }
7242
+ if (!sawResponseDelta && fallbackText.length > 0) {
7243
+ pushDelta("response", fallbackText);
7244
+ }
7245
+ } else if (provider === "fireworks") {
7246
+ if (request.tools && request.tools.length > 0) {
7247
+ throw new Error(
7248
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
7249
+ );
7250
+ }
7251
+ const fireworksMessages = toFireworksMessages(contents, {
7252
+ responseMimeType: request.responseMimeType,
7253
+ responseJsonSchema: request.responseJsonSchema
5879
7254
  });
5880
- let latestGrounding;
5881
- for await (const chunk of stream) {
5882
- if (chunk.modelVersion) {
5883
- modelVersion = chunk.modelVersion;
5884
- queue.push({ type: "model", modelVersion });
5885
- }
5886
- if (chunk.promptFeedback?.blockReason) {
7255
+ await runFireworksCall(async (client) => {
7256
+ const responseFormat = request.responseJsonSchema ? {
7257
+ type: "json_schema",
7258
+ json_schema: {
7259
+ name: "llm-response",
7260
+ schema: request.responseJsonSchema
7261
+ }
7262
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
7263
+ const response = await client.chat.completions.create(
7264
+ {
7265
+ model: modelForProvider,
7266
+ messages: fireworksMessages,
7267
+ ...responseFormat ? { response_format: responseFormat } : {}
7268
+ },
7269
+ { signal }
7270
+ );
7271
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
7272
+ queue.push({ type: "model", modelVersion });
7273
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
7274
+ if (choice?.finish_reason === "content_filter") {
5887
7275
  blocked = true;
5888
7276
  queue.push({ type: "blocked" });
5889
7277
  }
5890
- latestUsage = mergeTokenUpdates(
5891
- latestUsage,
5892
- extractGeminiUsageTokens(chunk.usageMetadata)
7278
+ const textOutput = extractFireworksMessageText(
7279
+ choice?.message
5893
7280
  );
5894
- const candidates = chunk.candidates;
5895
- if (!candidates || candidates.length === 0) {
5896
- continue;
5897
- }
5898
- const primary = candidates[0];
5899
- if (primary && isModerationFinish(primary.finishReason)) {
5900
- blocked = true;
5901
- queue.push({ type: "blocked" });
7281
+ if (textOutput.length > 0) {
7282
+ pushDelta("response", textOutput);
5902
7283
  }
5903
- for (const candidate of candidates) {
5904
- const candidateContent = candidate.content;
5905
- if (!candidateContent) {
5906
- continue;
7284
+ latestUsage = extractFireworksUsageTokens(response.usage);
7285
+ }, modelForProvider);
7286
+ } else {
7287
+ const geminiContents = await maybePrepareGeminiPromptContents(
7288
+ contents.map(convertLlmContentToGeminiContent)
7289
+ );
7290
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
7291
+ const config = {
7292
+ maxOutputTokens: 32e3,
7293
+ ...thinkingConfig ? { thinkingConfig } : {},
7294
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
7295
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
7296
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
7297
+ ...request.imageAspectRatio || request.imageSize ? {
7298
+ imageConfig: {
7299
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
7300
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
7301
+ }
7302
+ } : {}
7303
+ };
7304
+ const geminiTools = toGeminiTools(request.tools);
7305
+ if (geminiTools) {
7306
+ config.tools = geminiTools;
7307
+ }
7308
+ await runGeminiCall(async (client) => {
7309
+ const stream = await client.models.generateContentStream({
7310
+ model: modelForProvider,
7311
+ contents: geminiContents,
7312
+ config
7313
+ });
7314
+ let latestGrounding;
7315
+ for await (const chunk of stream) {
7316
+ if (chunk.modelVersion) {
7317
+ modelVersion = chunk.modelVersion;
7318
+ queue.push({ type: "model", modelVersion });
5907
7319
  }
5908
- if (candidate.groundingMetadata) {
5909
- latestGrounding = candidate.groundingMetadata;
7320
+ if (chunk.promptFeedback?.blockReason) {
7321
+ blocked = true;
7322
+ queue.push({ type: "blocked" });
5910
7323
  }
5911
- const content2 = convertGeminiContentToLlmContent(candidateContent);
5912
- if (!responseRole) {
5913
- responseRole = content2.role;
7324
+ latestUsage = mergeTokenUpdates(
7325
+ latestUsage,
7326
+ extractGeminiUsageTokens(chunk.usageMetadata)
7327
+ );
7328
+ const candidates = chunk.candidates;
7329
+ if (!candidates || candidates.length === 0) {
7330
+ continue;
5914
7331
  }
5915
- for (const part of content2.parts) {
5916
- if (part.type === "text") {
5917
- pushDelta(part.thought === true ? "thought" : "response", part.text);
5918
- } else {
5919
- pushInline(part.data, part.mimeType);
7332
+ const primary = candidates[0];
7333
+ if (primary && isModerationFinish(primary.finishReason)) {
7334
+ blocked = true;
7335
+ queue.push({ type: "blocked" });
7336
+ }
7337
+ for (const candidate of candidates) {
7338
+ const candidateContent = candidate.content;
7339
+ if (!candidateContent) {
7340
+ continue;
7341
+ }
7342
+ if (candidate.groundingMetadata) {
7343
+ latestGrounding = candidate.groundingMetadata;
7344
+ }
7345
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
7346
+ if (!responseRole) {
7347
+ responseRole = content2.role;
7348
+ }
7349
+ for (const part of content2.parts) {
7350
+ if (part.type === "text") {
7351
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
7352
+ } else if (part.type === "inlineData") {
7353
+ pushInline(part.data, part.mimeType);
7354
+ }
5920
7355
  }
5921
7356
  }
5922
7357
  }
7358
+ grounding = latestGrounding;
7359
+ }, modelForProvider);
7360
+ }
7361
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
7362
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
7363
+ const { text, thoughts } = extractTextByChannel(content);
7364
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
7365
+ const costUsd = estimateCallCostUsd({
7366
+ modelId: modelVersion,
7367
+ tokens: latestUsage,
7368
+ responseImages,
7369
+ imageSize: request.imageSize
7370
+ });
7371
+ if (latestUsage) {
7372
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7373
+ }
7374
+ callLogger?.complete({
7375
+ responseText: text,
7376
+ attachments: outputAttachments,
7377
+ metadata: {
7378
+ provider,
7379
+ model: request.model,
7380
+ modelVersion,
7381
+ blocked,
7382
+ costUsd,
7383
+ usage: latestUsage,
7384
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
7385
+ responseChars: text.length,
7386
+ thoughtChars: thoughts.length,
7387
+ responseImages,
7388
+ uploads: getCurrentFileUploadMetrics()
5923
7389
  }
5924
- grounding = latestGrounding;
5925
- }, modelForProvider);
5926
- }
5927
- const mergedParts = mergeConsecutiveTextParts(responseParts);
5928
- const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5929
- const { text, thoughts } = extractTextByChannel(content);
5930
- const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5931
- const costUsd = estimateCallCostUsd({
5932
- modelId: modelVersion,
5933
- tokens: latestUsage,
5934
- responseImages,
5935
- imageSize: request.imageSize
5936
- });
5937
- if (latestUsage) {
5938
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5939
- }
5940
- callLogger?.complete({
5941
- responseText: text,
5942
- attachments: outputAttachments,
5943
- metadata: {
5944
- provider,
5945
- model: request.model,
5946
- modelVersion,
5947
- blocked,
5948
- costUsd,
5949
- usage: latestUsage,
5950
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5951
- responseChars: text.length,
5952
- thoughtChars: thoughts.length,
5953
- responseImages
5954
- }
5955
- });
5956
- return {
5957
- provider,
5958
- model: request.model,
5959
- modelVersion,
5960
- content,
5961
- text,
5962
- thoughts,
5963
- blocked,
5964
- usage: latestUsage,
5965
- costUsd,
5966
- grounding
5967
- };
5968
- } catch (error) {
5969
- const partialParts = mergeConsecutiveTextParts(responseParts);
5970
- const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
5971
- const { text: partialText } = extractTextByChannel(partialContent);
5972
- callLogger?.fail(error, {
5973
- responseText: partialText,
5974
- attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
5975
- metadata: {
7390
+ });
7391
+ return {
5976
7392
  provider,
5977
7393
  model: request.model,
5978
7394
  modelVersion,
7395
+ content,
7396
+ text,
7397
+ thoughts,
5979
7398
  blocked,
5980
7399
  usage: latestUsage,
5981
- partialResponseParts: responseParts.length,
5982
- responseImages
5983
- }
5984
- });
5985
- throw error;
5986
- }
7400
+ costUsd,
7401
+ grounding
7402
+ };
7403
+ } catch (error) {
7404
+ const partialParts = mergeConsecutiveTextParts(responseParts);
7405
+ const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
7406
+ const { text: partialText } = extractTextByChannel(partialContent);
7407
+ callLogger?.fail(error, {
7408
+ responseText: partialText,
7409
+ attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
7410
+ metadata: {
7411
+ provider,
7412
+ model: request.model,
7413
+ modelVersion,
7414
+ blocked,
7415
+ usage: latestUsage,
7416
+ partialResponseParts: responseParts.length,
7417
+ responseImages,
7418
+ uploads: getCurrentFileUploadMetrics()
7419
+ }
7420
+ });
7421
+ throw error;
7422
+ }
7423
+ });
7424
+ return result;
5987
7425
  }
5988
7426
  function streamText(request) {
5989
7427
  const queue = createAsyncQueue();
@@ -6266,7 +7704,12 @@ function normalizeToolLoopSteeringInput(input) {
6266
7704
  if (part.type === "text") {
6267
7705
  parts.push({ type: "text", text: part.text });
6268
7706
  } else {
6269
- parts.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
7707
+ parts.push({
7708
+ type: "inlineData",
7709
+ data: part.data,
7710
+ mimeType: part.mimeType,
7711
+ filename: part.filename
7712
+ });
6270
7713
  }
6271
7714
  }
6272
7715
  if (parts.length > 0) {
@@ -6454,9 +7897,10 @@ async function runToolLoop(request) {
6454
7897
  let reasoningSummary = "";
6455
7898
  let stepToolCallText;
6456
7899
  let stepToolCallPayload;
7900
+ const preparedInput = await maybePrepareOpenAiPromptInput(input);
6457
7901
  const stepRequestPayload = {
6458
7902
  model: providerInfo.model,
6459
- input,
7903
+ input: preparedInput,
6460
7904
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
6461
7905
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
6462
7906
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -6484,7 +7928,7 @@ async function runToolLoop(request) {
6484
7928
  const stream = client.responses.stream(
6485
7929
  {
6486
7930
  model: providerInfo.model,
6487
- input,
7931
+ input: preparedInput,
6488
7932
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
6489
7933
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
6490
7934
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -6661,27 +8105,29 @@ async function runToolLoop(request) {
6661
8105
  input: entry.value
6662
8106
  });
6663
8107
  }
6664
- const callResults = await Promise.all(
6665
- callInputs.map(async (entry) => {
6666
- return await toolCallContextStorage.run(
6667
- {
6668
- toolName: entry.toolName,
6669
- toolId: entry.toolId,
6670
- turn: entry.turn,
6671
- toolIndex: entry.toolIndex
6672
- },
6673
- async () => {
6674
- const { result, outputPayload } = await executeToolCall({
6675
- callKind: entry.call.kind,
8108
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8109
+ await Promise.all(
8110
+ callInputs.map(async (entry) => {
8111
+ return await toolCallContextStorage.run(
8112
+ {
6676
8113
  toolName: entry.toolName,
6677
- tool: request.tools[entry.toolName],
6678
- rawInput: entry.value,
6679
- parseError: entry.parseError
6680
- });
6681
- return { entry, result, outputPayload };
6682
- }
6683
- );
6684
- })
8114
+ toolId: entry.toolId,
8115
+ turn: entry.turn,
8116
+ toolIndex: entry.toolIndex
8117
+ },
8118
+ async () => {
8119
+ const { result, outputPayload } = await executeToolCall({
8120
+ callKind: entry.call.kind,
8121
+ toolName: entry.toolName,
8122
+ tool: request.tools[entry.toolName],
8123
+ rawInput: entry.value,
8124
+ parseError: entry.parseError
8125
+ });
8126
+ return { entry, result, outputPayload };
8127
+ }
8128
+ );
8129
+ })
8130
+ )
6685
8131
  );
6686
8132
  const toolOutputs = [];
6687
8133
  let toolExecutionMs = 0;
@@ -6788,7 +8234,7 @@ async function runToolLoop(request) {
6788
8234
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
6789
8235
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
6790
8236
  const toolLoopInput = toChatGptInput(contents);
6791
- const conversationId = `tool-loop-${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
8237
+ const conversationId = `tool-loop-${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`;
6792
8238
  const promptCacheKey = conversationId;
6793
8239
  let input = [...toolLoopInput.input];
6794
8240
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
@@ -6962,27 +8408,29 @@ async function runToolLoop(request) {
6962
8408
  input: entry.value
6963
8409
  });
6964
8410
  }
6965
- const callResults = await Promise.all(
6966
- callInputs.map(async (entry) => {
6967
- return await toolCallContextStorage.run(
6968
- {
6969
- toolName: entry.toolName,
6970
- toolId: entry.toolId,
6971
- turn: entry.turn,
6972
- toolIndex: entry.toolIndex
6973
- },
6974
- async () => {
6975
- const { result, outputPayload } = await executeToolCall({
6976
- callKind: entry.call.kind,
8411
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8412
+ await Promise.all(
8413
+ callInputs.map(async (entry) => {
8414
+ return await toolCallContextStorage.run(
8415
+ {
6977
8416
  toolName: entry.toolName,
6978
- tool: request.tools[entry.toolName],
6979
- rawInput: entry.value,
6980
- parseError: entry.parseError
6981
- });
6982
- return { entry, result, outputPayload };
6983
- }
6984
- );
6985
- })
8417
+ toolId: entry.toolId,
8418
+ turn: entry.turn,
8419
+ toolIndex: entry.toolIndex
8420
+ },
8421
+ async () => {
8422
+ const { result, outputPayload } = await executeToolCall({
8423
+ callKind: entry.call.kind,
8424
+ toolName: entry.toolName,
8425
+ tool: request.tools[entry.toolName],
8426
+ rawInput: entry.value,
8427
+ parseError: entry.parseError
8428
+ });
8429
+ return { entry, result, outputPayload };
8430
+ }
8431
+ );
8432
+ })
8433
+ )
6986
8434
  );
6987
8435
  let toolExecutionMs = 0;
6988
8436
  let waitToolMs = 0;
@@ -7254,27 +8702,29 @@ async function runToolLoop(request) {
7254
8702
  input: entry.value
7255
8703
  });
7256
8704
  }
7257
- const callResults = await Promise.all(
7258
- callInputs.map(async (entry) => {
7259
- return await toolCallContextStorage.run(
7260
- {
7261
- toolName: entry.toolName,
7262
- toolId: entry.toolId,
7263
- turn: entry.turn,
7264
- toolIndex: entry.toolIndex
7265
- },
7266
- async () => {
7267
- const { result, outputPayload } = await executeToolCall({
7268
- callKind: "function",
8705
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8706
+ await Promise.all(
8707
+ callInputs.map(async (entry) => {
8708
+ return await toolCallContextStorage.run(
8709
+ {
7269
8710
  toolName: entry.toolName,
7270
- tool: request.tools[entry.toolName],
7271
- rawInput: entry.value,
7272
- parseError: entry.parseError
7273
- });
7274
- return { entry, result, outputPayload };
7275
- }
7276
- );
7277
- })
8711
+ toolId: entry.toolId,
8712
+ turn: entry.turn,
8713
+ toolIndex: entry.toolIndex
8714
+ },
8715
+ async () => {
8716
+ const { result, outputPayload } = await executeToolCall({
8717
+ callKind: "function",
8718
+ toolName: entry.toolName,
8719
+ tool: request.tools[entry.toolName],
8720
+ rawInput: entry.value,
8721
+ parseError: entry.parseError
8722
+ });
8723
+ return { entry, result, outputPayload };
8724
+ }
8725
+ );
8726
+ })
8727
+ )
7278
8728
  );
7279
8729
  const assistantToolCalls = [];
7280
8730
  const toolMessages = [];
@@ -7413,9 +8863,10 @@ async function runToolLoop(request) {
7413
8863
  ...thinkingConfig ? { thinkingConfig } : {}
7414
8864
  };
7415
8865
  const onEvent = request.onEvent;
8866
+ const preparedGeminiContents = await maybePrepareGeminiPromptContents(geminiContents);
7416
8867
  const stepRequestPayload = {
7417
8868
  model: request.model,
7418
- contents: geminiContents,
8869
+ contents: preparedGeminiContents,
7419
8870
  config
7420
8871
  };
7421
8872
  const stepCallLogger = startLlmCallLoggerFromPayload({
@@ -7429,7 +8880,7 @@ async function runToolLoop(request) {
7429
8880
  async (client) => {
7430
8881
  const stream = await client.models.generateContentStream({
7431
8882
  model: request.model,
7432
- contents: geminiContents,
8883
+ contents: preparedGeminiContents,
7433
8884
  config
7434
8885
  });
7435
8886
  let responseText2 = "";
@@ -7605,26 +9056,28 @@ async function runToolLoop(request) {
7605
9056
  input: entry.rawInput
7606
9057
  });
7607
9058
  }
7608
- const callResults = await Promise.all(
7609
- callInputs.map(async (entry) => {
7610
- return await toolCallContextStorage.run(
7611
- {
7612
- toolName: entry.toolName,
7613
- toolId: entry.toolId,
7614
- turn: entry.turn,
7615
- toolIndex: entry.toolIndex
7616
- },
7617
- async () => {
7618
- const { result, outputPayload } = await executeToolCall({
7619
- callKind: "function",
9059
+ const callResults = await maybeSpillCombinedToolCallOutputs(
9060
+ await Promise.all(
9061
+ callInputs.map(async (entry) => {
9062
+ return await toolCallContextStorage.run(
9063
+ {
7620
9064
  toolName: entry.toolName,
7621
- tool: request.tools[entry.toolName],
7622
- rawInput: entry.rawInput
7623
- });
7624
- return { entry, result, outputPayload };
7625
- }
7626
- );
7627
- })
9065
+ toolId: entry.toolId,
9066
+ turn: entry.turn,
9067
+ toolIndex: entry.toolIndex
9068
+ },
9069
+ async () => {
9070
+ const { result, outputPayload } = await executeToolCall({
9071
+ callKind: "function",
9072
+ toolName: entry.toolName,
9073
+ tool: request.tools[entry.toolName],
9074
+ rawInput: entry.rawInput
9075
+ });
9076
+ return { entry, result, outputPayload };
9077
+ }
9078
+ );
9079
+ })
9080
+ )
7628
9081
  );
7629
9082
  let toolExecutionMs = 0;
7630
9083
  let waitToolMs = 0;
@@ -8028,11 +9481,11 @@ ${lines}`;
8028
9481
  }
8029
9482
 
8030
9483
  // src/agent.ts
8031
- var import_node_crypto3 = require("crypto");
8032
- var import_node_path7 = __toESM(require("path"), 1);
9484
+ var import_node_crypto4 = require("crypto");
9485
+ var import_node_path9 = __toESM(require("path"), 1);
8033
9486
 
8034
9487
  // src/agent/subagents.ts
8035
- var import_node_crypto2 = require("crypto");
9488
+ var import_node_crypto3 = require("crypto");
8036
9489
  var import_zod4 = require("zod");
8037
9490
  var DEFAULT_SUBAGENT_MAX_AGENTS = 6;
8038
9491
  var DEFAULT_SUBAGENT_MAX_DEPTH = 1;
@@ -8252,7 +9705,7 @@ function createSubagentToolController(options) {
8252
9705
  }
8253
9706
  model = input.model;
8254
9707
  }
8255
- const id = `agent_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
9708
+ const id = `agent_${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
8256
9709
  const now = Date.now();
8257
9710
  const { roleName, roleInstructions } = resolveAgentType(input.agent_type);
8258
9711
  const nickname = reserveAgentNickname(roleName, roleNicknameCounts);
@@ -8504,26 +9957,26 @@ function resolveInputItemsText(items) {
8504
9957
  }
8505
9958
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
8506
9959
  const name = typeof item.name === "string" ? item.name.trim() : "";
8507
- const path8 = typeof item.path === "string" ? item.path.trim() : "";
9960
+ const path10 = typeof item.path === "string" ? item.path.trim() : "";
8508
9961
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
8509
9962
  if (itemType === "image") {
8510
9963
  lines.push("[image]");
8511
9964
  continue;
8512
9965
  }
8513
- if (itemType === "local_image" && path8) {
8514
- lines.push(`[local_image:${path8}]`);
9966
+ if (itemType === "local_image" && path10) {
9967
+ lines.push(`[local_image:${path10}]`);
8515
9968
  continue;
8516
9969
  }
8517
- if (itemType === "skill" && name && path8) {
8518
- lines.push(`[skill:$${name}](${path8})`);
9970
+ if (itemType === "skill" && name && path10) {
9971
+ lines.push(`[skill:$${name}](${path10})`);
8519
9972
  continue;
8520
9973
  }
8521
- if (itemType === "mention" && name && path8) {
8522
- lines.push(`[mention:$${name}](${path8})`);
9974
+ if (itemType === "mention" && name && path10) {
9975
+ lines.push(`[mention:$${name}](${path10})`);
8523
9976
  continue;
8524
9977
  }
8525
- if (path8 || imageUrl) {
8526
- lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
9978
+ if (path10 || imageUrl) {
9979
+ lines.push(`[${itemType || "input"}:${path10 || imageUrl}]`);
8527
9980
  continue;
8528
9981
  }
8529
9982
  if (name) {
@@ -8812,7 +10265,7 @@ function joinInstructionBlocks(...blocks) {
8812
10265
  return parts.join("\n\n");
8813
10266
  }
8814
10267
  function randomSubmissionId() {
8815
- return `sub_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
10268
+ return `sub_${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
8816
10269
  }
8817
10270
  function normalizeInteger(value, fallback, min, max) {
8818
10271
  const parsed = Number.isFinite(value) ? Math.floor(value) : fallback;
@@ -8841,27 +10294,27 @@ function sleep2(ms) {
8841
10294
  }
8842
10295
 
8843
10296
  // src/tools/filesystemTools.ts
8844
- var import_node_path6 = __toESM(require("path"), 1);
8845
- var import_node_buffer4 = require("buffer");
10297
+ var import_node_path8 = __toESM(require("path"), 1);
10298
+ var import_node_buffer5 = require("buffer");
8846
10299
  var import_zod6 = require("zod");
8847
10300
 
8848
10301
  // src/tools/applyPatch.ts
8849
- var import_node_path5 = __toESM(require("path"), 1);
10302
+ var import_node_path7 = __toESM(require("path"), 1);
8850
10303
  var import_zod5 = require("zod");
8851
10304
 
8852
10305
  // src/tools/filesystem.ts
8853
- var import_node_fs3 = require("fs");
8854
- var import_node_path4 = __toESM(require("path"), 1);
10306
+ var import_node_fs4 = require("fs");
10307
+ var import_node_path6 = __toESM(require("path"), 1);
8855
10308
  var InMemoryAgentFilesystem = class {
8856
10309
  #files = /* @__PURE__ */ new Map();
8857
10310
  #dirs = /* @__PURE__ */ new Map();
8858
10311
  #clock = 0;
8859
10312
  constructor(initialFiles = {}) {
8860
- const root = import_node_path4.default.resolve("/");
10313
+ const root = import_node_path6.default.resolve("/");
8861
10314
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
8862
10315
  for (const [filePath, content] of Object.entries(initialFiles)) {
8863
- const absolutePath = import_node_path4.default.resolve(filePath);
8864
- this.#ensureDirSync(import_node_path4.default.dirname(absolutePath));
10316
+ const absolutePath = import_node_path6.default.resolve(filePath);
10317
+ this.#ensureDirSync(import_node_path6.default.dirname(absolutePath));
8865
10318
  this.#files.set(absolutePath, {
8866
10319
  content,
8867
10320
  mtimeMs: this.#nextMtime()
@@ -8869,7 +10322,7 @@ var InMemoryAgentFilesystem = class {
8869
10322
  }
8870
10323
  }
8871
10324
  async readTextFile(filePath) {
8872
- const absolutePath = import_node_path4.default.resolve(filePath);
10325
+ const absolutePath = import_node_path6.default.resolve(filePath);
8873
10326
  const file = this.#files.get(absolutePath);
8874
10327
  if (!file) {
8875
10328
  throw createNoSuchFileError("open", absolutePath);
@@ -8881,24 +10334,24 @@ var InMemoryAgentFilesystem = class {
8881
10334
  return Buffer.from(content, "utf8");
8882
10335
  }
8883
10336
  async writeTextFile(filePath, content) {
8884
- const absolutePath = import_node_path4.default.resolve(filePath);
8885
- const parentPath = import_node_path4.default.dirname(absolutePath);
10337
+ const absolutePath = import_node_path6.default.resolve(filePath);
10338
+ const parentPath = import_node_path6.default.dirname(absolutePath);
8886
10339
  if (!this.#dirs.has(parentPath)) {
8887
10340
  throw createNoSuchFileError("open", parentPath);
8888
10341
  }
8889
10342
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
8890
10343
  }
8891
10344
  async deleteFile(filePath) {
8892
- const absolutePath = import_node_path4.default.resolve(filePath);
10345
+ const absolutePath = import_node_path6.default.resolve(filePath);
8893
10346
  if (!this.#files.delete(absolutePath)) {
8894
10347
  throw createNoSuchFileError("unlink", absolutePath);
8895
10348
  }
8896
10349
  }
8897
10350
  async ensureDir(directoryPath) {
8898
- this.#ensureDirSync(import_node_path4.default.resolve(directoryPath));
10351
+ this.#ensureDirSync(import_node_path6.default.resolve(directoryPath));
8899
10352
  }
8900
10353
  async readDir(directoryPath) {
8901
- const absolutePath = import_node_path4.default.resolve(directoryPath);
10354
+ const absolutePath = import_node_path6.default.resolve(directoryPath);
8902
10355
  const directory = this.#dirs.get(absolutePath);
8903
10356
  if (!directory) {
8904
10357
  throw createNoSuchFileError("scandir", absolutePath);
@@ -8909,10 +10362,10 @@ var InMemoryAgentFilesystem = class {
8909
10362
  if (dirPath === absolutePath) {
8910
10363
  continue;
8911
10364
  }
8912
- if (import_node_path4.default.dirname(dirPath) !== absolutePath) {
10365
+ if (import_node_path6.default.dirname(dirPath) !== absolutePath) {
8913
10366
  continue;
8914
10367
  }
8915
- const name = import_node_path4.default.basename(dirPath);
10368
+ const name = import_node_path6.default.basename(dirPath);
8916
10369
  if (seenNames.has(name)) {
8917
10370
  continue;
8918
10371
  }
@@ -8925,10 +10378,10 @@ var InMemoryAgentFilesystem = class {
8925
10378
  });
8926
10379
  }
8927
10380
  for (const [filePath, fileRecord] of this.#files.entries()) {
8928
- if (import_node_path4.default.dirname(filePath) !== absolutePath) {
10381
+ if (import_node_path6.default.dirname(filePath) !== absolutePath) {
8929
10382
  continue;
8930
10383
  }
8931
- const name = import_node_path4.default.basename(filePath);
10384
+ const name = import_node_path6.default.basename(filePath);
8932
10385
  if (seenNames.has(name)) {
8933
10386
  continue;
8934
10387
  }
@@ -8944,7 +10397,7 @@ var InMemoryAgentFilesystem = class {
8944
10397
  return entries;
8945
10398
  }
8946
10399
  async stat(entryPath) {
8947
- const absolutePath = import_node_path4.default.resolve(entryPath);
10400
+ const absolutePath = import_node_path6.default.resolve(entryPath);
8948
10401
  const file = this.#files.get(absolutePath);
8949
10402
  if (file) {
8950
10403
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -8960,7 +10413,7 @@ var InMemoryAgentFilesystem = class {
8960
10413
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
8961
10414
  }
8962
10415
  #ensureDirSync(directoryPath) {
8963
- const absolutePath = import_node_path4.default.resolve(directoryPath);
10416
+ const absolutePath = import_node_path6.default.resolve(directoryPath);
8964
10417
  const parts = [];
8965
10418
  let cursor = absolutePath;
8966
10419
  for (; ; ) {
@@ -8968,7 +10421,7 @@ var InMemoryAgentFilesystem = class {
8968
10421
  break;
8969
10422
  }
8970
10423
  parts.push(cursor);
8971
- const parent = import_node_path4.default.dirname(cursor);
10424
+ const parent = import_node_path6.default.dirname(cursor);
8972
10425
  if (parent === cursor) {
8973
10426
  break;
8974
10427
  }
@@ -8991,19 +10444,19 @@ var InMemoryAgentFilesystem = class {
8991
10444
  };
8992
10445
  function createNodeAgentFilesystem() {
8993
10446
  return {
8994
- readTextFile: async (filePath) => import_node_fs3.promises.readFile(filePath, "utf8"),
8995
- readBinaryFile: async (filePath) => import_node_fs3.promises.readFile(filePath),
8996
- writeTextFile: async (filePath, content) => import_node_fs3.promises.writeFile(filePath, content, "utf8"),
8997
- deleteFile: async (filePath) => import_node_fs3.promises.unlink(filePath),
10447
+ readTextFile: async (filePath) => import_node_fs4.promises.readFile(filePath, "utf8"),
10448
+ readBinaryFile: async (filePath) => import_node_fs4.promises.readFile(filePath),
10449
+ writeTextFile: async (filePath, content) => import_node_fs4.promises.writeFile(filePath, content, "utf8"),
10450
+ deleteFile: async (filePath) => import_node_fs4.promises.unlink(filePath),
8998
10451
  ensureDir: async (directoryPath) => {
8999
- await import_node_fs3.promises.mkdir(directoryPath, { recursive: true });
10452
+ await import_node_fs4.promises.mkdir(directoryPath, { recursive: true });
9000
10453
  },
9001
10454
  readDir: async (directoryPath) => {
9002
- const entries = await import_node_fs3.promises.readdir(directoryPath, { withFileTypes: true });
10455
+ const entries = await import_node_fs4.promises.readdir(directoryPath, { withFileTypes: true });
9003
10456
  const result = [];
9004
10457
  for (const entry of entries) {
9005
- const entryPath = import_node_path4.default.resolve(directoryPath, entry.name);
9006
- const stats = await import_node_fs3.promises.lstat(entryPath);
10458
+ const entryPath = import_node_path6.default.resolve(directoryPath, entry.name);
10459
+ const stats = await import_node_fs4.promises.lstat(entryPath);
9007
10460
  result.push({
9008
10461
  name: entry.name,
9009
10462
  path: entryPath,
@@ -9014,7 +10467,7 @@ function createNodeAgentFilesystem() {
9014
10467
  return result;
9015
10468
  },
9016
10469
  stat: async (entryPath) => {
9017
- const stats = await import_node_fs3.promises.lstat(entryPath);
10470
+ const stats = await import_node_fs4.promises.lstat(entryPath);
9018
10471
  return {
9019
10472
  kind: statsToKind(stats),
9020
10473
  mtimeMs: stats.mtimeMs
@@ -9166,7 +10619,7 @@ function createApplyPatchTool(options = {}) {
9166
10619
  });
9167
10620
  }
9168
10621
  async function applyPatch(request) {
9169
- const cwd = import_node_path5.default.resolve(request.cwd ?? process.cwd());
10622
+ const cwd = import_node_path7.default.resolve(request.cwd ?? process.cwd());
9170
10623
  const adapter = request.fs ?? createNodeAgentFilesystem();
9171
10624
  const allowOutsideCwd = request.allowOutsideCwd === true;
9172
10625
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -9188,7 +10641,7 @@ async function applyPatch(request) {
9188
10641
  kind: "add",
9189
10642
  path: absolutePath2
9190
10643
  });
9191
- await adapter.ensureDir(import_node_path5.default.dirname(absolutePath2));
10644
+ await adapter.ensureDir(import_node_path7.default.dirname(absolutePath2));
9192
10645
  await adapter.writeTextFile(absolutePath2, operation.content);
9193
10646
  added.push(toDisplayPath(absolutePath2, cwd));
9194
10647
  continue;
@@ -9222,7 +10675,7 @@ async function applyPatch(request) {
9222
10675
  fromPath: absolutePath,
9223
10676
  toPath: destinationPath
9224
10677
  });
9225
- await adapter.ensureDir(import_node_path5.default.dirname(destinationPath));
10678
+ await adapter.ensureDir(import_node_path7.default.dirname(destinationPath));
9226
10679
  await adapter.writeTextFile(destinationPath, next);
9227
10680
  await adapter.deleteFile(absolutePath);
9228
10681
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -9253,22 +10706,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
9253
10706
  if (trimmed.length === 0) {
9254
10707
  throw new Error("apply_patch failed: empty file path");
9255
10708
  }
9256
- const absolutePath = import_node_path5.default.isAbsolute(trimmed) ? import_node_path5.default.resolve(trimmed) : import_node_path5.default.resolve(cwd, trimmed);
10709
+ const absolutePath = import_node_path7.default.isAbsolute(trimmed) ? import_node_path7.default.resolve(trimmed) : import_node_path7.default.resolve(cwd, trimmed);
9257
10710
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
9258
10711
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
9259
10712
  }
9260
10713
  return absolutePath;
9261
10714
  }
9262
10715
  function isPathInsideCwd(candidatePath, cwd) {
9263
- const relative = import_node_path5.default.relative(cwd, candidatePath);
9264
- return relative === "" || !relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative);
10716
+ const relative = import_node_path7.default.relative(cwd, candidatePath);
10717
+ return relative === "" || !relative.startsWith("..") && !import_node_path7.default.isAbsolute(relative);
9265
10718
  }
9266
10719
  function toDisplayPath(absolutePath, cwd) {
9267
- const relative = import_node_path5.default.relative(cwd, absolutePath);
10720
+ const relative = import_node_path7.default.relative(cwd, absolutePath);
9268
10721
  if (relative === "") {
9269
10722
  return ".";
9270
10723
  }
9271
- if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
10724
+ if (!relative.startsWith("..") && !import_node_path7.default.isAbsolute(relative)) {
9272
10725
  return relative;
9273
10726
  }
9274
10727
  return absolutePath;
@@ -10035,7 +11488,7 @@ async function readBinaryFile(filesystem, filePath) {
10035
11488
  return await filesystem.readBinaryFile(filePath);
10036
11489
  }
10037
11490
  const text = await filesystem.readTextFile(filePath);
10038
- return import_node_buffer4.Buffer.from(text, "utf8");
11491
+ return import_node_buffer5.Buffer.from(text, "utf8");
10039
11492
  }
10040
11493
  function detectImageMimeType(buffer, filePath) {
10041
11494
  if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) {
@@ -10053,7 +11506,7 @@ function detectImageMimeType(buffer, filePath) {
10053
11506
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
10054
11507
  return "image/webp";
10055
11508
  }
10056
- const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path6.default.extname(filePath).toLowerCase()];
11509
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path8.default.extname(filePath).toLowerCase()];
10057
11510
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
10058
11511
  return fromExtension;
10059
11512
  }
@@ -10063,13 +11516,13 @@ function isPdfFile(buffer, filePath) {
10063
11516
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
10064
11517
  return true;
10065
11518
  }
10066
- return import_node_path6.default.extname(filePath).toLowerCase() === ".pdf";
11519
+ return import_node_path8.default.extname(filePath).toLowerCase() === ".pdf";
10067
11520
  }
10068
11521
  function isValidUtf8(buffer) {
10069
11522
  if (buffer.length === 0) {
10070
11523
  return true;
10071
11524
  }
10072
- return import_node_buffer4.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
11525
+ return import_node_buffer5.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
10073
11526
  }
10074
11527
  async function readFileGemini(input, options) {
10075
11528
  const runtime = resolveRuntime(options);
@@ -10101,7 +11554,7 @@ async function writeFileGemini(input, options) {
10101
11554
  action: "write",
10102
11555
  path: filePath
10103
11556
  });
10104
- await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
11557
+ await runtime.filesystem.ensureDir(import_node_path8.default.dirname(filePath));
10105
11558
  await runtime.filesystem.writeTextFile(filePath, input.content);
10106
11559
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
10107
11560
  }
@@ -10122,7 +11575,7 @@ async function replaceFileContentGemini(input, options) {
10122
11575
  originalContent = await runtime.filesystem.readTextFile(filePath);
10123
11576
  } catch (error) {
10124
11577
  if (isNoEntError(error) && oldValue.length === 0) {
10125
- await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
11578
+ await runtime.filesystem.ensureDir(import_node_path8.default.dirname(filePath));
10126
11579
  await runtime.filesystem.writeTextFile(filePath, newValue);
10127
11580
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
10128
11581
  }
@@ -10292,15 +11745,15 @@ async function globFilesGemini(input, options) {
10292
11745
  throw new Error(`Path is not a directory: ${dirPath}`);
10293
11746
  }
10294
11747
  const matcher = createGlobMatcher(input.pattern, input.case_sensitive === true);
10295
- const files = await collectSearchFiles({
11748
+ const files2 = await collectSearchFiles({
10296
11749
  filesystem: runtime.filesystem,
10297
11750
  searchPath: dirPath,
10298
11751
  rootKind: "directory",
10299
11752
  maxScannedFiles: runtime.grepMaxScannedFiles
10300
11753
  });
10301
11754
  const matched = [];
10302
- for (const filePath of files) {
10303
- const relativePath = normalizeSlashes(import_node_path6.default.relative(dirPath, filePath));
11755
+ for (const filePath of files2) {
11756
+ const relativePath = normalizeSlashes(import_node_path8.default.relative(dirPath, filePath));
10304
11757
  if (!matcher(relativePath)) {
10305
11758
  continue;
10306
11759
  }
@@ -10318,7 +11771,7 @@ async function globFilesGemini(input, options) {
10318
11771
  }
10319
11772
  function resolveRuntime(options) {
10320
11773
  return {
10321
- cwd: import_node_path6.default.resolve(options.cwd ?? process.cwd()),
11774
+ cwd: import_node_path8.default.resolve(options.cwd ?? process.cwd()),
10322
11775
  filesystem: options.fs ?? createNodeAgentFilesystem(),
10323
11776
  allowOutsideCwd: options.allowOutsideCwd === true,
10324
11777
  checkAccess: options.checkAccess,
@@ -10349,13 +11802,13 @@ function mapApplyPatchAction(action) {
10349
11802
  return "move";
10350
11803
  }
10351
11804
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
10352
- const absolutePath = import_node_path6.default.isAbsolute(inputPath) ? import_node_path6.default.resolve(inputPath) : import_node_path6.default.resolve(cwd, inputPath);
11805
+ const absolutePath = import_node_path8.default.isAbsolute(inputPath) ? import_node_path8.default.resolve(inputPath) : import_node_path8.default.resolve(cwd, inputPath);
10353
11806
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
10354
11807
  return absolutePath;
10355
11808
  }
10356
- if (import_node_path6.default.isAbsolute(inputPath)) {
11809
+ if (import_node_path8.default.isAbsolute(inputPath)) {
10357
11810
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
10358
- const sandboxRootedPath = import_node_path6.default.resolve(cwd, sandboxRelativePath);
11811
+ const sandboxRootedPath = import_node_path8.default.resolve(cwd, sandboxRelativePath);
10359
11812
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
10360
11813
  return sandboxRootedPath;
10361
11814
  }
@@ -10363,25 +11816,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
10363
11816
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
10364
11817
  }
10365
11818
  function isPathInsideCwd2(candidatePath, cwd) {
10366
- const relative = import_node_path6.default.relative(cwd, candidatePath);
10367
- return relative === "" || !relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative);
11819
+ const relative = import_node_path8.default.relative(cwd, candidatePath);
11820
+ return relative === "" || !relative.startsWith("..") && !import_node_path8.default.isAbsolute(relative);
10368
11821
  }
10369
11822
  function toDisplayPath2(absolutePath, cwd) {
10370
- const relative = import_node_path6.default.relative(cwd, absolutePath);
11823
+ const relative = import_node_path8.default.relative(cwd, absolutePath);
10371
11824
  if (relative === "") {
10372
11825
  return ".";
10373
11826
  }
10374
- if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
11827
+ if (!relative.startsWith("..") && !import_node_path8.default.isAbsolute(relative)) {
10375
11828
  return relative;
10376
11829
  }
10377
11830
  return absolutePath;
10378
11831
  }
10379
11832
  function toSandboxDisplayPath(absolutePath, cwd) {
10380
- const relative = import_node_path6.default.relative(cwd, absolutePath);
11833
+ const relative = import_node_path8.default.relative(cwd, absolutePath);
10381
11834
  if (relative === "") {
10382
11835
  return "/";
10383
11836
  }
10384
- if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
11837
+ if (!relative.startsWith("..") && !import_node_path8.default.isAbsolute(relative)) {
10385
11838
  return `/${normalizeSlashes(relative)}`;
10386
11839
  }
10387
11840
  return normalizeSlashes(absolutePath);
@@ -10457,7 +11910,7 @@ async function collectSearchFiles(params) {
10457
11910
  return [searchPath];
10458
11911
  }
10459
11912
  const queue = [searchPath];
10460
- const files = [];
11913
+ const files2 = [];
10461
11914
  while (queue.length > 0) {
10462
11915
  const current = queue.shift();
10463
11916
  if (!current) {
@@ -10472,13 +11925,13 @@ async function collectSearchFiles(params) {
10472
11925
  if (entry.kind !== "file") {
10473
11926
  continue;
10474
11927
  }
10475
- files.push(entry.path);
10476
- if (files.length >= maxScannedFiles) {
10477
- return files;
11928
+ files2.push(entry.path);
11929
+ if (files2.length >= maxScannedFiles) {
11930
+ return files2;
10478
11931
  }
10479
11932
  }
10480
11933
  }
10481
- return files;
11934
+ return files2;
10482
11935
  }
10483
11936
  function compileRegex(pattern, flags = "m") {
10484
11937
  try {
@@ -10497,7 +11950,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
10497
11950
  }));
10498
11951
  return (candidatePath) => {
10499
11952
  const normalizedPath = normalizeSlashes(candidatePath);
10500
- const basename = import_node_path6.default.posix.basename(normalizedPath);
11953
+ const basename = import_node_path8.default.posix.basename(normalizedPath);
10501
11954
  return compiled.some(
10502
11955
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
10503
11956
  );
@@ -10774,13 +12227,24 @@ async function runAgentLoopInternal(request, context) {
10774
12227
  }
10775
12228
  streamEventLogger?.appendEvent(event);
10776
12229
  } : void 0;
12230
+ let uploadMetrics = emptyFileUploadMetrics();
10777
12231
  try {
10778
- const result = await runToolLoop({
10779
- ...toolLoopRequestWithSteering,
10780
- ...instructions ? { instructions } : {},
10781
- ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
10782
- tools: mergedTools
12232
+ let result;
12233
+ await collectFileUploadMetrics(async () => {
12234
+ try {
12235
+ result = await runToolLoop({
12236
+ ...toolLoopRequestWithSteering,
12237
+ ...instructions ? { instructions } : {},
12238
+ ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
12239
+ tools: mergedTools
12240
+ });
12241
+ } finally {
12242
+ uploadMetrics = getCurrentFileUploadMetrics();
12243
+ }
10783
12244
  });
12245
+ if (!result) {
12246
+ throw new Error("runToolLoop returned no result.");
12247
+ }
10784
12248
  streamEventLogger?.flush();
10785
12249
  emitTelemetry({
10786
12250
  type: "agent.run.completed",
@@ -10789,7 +12253,10 @@ async function runAgentLoopInternal(request, context) {
10789
12253
  stepCount: result.steps.length,
10790
12254
  toolCallCount: countToolCalls(result),
10791
12255
  totalCostUsd: result.totalCostUsd,
10792
- usage: summarizeResultUsage(result)
12256
+ usage: summarizeResultUsage(result),
12257
+ uploadCount: uploadMetrics.count,
12258
+ uploadBytes: uploadMetrics.totalBytes,
12259
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
10793
12260
  });
10794
12261
  loggingSession?.logLine(
10795
12262
  [
@@ -10798,7 +12265,10 @@ async function runAgentLoopInternal(request, context) {
10798
12265
  `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10799
12266
  `steps=${result.steps.length.toString()}`,
10800
12267
  `toolCalls=${countToolCalls(result).toString()}`,
10801
- `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
12268
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`,
12269
+ `uploadCount=${uploadMetrics.count.toString()}`,
12270
+ `uploadBytes=${uploadMetrics.totalBytes.toString()}`,
12271
+ `uploadLatencyMs=${uploadMetrics.totalLatencyMs.toString()}`
10802
12272
  ].join(" ")
10803
12273
  );
10804
12274
  for (const step of result.steps) {
@@ -10819,6 +12289,9 @@ async function runAgentLoopInternal(request, context) {
10819
12289
  type: "agent.run.completed",
10820
12290
  success: false,
10821
12291
  durationMs: Math.max(0, Date.now() - startedAtMs),
12292
+ uploadCount: uploadMetrics.count,
12293
+ uploadBytes: uploadMetrics.totalBytes,
12294
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
10822
12295
  error: toErrorMessage3(error)
10823
12296
  });
10824
12297
  loggingSession?.logLine(
@@ -10826,6 +12299,9 @@ async function runAgentLoopInternal(request, context) {
10826
12299
  `[agent:${runId}] run_completed`,
10827
12300
  `status=error`,
10828
12301
  `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
12302
+ `uploadCount=${uploadMetrics.count.toString()}`,
12303
+ `uploadBytes=${uploadMetrics.totalBytes.toString()}`,
12304
+ `uploadLatencyMs=${uploadMetrics.totalLatencyMs.toString()}`,
10829
12305
  `error=${toErrorMessage3(error)}`
10830
12306
  ].join(" ")
10831
12307
  );
@@ -10963,7 +12439,7 @@ function trimToUndefined2(value) {
10963
12439
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
10964
12440
  }
10965
12441
  function randomRunId() {
10966
- return (0, import_node_crypto3.randomBytes)(8).toString("hex");
12442
+ return (0, import_node_crypto4.randomBytes)(8).toString("hex");
10967
12443
  }
10968
12444
  function toIsoNow2() {
10969
12445
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -11032,7 +12508,7 @@ function resolveWorkspaceDirForLogging(request) {
11032
12508
  if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
11033
12509
  const cwd = explicitSelection.options?.cwd;
11034
12510
  if (typeof cwd === "string" && cwd.trim().length > 0) {
11035
- return import_node_path7.default.resolve(cwd);
12511
+ return import_node_path9.default.resolve(cwd);
11036
12512
  }
11037
12513
  }
11038
12514
  return process.cwd();
@@ -11042,7 +12518,7 @@ function createRootAgentLoggingSession(request) {
11042
12518
  if (!selected) {
11043
12519
  return void 0;
11044
12520
  }
11045
- const workspaceDir = typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? import_node_path7.default.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request);
12521
+ const workspaceDir = typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? import_node_path9.default.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request);
11046
12522
  return createAgentLoggingSession({
11047
12523
  ...selected,
11048
12524
  workspaceDir,
@@ -11120,7 +12596,7 @@ function createAgentTelemetryEmitter(params) {
11120
12596
  }
11121
12597
 
11122
12598
  // src/agent/candidateEvolution.ts
11123
- var import_node_crypto4 = require("crypto");
12599
+ var import_node_crypto5 = require("crypto");
11124
12600
  var DEFAULT_BATCH_SIZE = 1;
11125
12601
  var DEFAULT_GENERATION_CONCURRENCY = 8;
11126
12602
  var DEFAULT_ASSESSMENT_CONCURRENCY = 8;
@@ -11152,7 +12628,7 @@ function addStats(left, right) {
11152
12628
  };
11153
12629
  }
11154
12630
  function randomId(prefix) {
11155
- return `${prefix}_${(0, import_node_crypto4.randomBytes)(8).toString("hex")}`;
12631
+ return `${prefix}_${(0, import_node_crypto5.randomBytes)(8).toString("hex")}`;
11156
12632
  }
11157
12633
  function toFiniteNumber(value, fallback) {
11158
12634
  if (!Number.isFinite(value)) {
@@ -11777,6 +13253,7 @@ async function runCandidateEvolution(options) {
11777
13253
  CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
11778
13254
  CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
11779
13255
  CODEX_APPLY_PATCH_LARK_GRAMMAR,
13256
+ DEFAULT_FILE_TTL_SECONDS,
11780
13257
  FIREWORKS_DEFAULT_GLM_MODEL,
11781
13258
  FIREWORKS_DEFAULT_GPT_OSS_120B_MODEL,
11782
13259
  FIREWORKS_DEFAULT_KIMI_MODEL,
@@ -11817,10 +13294,12 @@ async function runCandidateEvolution(options) {
11817
13294
  createViewImageTool,
11818
13295
  createWriteFileTool,
11819
13296
  customTool,
13297
+ emptyFileUploadMetrics,
11820
13298
  encodeChatGptAuthJson,
11821
13299
  encodeChatGptAuthJsonB64,
11822
13300
  estimateCallCostUsd,
11823
13301
  exchangeChatGptOauthCode,
13302
+ files,
11824
13303
  generateImageInBatches,
11825
13304
  generateImages,
11826
13305
  generateJson,