@ljoukov/llm 4.0.11 → 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.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // src/llm.ts
2
- import { Buffer as Buffer4 } from "buffer";
3
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2
+ import { Buffer as Buffer5 } from "buffer";
3
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
4
4
  import { randomBytes } from "crypto";
5
+ import path5 from "path";
5
6
  import {
6
7
  FinishReason,
7
8
  FunctionCallingConfigMode,
@@ -2248,6 +2249,14 @@ function normaliseConfigValue(value) {
2248
2249
  const trimmed = value.trim();
2249
2250
  return trimmed.length > 0 ? trimmed : void 0;
2250
2251
  }
2252
+ function resolveGeminiApiKey() {
2253
+ loadLocalEnv();
2254
+ const raw = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
2255
+ return normaliseConfigValue(raw);
2256
+ }
2257
+ function getGeminiBackend() {
2258
+ return resolveGeminiApiKey() ? "api" : "vertex";
2259
+ }
2251
2260
  function configureGemini(options = {}) {
2252
2261
  const nextProjectId = normaliseConfigValue(options.projectId);
2253
2262
  const nextLocation = normaliseConfigValue(options.location);
@@ -2275,6 +2284,10 @@ function resolveLocation() {
2275
2284
  async function getGeminiClient() {
2276
2285
  if (!geminiClientState.clientPromise) {
2277
2286
  geminiClientState.clientPromise = Promise.resolve().then(() => {
2287
+ const apiKey = resolveGeminiApiKey();
2288
+ if (apiKey) {
2289
+ return new GoogleGenAI({ apiKey });
2290
+ }
2278
2291
  const projectId = resolveProjectId();
2279
2292
  const location = resolveLocation();
2280
2293
  const googleAuthOptions = getGoogleAuthOptions(CLOUD_PLATFORM_SCOPE);
@@ -3241,14 +3254,708 @@ function getCurrentAgentLoggingSession() {
3241
3254
  return loggingSessionStorage.getStore();
3242
3255
  }
3243
3256
 
3257
+ // src/files.ts
3258
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
3259
+ import { Buffer as Buffer4, File as NodeFile } from "buffer";
3260
+ import { createHash } from "crypto";
3261
+ import { createReadStream, createWriteStream, openAsBlob } from "fs";
3262
+ import { mkdir as mkdir2, mkdtemp, stat, unlink, writeFile as writeFile2 } from "fs/promises";
3263
+ import os3 from "os";
3264
+ import path4 from "path";
3265
+ import { Readable } from "stream";
3266
+ import { pipeline } from "stream/promises";
3267
+ import { Storage } from "@google-cloud/storage";
3268
+ import mime from "mime";
3269
+ var DEFAULT_FILE_TTL_SECONDS = 48 * 60 * 60;
3270
+ var OPENAI_FILE_CREATE_MAX_BYTES = 512 * 1024 * 1024;
3271
+ var OPENAI_UPLOAD_PART_MAX_BYTES = 64 * 1024 * 1024;
3272
+ var GEMINI_FILE_POLL_INTERVAL_MS = 1e3;
3273
+ var GEMINI_FILE_POLL_TIMEOUT_MS = 6e4;
3274
+ var FILES_TEMP_ROOT = path4.join(os3.tmpdir(), "ljoukov-llm-files");
3275
+ var filesState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.filesState"), () => ({
3276
+ metadataById: /* @__PURE__ */ new Map(),
3277
+ openAiUploadCacheByKey: /* @__PURE__ */ new Map(),
3278
+ materializedById: /* @__PURE__ */ new Map(),
3279
+ geminiMirrorById: /* @__PURE__ */ new Map(),
3280
+ vertexMirrorById: /* @__PURE__ */ new Map(),
3281
+ storageClient: void 0,
3282
+ geminiClientPromise: void 0
3283
+ }));
3284
+ var fileUploadScopeStorage = getRuntimeSingleton(
3285
+ /* @__PURE__ */ Symbol.for("@ljoukov/llm.fileUploadScopeStorage"),
3286
+ () => new AsyncLocalStorage2()
3287
+ );
3288
+ function summarizeUploadEvents(events) {
3289
+ let totalBytes = 0;
3290
+ let totalLatencyMs = 0;
3291
+ for (const event of events) {
3292
+ totalBytes += Math.max(0, event.bytes);
3293
+ totalLatencyMs += Math.max(0, event.durationMs);
3294
+ }
3295
+ return {
3296
+ count: events.length,
3297
+ totalBytes,
3298
+ totalLatencyMs,
3299
+ events: Array.from(events)
3300
+ };
3301
+ }
3302
+ function emptyFileUploadMetrics() {
3303
+ return summarizeUploadEvents([]);
3304
+ }
3305
+ function getCurrentFileUploadMetrics() {
3306
+ const collector = fileUploadScopeStorage.getStore()?.collectors.at(-1);
3307
+ return summarizeUploadEvents(collector?.events ?? []);
3308
+ }
3309
+ async function collectFileUploadMetrics(fn) {
3310
+ const parent = fileUploadScopeStorage.getStore();
3311
+ const collector = { events: [] };
3312
+ const scope = {
3313
+ collectors: [...parent?.collectors ?? [], collector],
3314
+ source: parent?.source
3315
+ };
3316
+ return await fileUploadScopeStorage.run(scope, async () => {
3317
+ const result = await fn();
3318
+ return {
3319
+ result,
3320
+ uploads: summarizeUploadEvents(collector.events)
3321
+ };
3322
+ });
3323
+ }
3324
+ async function runWithFileUploadSource(source, fn) {
3325
+ const parent = fileUploadScopeStorage.getStore();
3326
+ const scope = {
3327
+ collectors: parent?.collectors ?? [],
3328
+ source
3329
+ };
3330
+ return await fileUploadScopeStorage.run(scope, fn);
3331
+ }
3332
+ function formatUploadLogLine(event) {
3333
+ const parts = [
3334
+ "[upload]",
3335
+ `source=${event.source}`,
3336
+ `backend=${event.backend}`,
3337
+ `mode=${event.mode}`,
3338
+ `filename=${JSON.stringify(event.filename)}`,
3339
+ `bytes=${event.bytes.toString()}`,
3340
+ `durationMs=${event.durationMs.toString()}`
3341
+ ];
3342
+ if (event.mimeType) {
3343
+ parts.push(`mimeType=${event.mimeType}`);
3344
+ }
3345
+ if (event.fileId) {
3346
+ parts.push(`fileId=${event.fileId}`);
3347
+ }
3348
+ if (event.mirrorId) {
3349
+ parts.push(`mirrorId=${event.mirrorId}`);
3350
+ }
3351
+ if (event.fileUri) {
3352
+ parts.push(`fileUri=${JSON.stringify(event.fileUri)}`);
3353
+ }
3354
+ return parts.join(" ");
3355
+ }
3356
+ function recordUploadEvent(event) {
3357
+ const scope = fileUploadScopeStorage.getStore();
3358
+ const resolvedSource = event.source ?? scope?.source ?? (event.backend === "openai" ? "files_api" : "provider_mirror");
3359
+ const timestampedEvent = {
3360
+ ...event,
3361
+ source: resolvedSource,
3362
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3363
+ };
3364
+ for (const collector of scope?.collectors ?? []) {
3365
+ collector.events.push(timestampedEvent);
3366
+ }
3367
+ getCurrentAgentLoggingSession()?.logLine(formatUploadLogLine(timestampedEvent));
3368
+ }
3369
+ function normaliseFilename(filename, fallback = "attachment.bin") {
3370
+ const trimmed = filename?.trim();
3371
+ if (!trimmed) {
3372
+ return fallback;
3373
+ }
3374
+ const basename = path4.basename(trimmed);
3375
+ return basename.length > 0 ? basename : fallback;
3376
+ }
3377
+ function resolveMimeType(filename, explicitMimeType, fallback = "application/octet-stream") {
3378
+ const trimmed = explicitMimeType?.trim();
3379
+ if (trimmed) {
3380
+ return trimmed;
3381
+ }
3382
+ const inferred = mime.getType(filename);
3383
+ return typeof inferred === "string" && inferred.length > 0 ? inferred : fallback;
3384
+ }
3385
+ function toBuffer(data) {
3386
+ if (typeof data === "string") {
3387
+ return Buffer4.from(data, "utf8");
3388
+ }
3389
+ if (ArrayBuffer.isView(data)) {
3390
+ return Buffer4.from(data.buffer, data.byteOffset, data.byteLength);
3391
+ }
3392
+ return Buffer4.from(data);
3393
+ }
3394
+ function computeSha256Hex(buffer) {
3395
+ return createHash("sha256").update(buffer).digest("hex");
3396
+ }
3397
+ async function computeFileSha256Hex(filePath) {
3398
+ const hash = createHash("sha256");
3399
+ const stream = createReadStream(filePath);
3400
+ for await (const chunk of stream) {
3401
+ hash.update(chunk);
3402
+ }
3403
+ return hash.digest("hex");
3404
+ }
3405
+ function toStoredFile(file) {
3406
+ return {
3407
+ id: file.id,
3408
+ bytes: file.bytes,
3409
+ created_at: file.created_at,
3410
+ filename: file.filename,
3411
+ object: "file",
3412
+ purpose: file.purpose,
3413
+ status: file.status,
3414
+ expires_at: file.expires_at
3415
+ };
3416
+ }
3417
+ function buildCacheKey(filename, mimeType, sha256Hex) {
3418
+ return `${sha256Hex}\0${filename}\0${mimeType}`;
3419
+ }
3420
+ function isFresh(file) {
3421
+ if (!file.expires_at) {
3422
+ return true;
3423
+ }
3424
+ return file.expires_at * 1e3 > Date.now() + 3e4;
3425
+ }
3426
+ function recordMetadata(metadata) {
3427
+ filesState.metadataById.set(metadata.file.id, metadata);
3428
+ if (metadata.sha256Hex) {
3429
+ filesState.openAiUploadCacheByKey.set(
3430
+ buildCacheKey(
3431
+ metadata.filename,
3432
+ metadata.mimeType ?? "application/octet-stream",
3433
+ metadata.sha256Hex
3434
+ ),
3435
+ metadata
3436
+ );
3437
+ }
3438
+ return metadata;
3439
+ }
3440
+ async function uploadOpenAiFileFromBytes(params) {
3441
+ const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3442
+ const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
3443
+ if (cached && isFresh(cached.file)) {
3444
+ return cached;
3445
+ }
3446
+ const client = getOpenAiClient();
3447
+ const startedAtMs = Date.now();
3448
+ let uploaded;
3449
+ let mode;
3450
+ if (params.bytes.byteLength <= OPENAI_FILE_CREATE_MAX_BYTES) {
3451
+ mode = "files.create";
3452
+ uploaded = await client.files.create({
3453
+ file: new NodeFile([new Uint8Array(params.bytes)], params.filename, {
3454
+ type: params.mimeType
3455
+ }),
3456
+ purpose: params.purpose,
3457
+ expires_after: {
3458
+ anchor: "created_at",
3459
+ seconds: params.expiresAfterSeconds
3460
+ }
3461
+ });
3462
+ } else {
3463
+ mode = "uploads";
3464
+ const upload = await client.uploads.create({
3465
+ bytes: params.bytes.byteLength,
3466
+ filename: params.filename,
3467
+ mime_type: params.mimeType,
3468
+ purpose: params.purpose
3469
+ });
3470
+ const partIds = [];
3471
+ for (let offset = 0; offset < params.bytes.byteLength; offset += OPENAI_UPLOAD_PART_MAX_BYTES) {
3472
+ const chunk = params.bytes.subarray(
3473
+ offset,
3474
+ Math.min(offset + OPENAI_UPLOAD_PART_MAX_BYTES, params.bytes.byteLength)
3475
+ );
3476
+ const uploadPart = await client.uploads.parts.create(upload.id, {
3477
+ data: new NodeFile([new Uint8Array(chunk)], `${params.sha256Hex}.part`, {
3478
+ type: params.mimeType
3479
+ })
3480
+ });
3481
+ partIds.push(uploadPart.id);
3482
+ }
3483
+ const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
3484
+ const fileId = completed.file?.id;
3485
+ if (!fileId) {
3486
+ throw new Error("OpenAI upload completed without a file id.");
3487
+ }
3488
+ uploaded = await client.files.retrieve(fileId);
3489
+ }
3490
+ const file = toStoredFile(uploaded);
3491
+ const metadata = recordMetadata({
3492
+ file,
3493
+ filename: file.filename,
3494
+ bytes: file.bytes,
3495
+ mimeType: params.mimeType,
3496
+ sha256Hex: params.sha256Hex
3497
+ });
3498
+ recordUploadEvent({
3499
+ backend: "openai",
3500
+ mode,
3501
+ filename: metadata.filename,
3502
+ bytes: metadata.bytes,
3503
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3504
+ mimeType: params.mimeType,
3505
+ fileId: metadata.file.id
3506
+ });
3507
+ return metadata;
3508
+ }
3509
+ async function uploadOpenAiFileFromPath(params) {
3510
+ const cacheKey = buildCacheKey(params.filename, params.mimeType, params.sha256Hex);
3511
+ const cached = filesState.openAiUploadCacheByKey.get(cacheKey);
3512
+ if (cached && isFresh(cached.file)) {
3513
+ return cached;
3514
+ }
3515
+ const client = getOpenAiClient();
3516
+ const startedAtMs = Date.now();
3517
+ let uploaded;
3518
+ let mode;
3519
+ if (params.bytes <= OPENAI_FILE_CREATE_MAX_BYTES) {
3520
+ mode = "files.create";
3521
+ const blob = await openAsBlob(params.filePath, { type: params.mimeType });
3522
+ uploaded = await client.files.create({
3523
+ file: new NodeFile([blob], params.filename, { type: params.mimeType }),
3524
+ purpose: params.purpose,
3525
+ expires_after: {
3526
+ anchor: "created_at",
3527
+ seconds: params.expiresAfterSeconds
3528
+ }
3529
+ });
3530
+ } else {
3531
+ mode = "uploads";
3532
+ const upload = await client.uploads.create({
3533
+ bytes: params.bytes,
3534
+ filename: params.filename,
3535
+ mime_type: params.mimeType,
3536
+ purpose: params.purpose
3537
+ });
3538
+ const partIds = [];
3539
+ const stream = createReadStream(params.filePath, {
3540
+ highWaterMark: OPENAI_UPLOAD_PART_MAX_BYTES
3541
+ });
3542
+ let partIndex = 0;
3543
+ for await (const chunk of stream) {
3544
+ const buffer = Buffer4.isBuffer(chunk) ? chunk : Buffer4.from(chunk);
3545
+ const uploadPart = await client.uploads.parts.create(upload.id, {
3546
+ data: new NodeFile(
3547
+ [new Uint8Array(buffer)],
3548
+ `${params.sha256Hex}.${partIndex.toString()}.part`,
3549
+ {
3550
+ type: params.mimeType
3551
+ }
3552
+ )
3553
+ });
3554
+ partIds.push(uploadPart.id);
3555
+ partIndex += 1;
3556
+ }
3557
+ const completed = await client.uploads.complete(upload.id, { part_ids: partIds });
3558
+ const fileId = completed.file?.id;
3559
+ if (!fileId) {
3560
+ throw new Error("OpenAI upload completed without a file id.");
3561
+ }
3562
+ uploaded = await client.files.retrieve(fileId);
3563
+ }
3564
+ const file = toStoredFile(uploaded);
3565
+ const metadata = recordMetadata({
3566
+ file,
3567
+ filename: file.filename,
3568
+ bytes: file.bytes,
3569
+ mimeType: params.mimeType,
3570
+ sha256Hex: params.sha256Hex
3571
+ });
3572
+ recordUploadEvent({
3573
+ backend: "openai",
3574
+ mode,
3575
+ filename: metadata.filename,
3576
+ bytes: metadata.bytes,
3577
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3578
+ mimeType: params.mimeType,
3579
+ fileId: metadata.file.id
3580
+ });
3581
+ return metadata;
3582
+ }
3583
+ async function retrieveOpenAiFile(fileId) {
3584
+ const cached = filesState.metadataById.get(fileId);
3585
+ if (cached && isFresh(cached.file)) {
3586
+ return cached;
3587
+ }
3588
+ const client = getOpenAiClient();
3589
+ const retrieved = await client.files.retrieve(fileId);
3590
+ const file = toStoredFile(retrieved);
3591
+ return recordMetadata({
3592
+ file,
3593
+ filename: file.filename,
3594
+ bytes: file.bytes,
3595
+ mimeType: cached?.mimeType ?? resolveMimeType(file.filename, void 0),
3596
+ sha256Hex: cached?.sha256Hex,
3597
+ localPath: cached?.localPath
3598
+ });
3599
+ }
3600
+ function buildGeminiMirrorName(sha256Hex) {
3601
+ return `files/${sha256Hex.slice(0, 40)}`;
3602
+ }
3603
+ async function waitForGeminiFileActive(client, name) {
3604
+ const startedAt = Date.now();
3605
+ while (true) {
3606
+ const file = await client.files.get({ name });
3607
+ if (!file.state || file.state === "ACTIVE") {
3608
+ return;
3609
+ }
3610
+ if (file.state === "FAILED") {
3611
+ throw new Error(file.error?.message ?? `Gemini file ${name} failed processing.`);
3612
+ }
3613
+ if (Date.now() - startedAt >= GEMINI_FILE_POLL_TIMEOUT_MS) {
3614
+ throw new Error(`Timed out waiting for Gemini file ${name} to become active.`);
3615
+ }
3616
+ await new Promise((resolve) => setTimeout(resolve, GEMINI_FILE_POLL_INTERVAL_MS));
3617
+ }
3618
+ }
3619
+ function resolveVertexMirrorBucket() {
3620
+ const raw = process.env.VERTEX_GCS_BUCKET ?? process.env.LLM_VERTEX_GCS_BUCKET;
3621
+ const trimmed = raw?.trim();
3622
+ if (!trimmed) {
3623
+ throw new Error(
3624
+ "VERTEX_GCS_BUCKET must be set to use OpenAI-backed file ids with Vertex Gemini models."
3625
+ );
3626
+ }
3627
+ return trimmed.replace(/^gs:\/\//u, "").replace(/\/+$/u, "");
3628
+ }
3629
+ function resolveVertexMirrorPrefix() {
3630
+ const raw = process.env.VERTEX_GCS_PREFIX ?? process.env.LLM_VERTEX_GCS_PREFIX;
3631
+ const trimmed = raw?.trim().replace(/^\/+/u, "").replace(/\/+$/u, "");
3632
+ return trimmed ? `${trimmed}/` : "";
3633
+ }
3634
+ function getStorageClient() {
3635
+ if (filesState.storageClient) {
3636
+ return filesState.storageClient;
3637
+ }
3638
+ const serviceAccount = getGoogleServiceAccount();
3639
+ filesState.storageClient = new Storage({
3640
+ projectId: serviceAccount.projectId,
3641
+ credentials: {
3642
+ client_email: serviceAccount.clientEmail,
3643
+ private_key: serviceAccount.privateKey
3644
+ }
3645
+ });
3646
+ return filesState.storageClient;
3647
+ }
3648
+ function getGeminiMirrorClient() {
3649
+ if (!filesState.geminiClientPromise) {
3650
+ filesState.geminiClientPromise = getGeminiClient();
3651
+ }
3652
+ return filesState.geminiClientPromise;
3653
+ }
3654
+ async function materializeOpenAiFile(fileId) {
3655
+ const cachedPromise = filesState.materializedById.get(fileId);
3656
+ if (cachedPromise) {
3657
+ return await cachedPromise;
3658
+ }
3659
+ const promise = (async () => {
3660
+ const metadata = await retrieveOpenAiFile(fileId);
3661
+ if (metadata.localPath && metadata.sha256Hex && metadata.mimeType) {
3662
+ return {
3663
+ file: metadata.file,
3664
+ filename: metadata.filename,
3665
+ bytes: metadata.bytes,
3666
+ mimeType: metadata.mimeType,
3667
+ sha256Hex: metadata.sha256Hex,
3668
+ localPath: metadata.localPath
3669
+ };
3670
+ }
3671
+ await mkdir2(FILES_TEMP_ROOT, { recursive: true });
3672
+ const tempDir = await mkdtemp(
3673
+ path4.join(FILES_TEMP_ROOT, `${fileId.replace(/[^a-z0-9_-]/giu, "")}-`)
3674
+ );
3675
+ const localPath = path4.join(tempDir, normaliseFilename(metadata.filename, `${fileId}.bin`));
3676
+ const response = await getOpenAiClient().files.content(fileId);
3677
+ if (!response.ok) {
3678
+ throw new Error(
3679
+ `Failed to download OpenAI file ${fileId}: ${response.status} ${response.statusText}`
3680
+ );
3681
+ }
3682
+ const responseMimeType = response.headers.get("content-type")?.trim() || void 0;
3683
+ const mimeType = resolveMimeType(metadata.filename, responseMimeType);
3684
+ const hash = createHash("sha256");
3685
+ let bytes = 0;
3686
+ if (response.body) {
3687
+ const source = Readable.fromWeb(response.body);
3688
+ const writable = createWriteStream(localPath, { flags: "wx" });
3689
+ source.on("data", (chunk) => {
3690
+ const buffer = Buffer4.isBuffer(chunk) ? chunk : Buffer4.from(chunk);
3691
+ hash.update(buffer);
3692
+ bytes += buffer.byteLength;
3693
+ });
3694
+ await pipeline(source, writable);
3695
+ } else {
3696
+ const buffer = Buffer4.from(await response.arrayBuffer());
3697
+ hash.update(buffer);
3698
+ bytes = buffer.byteLength;
3699
+ await writeFile2(localPath, buffer);
3700
+ }
3701
+ const sha256Hex = hash.digest("hex");
3702
+ const updated = recordMetadata({
3703
+ file: metadata.file,
3704
+ filename: metadata.filename,
3705
+ bytes: bytes || metadata.bytes,
3706
+ mimeType,
3707
+ sha256Hex,
3708
+ localPath
3709
+ });
3710
+ return {
3711
+ file: updated.file,
3712
+ filename: updated.filename,
3713
+ bytes: updated.bytes,
3714
+ mimeType: updated.mimeType ?? mimeType,
3715
+ sha256Hex,
3716
+ localPath
3717
+ };
3718
+ })();
3719
+ filesState.materializedById.set(fileId, promise);
3720
+ try {
3721
+ return await promise;
3722
+ } catch (error) {
3723
+ filesState.materializedById.delete(fileId);
3724
+ throw error;
3725
+ }
3726
+ }
3727
+ async function ensureGeminiFileMirror(fileId) {
3728
+ const cached = filesState.geminiMirrorById.get(fileId);
3729
+ if (cached) {
3730
+ return cached;
3731
+ }
3732
+ const materialized = await materializeOpenAiFile(fileId);
3733
+ const client = await getGeminiMirrorClient();
3734
+ const name = buildGeminiMirrorName(materialized.sha256Hex);
3735
+ try {
3736
+ const existing = await client.files.get({ name });
3737
+ if (existing.name && existing.uri && existing.mimeType) {
3738
+ const mirror2 = {
3739
+ openAiFileId: fileId,
3740
+ name: existing.name,
3741
+ uri: existing.uri,
3742
+ mimeType: existing.mimeType,
3743
+ displayName: existing.displayName ?? materialized.filename
3744
+ };
3745
+ filesState.geminiMirrorById.set(fileId, mirror2);
3746
+ return mirror2;
3747
+ }
3748
+ } catch {
3749
+ }
3750
+ const startedAtMs = Date.now();
3751
+ const uploaded = await client.files.upload({
3752
+ file: materialized.localPath,
3753
+ config: {
3754
+ name,
3755
+ mimeType: materialized.mimeType,
3756
+ displayName: materialized.filename
3757
+ }
3758
+ });
3759
+ if (uploaded.name && uploaded.state && uploaded.state !== "ACTIVE") {
3760
+ await waitForGeminiFileActive(client, uploaded.name);
3761
+ }
3762
+ const resolved = await client.files.get({ name: uploaded.name ?? name });
3763
+ if (!resolved.name || !resolved.uri || !resolved.mimeType) {
3764
+ throw new Error("Gemini file upload completed without a usable URI.");
3765
+ }
3766
+ const mirror = {
3767
+ openAiFileId: fileId,
3768
+ name: resolved.name,
3769
+ uri: resolved.uri,
3770
+ mimeType: resolved.mimeType,
3771
+ displayName: resolved.displayName ?? materialized.filename
3772
+ };
3773
+ filesState.geminiMirrorById.set(fileId, mirror);
3774
+ recordUploadEvent({
3775
+ backend: "gemini",
3776
+ mode: "mirror",
3777
+ filename: materialized.filename,
3778
+ bytes: materialized.bytes,
3779
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3780
+ mimeType: materialized.mimeType,
3781
+ fileId,
3782
+ mirrorId: mirror.name,
3783
+ fileUri: mirror.uri
3784
+ });
3785
+ return mirror;
3786
+ }
3787
+ async function ensureVertexFileMirror(fileId) {
3788
+ const cached = filesState.vertexMirrorById.get(fileId);
3789
+ if (cached) {
3790
+ return cached;
3791
+ }
3792
+ const materialized = await materializeOpenAiFile(fileId);
3793
+ const bucketName = resolveVertexMirrorBucket();
3794
+ const prefix = resolveVertexMirrorPrefix();
3795
+ const extension = mime.getExtension(materialized.mimeType) ?? path4.extname(materialized.filename).replace(/^\./u, "") ?? "bin";
3796
+ const objectName = `${prefix}${materialized.sha256Hex}.${extension}`;
3797
+ const file = getStorageClient().bucket(bucketName).file(objectName);
3798
+ let uploaded = false;
3799
+ const startedAtMs = Date.now();
3800
+ try {
3801
+ await file.getMetadata();
3802
+ } catch (error) {
3803
+ const code = error.code;
3804
+ if (code !== 404 && code !== "404") {
3805
+ throw error;
3806
+ }
3807
+ try {
3808
+ await pipeline(
3809
+ createReadStream(materialized.localPath),
3810
+ file.createWriteStream({
3811
+ resumable: materialized.bytes >= 10 * 1024 * 1024,
3812
+ preconditionOpts: { ifGenerationMatch: 0 },
3813
+ metadata: {
3814
+ contentType: materialized.mimeType,
3815
+ customTime: (/* @__PURE__ */ new Date()).toISOString(),
3816
+ metadata: {
3817
+ filename: materialized.filename,
3818
+ sha256: materialized.sha256Hex,
3819
+ expiresAt: new Date(Date.now() + DEFAULT_FILE_TTL_SECONDS * 1e3).toISOString()
3820
+ }
3821
+ }
3822
+ })
3823
+ );
3824
+ uploaded = true;
3825
+ } catch (uploadError) {
3826
+ const uploadCode = uploadError.code;
3827
+ if (uploadCode !== 412 && uploadCode !== "412") {
3828
+ throw uploadError;
3829
+ }
3830
+ }
3831
+ }
3832
+ const mirror = {
3833
+ openAiFileId: fileId,
3834
+ bucket: bucketName,
3835
+ objectName,
3836
+ fileUri: `gs://${bucketName}/${objectName}`,
3837
+ mimeType: materialized.mimeType
3838
+ };
3839
+ filesState.vertexMirrorById.set(fileId, mirror);
3840
+ if (uploaded) {
3841
+ recordUploadEvent({
3842
+ backend: "vertex",
3843
+ mode: "mirror",
3844
+ filename: materialized.filename,
3845
+ bytes: materialized.bytes,
3846
+ durationMs: Math.max(0, Date.now() - startedAtMs),
3847
+ mimeType: materialized.mimeType,
3848
+ fileId,
3849
+ mirrorId: mirror.objectName,
3850
+ fileUri: mirror.fileUri
3851
+ });
3852
+ }
3853
+ return mirror;
3854
+ }
3855
+ async function filesCreate(params) {
3856
+ const purpose = params.purpose ?? "user_data";
3857
+ const expiresAfterSeconds = params.expiresAfterSeconds ?? DEFAULT_FILE_TTL_SECONDS;
3858
+ if ("path" in params) {
3859
+ const filePath = path4.resolve(params.path);
3860
+ const info = await stat(filePath);
3861
+ const filename2 = normaliseFilename(params.filename, path4.basename(filePath));
3862
+ const mimeType2 = resolveMimeType(filename2, params.mimeType);
3863
+ const sha256Hex2 = await computeFileSha256Hex(filePath);
3864
+ const uploaded2 = await uploadOpenAiFileFromPath({
3865
+ filePath,
3866
+ filename: filename2,
3867
+ mimeType: mimeType2,
3868
+ purpose,
3869
+ expiresAfterSeconds,
3870
+ sha256Hex: sha256Hex2,
3871
+ bytes: info.size
3872
+ });
3873
+ return uploaded2.file;
3874
+ }
3875
+ const filename = normaliseFilename(params.filename);
3876
+ const bytes = toBuffer(params.data);
3877
+ const mimeType = resolveMimeType(filename, params.mimeType, "text/plain");
3878
+ const sha256Hex = computeSha256Hex(bytes);
3879
+ const uploaded = await uploadOpenAiFileFromBytes({
3880
+ bytes,
3881
+ filename,
3882
+ mimeType,
3883
+ purpose,
3884
+ expiresAfterSeconds,
3885
+ sha256Hex
3886
+ });
3887
+ return uploaded.file;
3888
+ }
3889
+ async function filesRetrieve(fileId) {
3890
+ return (await retrieveOpenAiFile(fileId)).file;
3891
+ }
3892
+ async function filesDelete(fileId) {
3893
+ const cachedGemini = filesState.geminiMirrorById.get(fileId);
3894
+ if (cachedGemini) {
3895
+ try {
3896
+ const client = await getGeminiMirrorClient();
3897
+ await client.files.delete({ name: cachedGemini.name });
3898
+ } catch {
3899
+ }
3900
+ filesState.geminiMirrorById.delete(fileId);
3901
+ }
3902
+ const cachedVertex = filesState.vertexMirrorById.get(fileId);
3903
+ if (cachedVertex) {
3904
+ try {
3905
+ await getStorageClient().bucket(cachedVertex.bucket).file(cachedVertex.objectName).delete({ ignoreNotFound: true });
3906
+ } catch {
3907
+ }
3908
+ filesState.vertexMirrorById.delete(fileId);
3909
+ }
3910
+ const cachedMaterialized = filesState.metadataById.get(fileId)?.localPath;
3911
+ if (cachedMaterialized) {
3912
+ try {
3913
+ await unlink(cachedMaterialized);
3914
+ } catch {
3915
+ }
3916
+ }
3917
+ const response = await getOpenAiClient().files.delete(fileId);
3918
+ filesState.metadataById.delete(fileId);
3919
+ filesState.materializedById.delete(fileId);
3920
+ return {
3921
+ id: response.id,
3922
+ deleted: response.deleted,
3923
+ object: "file"
3924
+ };
3925
+ }
3926
+ async function filesContent(fileId) {
3927
+ return await getOpenAiClient().files.content(fileId);
3928
+ }
3929
+ async function getCanonicalFileMetadata(fileId) {
3930
+ const metadata = await retrieveOpenAiFile(fileId);
3931
+ const mimeType = metadata.mimeType ?? resolveMimeType(metadata.filename, void 0);
3932
+ const updated = metadata.mimeType === mimeType ? metadata : recordMetadata({
3933
+ ...metadata,
3934
+ mimeType
3935
+ });
3936
+ return {
3937
+ ...updated,
3938
+ mimeType
3939
+ };
3940
+ }
3941
+ var files = {
3942
+ create: filesCreate,
3943
+ retrieve: filesRetrieve,
3944
+ delete: filesDelete,
3945
+ content: filesContent
3946
+ };
3947
+
3244
3948
  // src/llm.ts
3245
3949
  var toolCallContextStorage = getRuntimeSingleton(
3246
3950
  /* @__PURE__ */ Symbol.for("@ljoukov/llm.toolCallContextStorage"),
3247
- () => new AsyncLocalStorage2()
3951
+ () => new AsyncLocalStorage3()
3248
3952
  );
3249
3953
  function getCurrentToolCallContext() {
3250
3954
  return toolCallContextStorage.getStore() ?? null;
3251
3955
  }
3956
+ var INLINE_ATTACHMENT_FILENAME_SYMBOL = /* @__PURE__ */ Symbol.for("@ljoukov/llm.inlineAttachmentFilename");
3957
+ var INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES = 20 * 1024 * 1024;
3958
+ var TOOL_OUTPUT_SPILL_THRESHOLD_BYTES = 1 * 1024 * 1024;
3252
3959
  var LLM_TEXT_MODEL_IDS = [
3253
3960
  ...OPENAI_MODEL_IDS,
3254
3961
  ...CHATGPT_MODEL_IDS,
@@ -3526,6 +4233,52 @@ function isJsonSchemaObject(schema) {
3526
4233
  }
3527
4234
  return false;
3528
4235
  }
4236
+ var CANONICAL_GEMINI_FILE_URI_PREFIX = "openai://file/";
4237
+ function buildCanonicalGeminiFileUri(fileId) {
4238
+ return `${CANONICAL_GEMINI_FILE_URI_PREFIX}${fileId}`;
4239
+ }
4240
+ function parseCanonicalGeminiFileId(fileUri) {
4241
+ if (!fileUri?.startsWith(CANONICAL_GEMINI_FILE_URI_PREFIX)) {
4242
+ return void 0;
4243
+ }
4244
+ const fileId = fileUri.slice(CANONICAL_GEMINI_FILE_URI_PREFIX.length).trim();
4245
+ return fileId.length > 0 ? fileId : void 0;
4246
+ }
4247
+ function cloneContentPart(part) {
4248
+ switch (part.type) {
4249
+ case "text":
4250
+ return {
4251
+ type: "text",
4252
+ text: part.text,
4253
+ thought: part.thought === true ? true : void 0
4254
+ };
4255
+ case "inlineData":
4256
+ return {
4257
+ type: "inlineData",
4258
+ data: part.data,
4259
+ mimeType: part.mimeType,
4260
+ filename: part.filename
4261
+ };
4262
+ case "input_image":
4263
+ return {
4264
+ type: "input_image",
4265
+ image_url: part.image_url ?? void 0,
4266
+ file_id: part.file_id ?? void 0,
4267
+ detail: part.detail,
4268
+ filename: part.filename ?? void 0
4269
+ };
4270
+ case "input_file":
4271
+ return {
4272
+ type: "input_file",
4273
+ file_data: part.file_data ?? void 0,
4274
+ file_id: part.file_id ?? void 0,
4275
+ file_url: part.file_url ?? void 0,
4276
+ filename: part.filename ?? void 0
4277
+ };
4278
+ default:
4279
+ return part;
4280
+ }
4281
+ }
3529
4282
  function sanitisePartForLogging(part) {
3530
4283
  switch (part.type) {
3531
4284
  case "text":
@@ -3537,16 +4290,33 @@ function sanitisePartForLogging(part) {
3537
4290
  case "inlineData": {
3538
4291
  let omittedBytes;
3539
4292
  try {
3540
- omittedBytes = Buffer4.from(part.data, "base64").byteLength;
4293
+ omittedBytes = Buffer5.from(part.data, "base64").byteLength;
3541
4294
  } catch {
3542
- omittedBytes = Buffer4.byteLength(part.data, "utf8");
4295
+ omittedBytes = Buffer5.byteLength(part.data, "utf8");
3543
4296
  }
3544
4297
  return {
3545
4298
  type: "inlineData",
3546
4299
  mimeType: part.mimeType,
4300
+ filename: part.filename,
3547
4301
  data: `[omitted:${omittedBytes}b]`
3548
4302
  };
3549
4303
  }
4304
+ case "input_image":
4305
+ return {
4306
+ type: "input_image",
4307
+ file_id: part.file_id ?? void 0,
4308
+ filename: part.filename ?? void 0,
4309
+ detail: part.detail ?? void 0,
4310
+ image_url: typeof part.image_url === "string" ? part.image_url.startsWith("data:") ? "[omitted:data-url]" : part.image_url : void 0
4311
+ };
4312
+ case "input_file":
4313
+ return {
4314
+ type: "input_file",
4315
+ file_id: part.file_id ?? void 0,
4316
+ filename: part.filename ?? void 0,
4317
+ file_url: typeof part.file_url === "string" ? part.file_url.startsWith("data:") ? "[omitted:data-url]" : part.file_url : void 0,
4318
+ file_data: typeof part.file_data === "string" ? `[omitted:${Buffer5.byteLength(part.file_data, "utf8")}b]` : void 0
4319
+ };
3550
4320
  default:
3551
4321
  return "[unknown part]";
3552
4322
  }
@@ -3567,12 +4337,17 @@ function convertGooglePartsToLlmParts(parts) {
3567
4337
  result.push({
3568
4338
  type: "inlineData",
3569
4339
  data: inline.data,
3570
- mimeType: inline.mimeType
4340
+ mimeType: inline.mimeType,
4341
+ filename: inline.displayName
3571
4342
  });
3572
4343
  continue;
3573
4344
  }
3574
4345
  if (part.fileData?.fileUri) {
3575
- throw new Error("fileData parts are not supported");
4346
+ result.push({
4347
+ type: "input_file",
4348
+ file_url: part.fileData.fileUri,
4349
+ filename: part.fileData.displayName
4350
+ });
3576
4351
  }
3577
4352
  }
3578
4353
  return result;
@@ -3604,13 +4379,86 @@ function toGeminiPart(part) {
3604
4379
  text: part.text,
3605
4380
  thought: part.thought === true ? true : void 0
3606
4381
  };
3607
- case "inlineData":
4382
+ case "inlineData": {
4383
+ const inlineData = {
4384
+ data: part.data,
4385
+ mimeType: part.mimeType
4386
+ };
4387
+ setInlineAttachmentFilename(inlineData, part.filename);
3608
4388
  return {
3609
4389
  inlineData: {
3610
- data: part.data,
3611
- mimeType: part.mimeType
4390
+ ...inlineData
4391
+ }
4392
+ };
4393
+ }
4394
+ case "input_image": {
4395
+ if (part.file_id) {
4396
+ return {
4397
+ fileData: {
4398
+ fileUri: buildCanonicalGeminiFileUri(part.file_id),
4399
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4400
+ displayName: part.filename ?? void 0
4401
+ }
4402
+ };
4403
+ }
4404
+ if (typeof part.image_url !== "string" || part.image_url.trim().length === 0) {
4405
+ throw new Error("input_image requires image_url or file_id.");
4406
+ }
4407
+ const parsed = parseDataUrlPayload(part.image_url);
4408
+ if (parsed) {
4409
+ const geminiPart = createPartFromBase64(parsed.dataBase64, parsed.mimeType);
4410
+ if (part.filename && geminiPart.inlineData) {
4411
+ geminiPart.inlineData.displayName = part.filename;
4412
+ }
4413
+ return geminiPart;
4414
+ }
4415
+ return {
4416
+ fileData: {
4417
+ fileUri: part.image_url,
4418
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4419
+ displayName: part.filename ?? void 0
3612
4420
  }
3613
4421
  };
4422
+ }
4423
+ case "input_file": {
4424
+ if (part.file_id) {
4425
+ return {
4426
+ fileData: {
4427
+ fileUri: buildCanonicalGeminiFileUri(part.file_id),
4428
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4429
+ displayName: part.filename ?? void 0
4430
+ }
4431
+ };
4432
+ }
4433
+ if (typeof part.file_data === "string" && part.file_data.trim().length > 0) {
4434
+ const geminiPart = createPartFromBase64(
4435
+ part.file_data,
4436
+ inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream"
4437
+ );
4438
+ if (part.filename && geminiPart.inlineData) {
4439
+ geminiPart.inlineData.displayName = part.filename;
4440
+ }
4441
+ return geminiPart;
4442
+ }
4443
+ if (typeof part.file_url === "string" && part.file_url.trim().length > 0) {
4444
+ const parsed = parseDataUrlPayload(part.file_url);
4445
+ if (parsed) {
4446
+ const geminiPart = createPartFromBase64(parsed.dataBase64, parsed.mimeType);
4447
+ if (part.filename && geminiPart.inlineData) {
4448
+ geminiPart.inlineData.displayName = part.filename;
4449
+ }
4450
+ return geminiPart;
4451
+ }
4452
+ return {
4453
+ fileData: {
4454
+ fileUri: part.file_url,
4455
+ mimeType: inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream",
4456
+ displayName: part.filename ?? void 0
4457
+ }
4458
+ };
4459
+ }
4460
+ throw new Error("input_file requires file_id, file_data, or file_url.");
4461
+ }
3614
4462
  default:
3615
4463
  throw new Error("Unsupported LLM content part");
3616
4464
  }
@@ -3707,6 +4555,14 @@ function isInlineImageMime(mimeType) {
3707
4555
  }
3708
4556
  function guessInlineDataFilename(mimeType) {
3709
4557
  switch (mimeType) {
4558
+ case "image/jpeg":
4559
+ return "image.jpg";
4560
+ case "image/png":
4561
+ return "image.png";
4562
+ case "image/webp":
4563
+ return "image.webp";
4564
+ case "image/gif":
4565
+ return "image.gif";
3710
4566
  case "application/pdf":
3711
4567
  return "document.pdf";
3712
4568
  case "application/json":
@@ -3719,14 +4575,269 @@ function guessInlineDataFilename(mimeType) {
3719
4575
  return "attachment.bin";
3720
4576
  }
3721
4577
  }
3722
- function mergeConsecutiveTextParts(parts) {
4578
+ function normaliseAttachmentFilename(value, fallback) {
4579
+ const trimmed = value?.trim();
4580
+ if (!trimmed) {
4581
+ return fallback;
4582
+ }
4583
+ const basename = path5.basename(trimmed).replace(/[^\w.-]+/g, "-");
4584
+ return basename.length > 0 ? basename : fallback;
4585
+ }
4586
+ function setInlineAttachmentFilename(target, filename) {
4587
+ const normalized = filename?.trim();
4588
+ if (!normalized) {
4589
+ return;
4590
+ }
4591
+ target[INLINE_ATTACHMENT_FILENAME_SYMBOL] = normalized;
4592
+ }
4593
+ function getInlineAttachmentFilename(target) {
4594
+ if (!target || typeof target !== "object") {
4595
+ return void 0;
4596
+ }
4597
+ const value = target[INLINE_ATTACHMENT_FILENAME_SYMBOL];
4598
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
4599
+ }
4600
+ function estimateInlinePayloadBytes(value) {
4601
+ return Buffer5.byteLength(value, "utf8");
4602
+ }
4603
+ function isOpenAiNativeContentItem(value) {
4604
+ return !!value && typeof value === "object" && typeof value.type === "string";
4605
+ }
4606
+ function estimateOpenAiInlinePromptBytes(input) {
4607
+ let total = 0;
4608
+ const visitItems = (items) => {
4609
+ for (const item of items) {
4610
+ if (!item || typeof item !== "object") {
4611
+ continue;
4612
+ }
4613
+ if (Array.isArray(item.content)) {
4614
+ visitItems(item.content);
4615
+ }
4616
+ if (Array.isArray(item.output)) {
4617
+ visitItems(item.output);
4618
+ }
4619
+ if (!isOpenAiNativeContentItem(item)) {
4620
+ continue;
4621
+ }
4622
+ if (item.type === "input_image" && typeof item.image_url === "string" && item.image_url.trim().toLowerCase().startsWith("data:")) {
4623
+ total += estimateInlinePayloadBytes(item.image_url);
4624
+ }
4625
+ if (item.type === "input_file" && typeof item.file_data === "string" && item.file_data.trim().length > 0) {
4626
+ total += estimateInlinePayloadBytes(item.file_data);
4627
+ }
4628
+ if (item.type === "input_file" && typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4629
+ total += estimateInlinePayloadBytes(item.file_url);
4630
+ }
4631
+ }
4632
+ };
4633
+ visitItems(input);
4634
+ return total;
4635
+ }
4636
+ async function storeCanonicalPromptFile(options) {
4637
+ const file = await runWithFileUploadSource("prompt_inline_offload", async () => {
4638
+ return await filesCreate({
4639
+ data: options.bytes,
4640
+ filename: options.filename,
4641
+ mimeType: options.mimeType,
4642
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
4643
+ });
4644
+ });
4645
+ return {
4646
+ fileId: file.id,
4647
+ filename: file.filename,
4648
+ mimeType: options.mimeType
4649
+ };
4650
+ }
4651
+ async function prepareOpenAiPromptContentItem(item) {
4652
+ if (!isOpenAiNativeContentItem(item)) {
4653
+ return item;
4654
+ }
4655
+ if (item.type === "input_image" && typeof item.image_url === "string" && item.image_url.trim().toLowerCase().startsWith("data:")) {
4656
+ const parsed = parseDataUrlPayload(item.image_url);
4657
+ if (!parsed) {
4658
+ return item;
4659
+ }
4660
+ const uploaded = await storeCanonicalPromptFile({
4661
+ bytes: parsed.bytes,
4662
+ mimeType: parsed.mimeType ?? "application/octet-stream",
4663
+ filename: normaliseAttachmentFilename(
4664
+ getInlineAttachmentFilename(item),
4665
+ guessInlineDataFilename(parsed.mimeType)
4666
+ )
4667
+ });
4668
+ return {
4669
+ type: "input_image",
4670
+ detail: item.detail === "high" || item.detail === "low" ? item.detail : "auto",
4671
+ file_id: uploaded.fileId
4672
+ };
4673
+ }
4674
+ if (item.type !== "input_file" || item.file_id) {
4675
+ return item;
4676
+ }
4677
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
4678
+ const filename = normaliseAttachmentFilename(
4679
+ typeof item.filename === "string" ? item.filename : void 0,
4680
+ guessInlineDataFilename(void 0)
4681
+ );
4682
+ const mimeType = inferToolOutputMimeTypeFromFilename(filename) ?? "application/octet-stream";
4683
+ const uploaded = await storeCanonicalPromptFile({
4684
+ bytes: decodeInlineDataBuffer(item.file_data),
4685
+ mimeType,
4686
+ filename
4687
+ });
4688
+ return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4689
+ }
4690
+ if (typeof item.file_url === "string" && item.file_url.trim().toLowerCase().startsWith("data:")) {
4691
+ const parsed = parseDataUrlPayload(item.file_url);
4692
+ if (!parsed) {
4693
+ return item;
4694
+ }
4695
+ const uploaded = await storeCanonicalPromptFile({
4696
+ bytes: parsed.bytes,
4697
+ mimeType: parsed.mimeType ?? "application/octet-stream",
4698
+ filename: normaliseAttachmentFilename(
4699
+ typeof item.filename === "string" ? item.filename : void 0,
4700
+ guessInlineDataFilename(parsed.mimeType)
4701
+ )
4702
+ });
4703
+ return { type: "input_file", file_id: uploaded.fileId, filename: uploaded.filename };
4704
+ }
4705
+ return item;
4706
+ }
4707
+ async function prepareOpenAiPromptInput(input) {
4708
+ const prepareItem = async (item) => {
4709
+ if (!item || typeof item !== "object") {
4710
+ return item;
4711
+ }
4712
+ const record = item;
4713
+ if (Array.isArray(record.content)) {
4714
+ return {
4715
+ ...record,
4716
+ content: await Promise.all(
4717
+ record.content.map((part) => prepareOpenAiPromptContentItem(part))
4718
+ )
4719
+ };
4720
+ }
4721
+ if (Array.isArray(record.output)) {
4722
+ return {
4723
+ ...record,
4724
+ output: await Promise.all(
4725
+ record.output.map((part) => prepareOpenAiPromptContentItem(part))
4726
+ )
4727
+ };
4728
+ }
4729
+ return await prepareOpenAiPromptContentItem(item);
4730
+ };
4731
+ return await Promise.all(input.map((item) => prepareItem(item)));
4732
+ }
4733
+ async function maybePrepareOpenAiPromptInput(input) {
4734
+ if (estimateOpenAiInlinePromptBytes(input) <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
4735
+ return Array.from(input);
4736
+ }
4737
+ return await prepareOpenAiPromptInput(input);
4738
+ }
4739
+ function estimateGeminiInlinePromptBytes(contents) {
4740
+ let total = 0;
4741
+ for (const content of contents) {
4742
+ for (const part of content.parts ?? []) {
4743
+ if (part.inlineData?.data) {
4744
+ total += estimateInlinePayloadBytes(part.inlineData.data);
4745
+ }
4746
+ }
4747
+ }
4748
+ return total;
4749
+ }
4750
+ function hasCanonicalGeminiFileReferences(contents) {
4751
+ for (const content of contents) {
4752
+ for (const part of content.parts ?? []) {
4753
+ if (parseCanonicalGeminiFileId(part.fileData?.fileUri)) {
4754
+ return true;
4755
+ }
4756
+ }
4757
+ }
4758
+ return false;
4759
+ }
4760
+ async function prepareGeminiPromptContents(contents) {
4761
+ const backend = getGeminiBackend();
4762
+ const preparedContents = [];
4763
+ for (const content of contents) {
4764
+ const parts = [];
4765
+ for (const part of content.parts ?? []) {
4766
+ const canonicalFileId = parseCanonicalGeminiFileId(part.fileData?.fileUri);
4767
+ if (canonicalFileId) {
4768
+ const metadata = await getCanonicalFileMetadata(canonicalFileId);
4769
+ if (backend === "api") {
4770
+ const mirrored = await ensureGeminiFileMirror(canonicalFileId);
4771
+ const mirroredPart = createPartFromUri(mirrored.uri, mirrored.mimeType);
4772
+ if (metadata.filename && mirroredPart.fileData) {
4773
+ mirroredPart.fileData.displayName = metadata.filename;
4774
+ }
4775
+ parts.push(mirroredPart);
4776
+ } else {
4777
+ const mirrored = await ensureVertexFileMirror(canonicalFileId);
4778
+ parts.push({
4779
+ fileData: {
4780
+ fileUri: mirrored.fileUri,
4781
+ mimeType: mirrored.mimeType,
4782
+ displayName: metadata.filename
4783
+ }
4784
+ });
4785
+ }
4786
+ continue;
4787
+ }
4788
+ if (part.inlineData?.data) {
4789
+ const mimeType = part.inlineData.mimeType ?? "application/octet-stream";
4790
+ const filename = normaliseAttachmentFilename(
4791
+ getInlineAttachmentFilename(part.inlineData) ?? part.inlineData.displayName ?? guessInlineDataFilename(mimeType),
4792
+ guessInlineDataFilename(mimeType)
4793
+ );
4794
+ const stored = await storeCanonicalPromptFile({
4795
+ bytes: decodeInlineDataBuffer(part.inlineData.data),
4796
+ mimeType,
4797
+ filename
4798
+ });
4799
+ if (backend === "api") {
4800
+ const mirrored = await ensureGeminiFileMirror(stored.fileId);
4801
+ const mirroredPart = createPartFromUri(mirrored.uri, mirrored.mimeType);
4802
+ if (filename && mirroredPart.fileData) {
4803
+ mirroredPart.fileData.displayName = filename;
4804
+ }
4805
+ parts.push(mirroredPart);
4806
+ } else {
4807
+ const mirrored = await ensureVertexFileMirror(stored.fileId);
4808
+ parts.push({
4809
+ fileData: {
4810
+ fileUri: mirrored.fileUri,
4811
+ mimeType: mirrored.mimeType,
4812
+ displayName: filename
4813
+ }
4814
+ });
4815
+ }
4816
+ continue;
4817
+ }
4818
+ parts.push(part);
4819
+ }
4820
+ preparedContents.push({
4821
+ ...content,
4822
+ parts
4823
+ });
4824
+ }
4825
+ return preparedContents;
4826
+ }
4827
+ async function maybePrepareGeminiPromptContents(contents) {
4828
+ if (!hasCanonicalGeminiFileReferences(contents) && estimateGeminiInlinePromptBytes(contents) <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
4829
+ return Array.from(contents);
4830
+ }
4831
+ return await prepareGeminiPromptContents(contents);
4832
+ }
4833
+ function mergeConsecutiveTextParts(parts) {
3723
4834
  if (parts.length === 0) {
3724
4835
  return [];
3725
4836
  }
3726
4837
  const merged = [];
3727
4838
  for (const part of parts) {
3728
4839
  if (part.type !== "text") {
3729
- merged.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
4840
+ merged.push(cloneContentPart(part));
3730
4841
  continue;
3731
4842
  }
3732
4843
  const isThought = part.thought === true;
@@ -4157,13 +5268,7 @@ function resolveTextContents(input) {
4157
5268
  const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
4158
5269
  contents.push({
4159
5270
  role: message.role,
4160
- parts: parts.map(
4161
- (part) => part.type === "text" ? {
4162
- type: "text",
4163
- text: part.text,
4164
- thought: "thought" in part && part.thought === true ? true : void 0
4165
- } : { type: "inlineData", data: part.data, mimeType: part.mimeType }
4166
- )
5271
+ parts: parts.map((part) => cloneContentPart(part))
4167
5272
  });
4168
5273
  }
4169
5274
  return contents;
@@ -4179,22 +5284,58 @@ function toOpenAiInput(contents) {
4179
5284
  return contents.map((content) => {
4180
5285
  const parts = [];
4181
5286
  for (const part of content.parts) {
4182
- if (part.type === "text") {
4183
- parts.push({ type: "input_text", text: part.text });
4184
- continue;
4185
- }
4186
- const mimeType = part.mimeType;
4187
- if (isInlineImageMime(mimeType)) {
4188
- const dataUrl = `data:${mimeType};base64,${part.data}`;
4189
- parts.push({ type: "input_image", image_url: dataUrl, detail: "auto" });
4190
- continue;
5287
+ switch (part.type) {
5288
+ case "text":
5289
+ parts.push({ type: "input_text", text: part.text });
5290
+ break;
5291
+ case "inlineData": {
5292
+ const mimeType = part.mimeType;
5293
+ if (isInlineImageMime(mimeType)) {
5294
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
5295
+ const imagePart = {
5296
+ type: "input_image",
5297
+ image_url: dataUrl,
5298
+ detail: "auto"
5299
+ };
5300
+ setInlineAttachmentFilename(
5301
+ imagePart,
5302
+ normaliseAttachmentFilename(part.filename, guessInlineDataFilename(mimeType))
5303
+ );
5304
+ parts.push(imagePart);
5305
+ break;
5306
+ }
5307
+ parts.push({
5308
+ type: "input_file",
5309
+ filename: normaliseAttachmentFilename(part.filename, guessInlineDataFilename(mimeType)),
5310
+ file_data: part.data
5311
+ });
5312
+ break;
5313
+ }
5314
+ case "input_image": {
5315
+ const imagePart = {
5316
+ type: "input_image",
5317
+ ...part.file_id ? { file_id: part.file_id } : {},
5318
+ ...part.image_url ? { image_url: part.image_url } : {},
5319
+ detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
5320
+ };
5321
+ if (part.filename) {
5322
+ setInlineAttachmentFilename(imagePart, part.filename);
5323
+ }
5324
+ parts.push(imagePart);
5325
+ break;
5326
+ }
5327
+ case "input_file":
5328
+ parts.push({
5329
+ type: "input_file",
5330
+ ...part.file_id ? { file_id: part.file_id } : {},
5331
+ ...part.file_data ? { file_data: part.file_data } : {},
5332
+ ...part.file_url ? { file_url: part.file_url } : {},
5333
+ ...part.filename ? { filename: part.filename } : {}
5334
+ });
5335
+ break;
5336
+ default:
5337
+ throw new Error("Unsupported LLM content part");
4191
5338
  }
4192
- const fileData = decodeInlineDataBuffer(part.data).toString("base64");
4193
- parts.push({
4194
- type: "input_file",
4195
- filename: guessInlineDataFilename(mimeType),
4196
- file_data: fileData
4197
- });
4198
5339
  }
4199
5340
  if (parts.length === 1 && parts[0]?.type === "input_text" && typeof parts[0].text === "string") {
4200
5341
  return {
@@ -4231,28 +5372,54 @@ function toChatGptInput(contents) {
4231
5372
  continue;
4232
5373
  }
4233
5374
  if (isAssistant) {
4234
- const mimeType = part.mimeType ?? "application/octet-stream";
5375
+ const mimeType = part.type === "inlineData" ? part.mimeType ?? "application/octet-stream" : inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream";
4235
5376
  parts.push({
4236
5377
  type: "output_text",
4237
- text: isInlineImageMime(part.mimeType) ? `[image:${mimeType}]` : `[file:${mimeType}]`
5378
+ text: part.type === "input_image" || isInlineImageMime(part.mimeType) ? `[image:${mimeType}]` : `[file:${mimeType}]`
4238
5379
  });
4239
- } else {
4240
- if (isInlineImageMime(part.mimeType)) {
4241
- const mimeType = part.mimeType ?? "application/octet-stream";
4242
- const dataUrl = `data:${mimeType};base64,${part.data}`;
5380
+ continue;
5381
+ }
5382
+ switch (part.type) {
5383
+ case "inlineData": {
5384
+ if (isInlineImageMime(part.mimeType)) {
5385
+ const mimeType = part.mimeType ?? "application/octet-stream";
5386
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
5387
+ parts.push({
5388
+ type: "input_image",
5389
+ image_url: dataUrl,
5390
+ detail: "auto"
5391
+ });
5392
+ } else {
5393
+ parts.push({
5394
+ type: "input_file",
5395
+ filename: normaliseAttachmentFilename(
5396
+ part.filename,
5397
+ guessInlineDataFilename(part.mimeType)
5398
+ ),
5399
+ file_data: part.data
5400
+ });
5401
+ }
5402
+ break;
5403
+ }
5404
+ case "input_image":
4243
5405
  parts.push({
4244
5406
  type: "input_image",
4245
- image_url: dataUrl,
4246
- detail: "auto"
5407
+ ...part.file_id ? { file_id: part.file_id } : {},
5408
+ ...part.image_url ? { image_url: part.image_url } : {},
5409
+ detail: part.detail === "high" || part.detail === "low" ? part.detail : "auto"
4247
5410
  });
4248
- } else {
4249
- const fileData = decodeInlineDataBuffer(part.data).toString("base64");
5411
+ break;
5412
+ case "input_file":
4250
5413
  parts.push({
4251
5414
  type: "input_file",
4252
- filename: guessInlineDataFilename(part.mimeType),
4253
- file_data: fileData
5415
+ ...part.file_id ? { file_id: part.file_id } : {},
5416
+ ...part.file_data ? { file_data: part.file_data } : {},
5417
+ ...part.file_url ? { file_url: part.file_url } : {},
5418
+ ...part.filename ? { filename: part.filename } : {}
4254
5419
  });
4255
- }
5420
+ break;
5421
+ default:
5422
+ throw new Error("Unsupported LLM content part");
4256
5423
  }
4257
5424
  }
4258
5425
  if (parts.length === 0) {
@@ -4296,8 +5463,8 @@ ${JSON.stringify(options.responseJsonSchema)}`);
4296
5463
  if (part.type === "text") {
4297
5464
  return part.text;
4298
5465
  }
4299
- const mimeType = part.mimeType ?? "application/octet-stream";
4300
- if (isInlineImageMime(mimeType)) {
5466
+ const mimeType = part.type === "inlineData" ? part.mimeType ?? "application/octet-stream" : inferToolOutputMimeTypeFromFilename(part.filename) ?? "application/octet-stream";
5467
+ if (part.type === "input_image" || isInlineImageMime(mimeType)) {
4301
5468
  return `[image:${mimeType}]`;
4302
5469
  }
4303
5470
  return `[file:${mimeType}]`;
@@ -4568,7 +5735,14 @@ function isLlmToolOutputContentItem(value) {
4568
5735
  return typeof value.text === "string";
4569
5736
  }
4570
5737
  if (itemType === "input_image") {
4571
- return typeof value.image_url === "string";
5738
+ const keys = ["image_url", "file_id", "filename"];
5739
+ for (const key of keys) {
5740
+ const part = value[key];
5741
+ if (part !== void 0 && part !== null && typeof part !== "string") {
5742
+ return false;
5743
+ }
5744
+ }
5745
+ return value.image_url !== void 0 || value.file_id !== void 0;
4572
5746
  }
4573
5747
  if (itemType === "input_file") {
4574
5748
  const keys = ["file_data", "file_id", "file_url", "filename"];
@@ -4637,15 +5811,252 @@ function inferToolOutputMimeTypeFromFilename(filename) {
4637
5811
  }
4638
5812
  return void 0;
4639
5813
  }
4640
- function buildGeminiToolOutputMediaPart(item) {
5814
+ function estimateToolOutputItemBytes(item) {
5815
+ if (item.type === "input_text") {
5816
+ return Buffer5.byteLength(item.text, "utf8");
5817
+ }
4641
5818
  if (item.type === "input_image") {
5819
+ return typeof item.image_url === "string" ? estimateInlinePayloadBytes(item.image_url) : 0;
5820
+ }
5821
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
5822
+ return estimateInlinePayloadBytes(item.file_data);
5823
+ }
5824
+ if (typeof item.file_url === "string" && item.file_url.trim().length > 0) {
5825
+ return estimateInlinePayloadBytes(item.file_url);
5826
+ }
5827
+ return 0;
5828
+ }
5829
+ async function spillTextToolOutputToFile(options) {
5830
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5831
+ return await filesCreate({
5832
+ data: options.text,
5833
+ filename: options.filename,
5834
+ mimeType: options.mimeType,
5835
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5836
+ });
5837
+ });
5838
+ return [
5839
+ {
5840
+ type: "input_text",
5841
+ text: `Tool output was attached as ${stored.filename} (${stored.id}) because it exceeded the inline payload threshold.`
5842
+ },
5843
+ {
5844
+ type: "input_file",
5845
+ file_id: stored.id,
5846
+ filename: stored.filename
5847
+ }
5848
+ ];
5849
+ }
5850
+ async function maybeSpillToolOutputItem(item, toolName, options) {
5851
+ if (options?.force !== true && estimateToolOutputItemBytes(item) <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
5852
+ return item;
5853
+ }
5854
+ if (item.type === "input_text") {
5855
+ return await spillTextToolOutputToFile({
5856
+ text: item.text,
5857
+ filename: normaliseAttachmentFilename(`${toolName}.txt`, "tool-output.txt"),
5858
+ mimeType: "text/plain"
5859
+ });
5860
+ }
5861
+ if (item.type === "input_image") {
5862
+ if (item.file_id || !item.image_url) {
5863
+ return item;
5864
+ }
4642
5865
  const parsed = parseDataUrlPayload(item.image_url);
4643
5866
  if (!parsed) {
5867
+ return item;
5868
+ }
5869
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5870
+ return await filesCreate({
5871
+ data: parsed.bytes,
5872
+ filename: normaliseAttachmentFilename(
5873
+ item.filename ?? guessInlineDataFilename(parsed.mimeType),
5874
+ guessInlineDataFilename(parsed.mimeType)
5875
+ ),
5876
+ mimeType: parsed.mimeType,
5877
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5878
+ });
5879
+ });
5880
+ return {
5881
+ type: "input_image",
5882
+ file_id: stored.id,
5883
+ detail: item.detail ?? "auto",
5884
+ filename: stored.filename
5885
+ };
5886
+ }
5887
+ if (item.file_id) {
5888
+ return item;
5889
+ }
5890
+ if (typeof item.file_data === "string" && item.file_data.trim().length > 0) {
5891
+ const fileData = item.file_data;
5892
+ const filename = normaliseAttachmentFilename(
5893
+ item.filename ?? `${toolName}.bin`,
5894
+ `${toolName}.bin`
5895
+ );
5896
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5897
+ return await filesCreate({
5898
+ data: decodeInlineDataBuffer(fileData),
5899
+ filename,
5900
+ mimeType: inferToolOutputMimeTypeFromFilename(filename) ?? "application/octet-stream",
5901
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5902
+ });
5903
+ });
5904
+ return {
5905
+ type: "input_file",
5906
+ file_id: stored.id,
5907
+ filename: stored.filename
5908
+ };
5909
+ }
5910
+ if (typeof item.file_url === "string" && item.file_url.trim().length > 0) {
5911
+ const parsed = parseDataUrlPayload(item.file_url);
5912
+ if (!parsed) {
5913
+ return item;
5914
+ }
5915
+ const stored = await runWithFileUploadSource("tool_output_spill", async () => {
5916
+ return await filesCreate({
5917
+ data: parsed.bytes,
5918
+ filename: normaliseAttachmentFilename(
5919
+ item.filename ?? guessInlineDataFilename(parsed.mimeType),
5920
+ guessInlineDataFilename(parsed.mimeType)
5921
+ ),
5922
+ mimeType: parsed.mimeType,
5923
+ expiresAfterSeconds: DEFAULT_FILE_TTL_SECONDS
5924
+ });
5925
+ });
5926
+ return {
5927
+ type: "input_file",
5928
+ file_id: stored.id,
5929
+ filename: stored.filename
5930
+ };
5931
+ }
5932
+ return item;
5933
+ }
5934
+ async function maybeSpillToolOutput(value, toolName, options) {
5935
+ if (typeof value === "string") {
5936
+ if (options?.force !== true && Buffer5.byteLength(value, "utf8") <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
5937
+ return value;
5938
+ }
5939
+ return await spillTextToolOutputToFile({
5940
+ text: value,
5941
+ filename: normaliseAttachmentFilename(`${toolName}.txt`, "tool-output.txt"),
5942
+ mimeType: "text/plain"
5943
+ });
5944
+ }
5945
+ if (isLlmToolOutputContentItem(value)) {
5946
+ return await maybeSpillToolOutputItem(value, toolName, options);
5947
+ }
5948
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
5949
+ const spilledItems = [];
5950
+ for (const item of value) {
5951
+ const maybeSpilled = await maybeSpillToolOutputItem(item, toolName, options);
5952
+ if (Array.isArray(maybeSpilled)) {
5953
+ spilledItems.push(...maybeSpilled);
5954
+ } else {
5955
+ spilledItems.push(maybeSpilled);
5956
+ }
5957
+ }
5958
+ return spilledItems;
5959
+ }
5960
+ try {
5961
+ const serialized = JSON.stringify(value, null, 2);
5962
+ if (options?.force !== true && Buffer5.byteLength(serialized, "utf8") <= TOOL_OUTPUT_SPILL_THRESHOLD_BYTES) {
5963
+ return value;
5964
+ }
5965
+ return await spillTextToolOutputToFile({
5966
+ text: serialized,
5967
+ filename: normaliseAttachmentFilename(`${toolName}.json`, "tool-output.json"),
5968
+ mimeType: "application/json"
5969
+ });
5970
+ } catch {
5971
+ return value;
5972
+ }
5973
+ }
5974
+ function estimateToolOutputPayloadBytes(value) {
5975
+ if (typeof value === "string") {
5976
+ return Buffer5.byteLength(value, "utf8");
5977
+ }
5978
+ if (isLlmToolOutputContentItem(value)) {
5979
+ return value.type === "input_text" ? 0 : estimateToolOutputItemBytes(value);
5980
+ }
5981
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
5982
+ return value.reduce((total, item) => {
5983
+ return total + (item.type === "input_text" ? 0 : estimateToolOutputItemBytes(item));
5984
+ }, 0);
5985
+ }
5986
+ return 0;
5987
+ }
5988
+ async function maybeSpillCombinedToolCallOutputs(callResults) {
5989
+ const totalBytes = callResults.reduce(
5990
+ (sum, callResult) => sum + estimateToolOutputPayloadBytes(callResult.outputPayload),
5991
+ 0
5992
+ );
5993
+ if (totalBytes <= INLINE_ATTACHMENT_PROMPT_THRESHOLD_BYTES) {
5994
+ return Array.from(callResults);
5995
+ }
5996
+ return await Promise.all(
5997
+ callResults.map(async (callResult) => {
5998
+ if (estimateToolOutputPayloadBytes(callResult.outputPayload) === 0) {
5999
+ return callResult;
6000
+ }
6001
+ const outputPayload = await maybeSpillToolOutput(
6002
+ callResult.outputPayload,
6003
+ callResult.entry.toolName,
6004
+ {
6005
+ force: true
6006
+ }
6007
+ );
6008
+ return {
6009
+ ...callResult,
6010
+ outputPayload,
6011
+ result: {
6012
+ ...callResult.result,
6013
+ output: outputPayload
6014
+ }
6015
+ };
6016
+ })
6017
+ );
6018
+ }
6019
+ function buildGeminiToolOutputMediaPart(item) {
6020
+ if (item.type === "input_image") {
6021
+ if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
6022
+ return {
6023
+ fileData: {
6024
+ fileUri: buildCanonicalGeminiFileUri(item.file_id),
6025
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6026
+ displayName: item.filename ?? void 0
6027
+ }
6028
+ };
6029
+ }
6030
+ if (typeof item.image_url !== "string" || item.image_url.trim().length === 0) {
4644
6031
  return null;
4645
6032
  }
4646
- return createPartFromBase64(parsed.dataBase64, parsed.mimeType);
6033
+ const parsed = parseDataUrlPayload(item.image_url);
6034
+ if (parsed) {
6035
+ const part = createPartFromBase64(parsed.dataBase64, parsed.mimeType);
6036
+ const displayName = item.filename?.trim();
6037
+ if (displayName && part.inlineData) {
6038
+ part.inlineData.displayName = displayName;
6039
+ }
6040
+ return part;
6041
+ }
6042
+ return {
6043
+ fileData: {
6044
+ fileUri: item.image_url,
6045
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6046
+ displayName: item.filename ?? void 0
6047
+ }
6048
+ };
4647
6049
  }
4648
6050
  if (item.type === "input_file") {
6051
+ if (typeof item.file_id === "string" && item.file_id.trim().length > 0) {
6052
+ return {
6053
+ fileData: {
6054
+ fileUri: buildCanonicalGeminiFileUri(item.file_id),
6055
+ mimeType: inferToolOutputMimeTypeFromFilename(item.filename) ?? "application/octet-stream",
6056
+ displayName: item.filename ?? void 0
6057
+ }
6058
+ };
6059
+ }
4649
6060
  const dataUrl = typeof item.file_url === "string" ? parseDataUrlPayload(item.file_url) : null;
4650
6061
  if (dataUrl) {
4651
6062
  const part = createPartFromBase64(dataUrl.dataBase64, dataUrl.mimeType);
@@ -4683,11 +6094,12 @@ function toGeminiToolOutputPlaceholder(item) {
4683
6094
  };
4684
6095
  }
4685
6096
  if (item.type === "input_image") {
4686
- const parsed = parseDataUrlPayload(item.image_url);
6097
+ const parsed = typeof item.image_url === "string" ? parseDataUrlPayload(item.image_url) : null;
4687
6098
  return {
4688
6099
  type: item.type,
6100
+ fileId: item.file_id ?? void 0,
4689
6101
  mimeType: parsed?.mimeType ?? void 0,
4690
- media: "attached-inline-data"
6102
+ media: item.file_id ? "attached-file-id" : parsed ? "attached-inline-data" : item.image_url ? "attached-file-data" : void 0
4691
6103
  };
4692
6104
  }
4693
6105
  const dataUrl = typeof item.file_url === "string" ? parseDataUrlPayload(item.file_url) : null;
@@ -4696,7 +6108,7 @@ function toGeminiToolOutputPlaceholder(item) {
4696
6108
  filename: item.filename ?? void 0,
4697
6109
  fileId: item.file_id ?? void 0,
4698
6110
  mimeType: dataUrl?.mimeType ?? inferToolOutputMimeTypeFromFilename(item.filename) ?? void 0,
4699
- 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
6111
+ 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
4700
6112
  };
4701
6113
  }
4702
6114
  function buildGeminiFunctionResponseParts(options) {
@@ -4744,8 +6156,8 @@ function parseOpenAiToolArguments(raw) {
4744
6156
  function formatZodIssues(issues) {
4745
6157
  const messages = [];
4746
6158
  for (const issue of issues) {
4747
- const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4748
- messages.push(`${path8}: ${issue.message}`);
6159
+ const path10 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
6160
+ messages.push(`${path10}: ${issue.message}`);
4749
6161
  }
4750
6162
  return messages.join("; ");
4751
6163
  }
@@ -4863,8 +6275,9 @@ async function executeToolCall(params) {
4863
6275
  const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
4864
6276
  try {
4865
6277
  const output = await tool2.execute(input);
4866
- const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4867
- return finalize({ toolName, input, output }, output, metrics);
6278
+ const outputPayload = await maybeSpillToolOutput(output, toolName);
6279
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(outputPayload) : void 0;
6280
+ return finalize({ toolName, input, output: outputPayload }, outputPayload, metrics);
4868
6281
  } catch (error) {
4869
6282
  const message = error instanceof Error ? error.message : String(error);
4870
6283
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
@@ -4898,8 +6311,13 @@ async function executeToolCall(params) {
4898
6311
  }
4899
6312
  try {
4900
6313
  const output = await tool2.execute(parsed.data);
4901
- const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4902
- return finalize({ toolName, input: parsed.data, output }, output, metrics);
6314
+ const outputPayload = await maybeSpillToolOutput(output, toolName);
6315
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(outputPayload) : void 0;
6316
+ return finalize(
6317
+ { toolName, input: parsed.data, output: outputPayload },
6318
+ outputPayload,
6319
+ metrics
6320
+ );
4903
6321
  } catch (error) {
4904
6322
  const message = error instanceof Error ? error.message : String(error);
4905
6323
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
@@ -5100,7 +6518,7 @@ function toGemini25ProThinkingBudget(thinkingLevel) {
5100
6518
  }
5101
6519
  }
5102
6520
  function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
5103
- if (isGeminiImageModelId(modelId)) {
6521
+ if (isGeminiImageModelId(modelId) || modelId === "gemini-flash-lite-latest") {
5104
6522
  return void 0;
5105
6523
  }
5106
6524
  if (thinkingLevel) {
@@ -5131,9 +6549,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
5131
6549
  }
5132
6550
  function decodeInlineDataBuffer(base64) {
5133
6551
  try {
5134
- return Buffer4.from(base64, "base64");
6552
+ return Buffer5.from(base64, "base64");
5135
6553
  } catch {
5136
- return Buffer4.from(base64, "base64url");
6554
+ return Buffer5.from(base64, "base64url");
5137
6555
  }
5138
6556
  }
5139
6557
  function extractImages(content) {
@@ -5203,7 +6621,7 @@ function parseDataUrlPayload(value) {
5203
6621
  const isBase64 = /;base64(?:;|$)/iu.test(header);
5204
6622
  const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
5205
6623
  try {
5206
- const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
6624
+ const bytes = isBase64 ? Buffer5.from(payload, "base64") : Buffer5.from(decodeURIComponent(payload), "utf8");
5207
6625
  return {
5208
6626
  mimeType,
5209
6627
  dataBase64: bytes.toString("base64"),
@@ -5306,7 +6724,10 @@ function collectLoggedAttachmentsFromLlmParts(parts, prefix) {
5306
6724
  continue;
5307
6725
  }
5308
6726
  attachments.push({
5309
- filename: buildLoggedAttachmentFilename(prefix, index, part.mimeType),
6727
+ filename: normaliseAttachmentFilename(
6728
+ part.filename,
6729
+ buildLoggedAttachmentFilename(prefix, index, part.mimeType)
6730
+ ),
5310
6731
  bytes: decodeInlineDataBuffer(part.data)
5311
6732
  });
5312
6733
  index += 1;
@@ -5435,15 +6856,19 @@ function startLlmCallLoggerFromContents(options) {
5435
6856
  sections.push(part.text);
5436
6857
  continue;
5437
6858
  }
5438
- const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
5439
- attachments.push({
5440
- filename,
5441
- bytes: decodeInlineDataBuffer(part.data)
5442
- });
5443
- attachmentIndex += 1;
5444
- sections.push(
5445
- `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5446
- );
6859
+ if (part.type === "inlineData") {
6860
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
6861
+ attachments.push({
6862
+ filename,
6863
+ bytes: decodeInlineDataBuffer(part.data)
6864
+ });
6865
+ attachmentIndex += 1;
6866
+ sections.push(
6867
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
6868
+ );
6869
+ continue;
6870
+ }
6871
+ sections.push(`[${part.type}] file_id=${"file_id" in part ? part.file_id ?? "" : ""}`);
5447
6872
  }
5448
6873
  sections.push("");
5449
6874
  }
@@ -5568,313 +6993,323 @@ async function runTextCall(params) {
5568
6993
  request.signal.addEventListener(
5569
6994
  "abort",
5570
6995
  () => abortController.abort(request.signal?.reason),
5571
- { once: true }
5572
- );
5573
- }
5574
- return abortController.signal;
5575
- };
5576
- const signal = resolveAbortSignal();
5577
- try {
5578
- if (provider === "openai") {
5579
- const openAiInput = toOpenAiInput(contents);
5580
- const openAiTools = toOpenAiTools(request.tools);
5581
- const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5582
- const openAiTextConfig = {
5583
- format: request.openAiTextFormat ?? { type: "text" },
5584
- verbosity: resolveOpenAiVerbosity(modelForProvider)
5585
- };
5586
- const reasoning = {
5587
- effort: toOpenAiReasoningEffort(reasoningEffort),
5588
- summary: "detailed"
5589
- };
5590
- await runOpenAiCall(async (client) => {
5591
- const stream = client.responses.stream(
5592
- {
5593
- model: modelForProvider,
5594
- input: openAiInput,
5595
- reasoning,
5596
- text: openAiTextConfig,
5597
- ...openAiTools ? { tools: openAiTools } : {},
5598
- include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5599
- },
5600
- { signal }
5601
- );
5602
- for await (const event of stream) {
5603
- switch (event.type) {
5604
- case "response.output_text.delta": {
5605
- const delta = event.delta ?? "";
5606
- pushDelta("response", typeof delta === "string" ? delta : "");
5607
- break;
5608
- }
5609
- case "response.reasoning_summary_text.delta": {
5610
- const delta = event.delta ?? "";
5611
- pushDelta("thought", typeof delta === "string" ? delta : "");
5612
- break;
5613
- }
5614
- case "response.refusal.delta": {
5615
- blocked = true;
5616
- queue.push({ type: "blocked" });
5617
- break;
5618
- }
5619
- default:
5620
- break;
5621
- }
5622
- }
5623
- const finalResponse = await stream.finalResponse();
5624
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5625
- queue.push({ type: "model", modelVersion });
5626
- if (finalResponse.error) {
5627
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5628
- throw new Error(message);
5629
- }
5630
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5631
- const detail = finalResponse.incomplete_details?.reason;
5632
- throw new Error(
5633
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
6996
+ { once: true }
6997
+ );
6998
+ }
6999
+ return abortController.signal;
7000
+ };
7001
+ const signal = resolveAbortSignal();
7002
+ const { result } = await collectFileUploadMetrics(async () => {
7003
+ try {
7004
+ if (provider === "openai") {
7005
+ const openAiInput = await maybePrepareOpenAiPromptInput(toOpenAiInput(contents));
7006
+ const openAiTools = toOpenAiTools(request.tools);
7007
+ const reasoningEffort = resolveOpenAiReasoningEffort(
7008
+ modelForProvider,
7009
+ request.thinkingLevel
7010
+ );
7011
+ const openAiTextConfig = {
7012
+ format: request.openAiTextFormat ?? { type: "text" },
7013
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
7014
+ };
7015
+ const reasoning = {
7016
+ effort: toOpenAiReasoningEffort(reasoningEffort),
7017
+ summary: "detailed"
7018
+ };
7019
+ await runOpenAiCall(async (client) => {
7020
+ const stream = client.responses.stream(
7021
+ {
7022
+ model: modelForProvider,
7023
+ input: openAiInput,
7024
+ reasoning,
7025
+ text: openAiTextConfig,
7026
+ ...openAiTools ? { tools: openAiTools } : {},
7027
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
7028
+ },
7029
+ { signal }
5634
7030
  );
5635
- }
5636
- latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5637
- if (responseParts.length === 0) {
5638
- const fallback = extractOpenAiResponseParts(finalResponse);
5639
- blocked = blocked || fallback.blocked;
5640
- for (const part of fallback.parts) {
5641
- if (part.type === "text") {
5642
- pushDelta(part.thought === true ? "thought" : "response", part.text);
5643
- } else {
5644
- pushInline(part.data, part.mimeType);
7031
+ for await (const event of stream) {
7032
+ switch (event.type) {
7033
+ case "response.output_text.delta": {
7034
+ const delta = event.delta ?? "";
7035
+ pushDelta("response", typeof delta === "string" ? delta : "");
7036
+ break;
7037
+ }
7038
+ case "response.reasoning_summary_text.delta": {
7039
+ const delta = event.delta ?? "";
7040
+ pushDelta("thought", typeof delta === "string" ? delta : "");
7041
+ break;
7042
+ }
7043
+ case "response.refusal.delta": {
7044
+ blocked = true;
7045
+ queue.push({ type: "blocked" });
7046
+ break;
7047
+ }
7048
+ default:
7049
+ break;
5645
7050
  }
5646
7051
  }
5647
- }
5648
- }, modelForProvider);
5649
- } else if (provider === "chatgpt") {
5650
- const chatGptInput = toChatGptInput(contents);
5651
- const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5652
- const openAiTools = toOpenAiTools(request.tools);
5653
- const requestPayload = {
5654
- model: modelForProvider,
5655
- store: false,
5656
- stream: true,
5657
- ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
5658
- instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5659
- input: chatGptInput.input,
5660
- include: ["reasoning.encrypted_content"],
5661
- reasoning: {
5662
- effort: toOpenAiReasoningEffort(reasoningEffort),
5663
- summary: "detailed"
5664
- },
5665
- text: {
5666
- format: request.openAiTextFormat ?? { type: "text" },
5667
- verbosity: resolveOpenAiVerbosity(request.model)
5668
- },
5669
- ...openAiTools ? { tools: openAiTools } : {}
5670
- };
5671
- let sawResponseDelta = false;
5672
- let sawThoughtDelta = false;
5673
- const result = await collectChatGptCodexResponseWithRetry({
5674
- request: requestPayload,
5675
- signal,
5676
- onDelta: (delta) => {
5677
- if (delta.thoughtDelta) {
5678
- sawThoughtDelta = true;
5679
- pushDelta("thought", delta.thoughtDelta);
7052
+ const finalResponse = await stream.finalResponse();
7053
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
7054
+ queue.push({ type: "model", modelVersion });
7055
+ if (finalResponse.error) {
7056
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
7057
+ throw new Error(message);
5680
7058
  }
5681
- if (delta.textDelta) {
5682
- sawResponseDelta = true;
5683
- pushDelta("response", delta.textDelta);
7059
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
7060
+ const detail = finalResponse.incomplete_details?.reason;
7061
+ throw new Error(
7062
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
7063
+ );
5684
7064
  }
5685
- }
5686
- });
5687
- blocked = blocked || result.blocked;
5688
- if (blocked) {
5689
- queue.push({ type: "blocked" });
5690
- }
5691
- if (result.model) {
5692
- modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result.model}`;
5693
- queue.push({ type: "model", modelVersion });
5694
- }
5695
- latestUsage = extractChatGptUsageTokens(result.usage);
5696
- const fallbackText = typeof result.text === "string" ? result.text : "";
5697
- const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5698
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5699
- pushDelta("thought", fallbackThoughts);
5700
- }
5701
- if (!sawResponseDelta && fallbackText.length > 0) {
5702
- pushDelta("response", fallbackText);
5703
- }
5704
- } else if (provider === "fireworks") {
5705
- if (request.tools && request.tools.length > 0) {
5706
- throw new Error(
5707
- "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
5708
- );
5709
- }
5710
- const fireworksMessages = toFireworksMessages(contents, {
5711
- responseMimeType: request.responseMimeType,
5712
- responseJsonSchema: request.responseJsonSchema
5713
- });
5714
- await runFireworksCall(async (client) => {
5715
- const responseFormat = request.responseJsonSchema ? {
5716
- type: "json_schema",
5717
- json_schema: {
5718
- name: "llm-response",
5719
- schema: request.responseJsonSchema
7065
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
7066
+ if (responseParts.length === 0) {
7067
+ const fallback = extractOpenAiResponseParts(finalResponse);
7068
+ blocked = blocked || fallback.blocked;
7069
+ for (const part of fallback.parts) {
7070
+ if (part.type === "text") {
7071
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
7072
+ } else if (part.type === "inlineData") {
7073
+ pushInline(part.data, part.mimeType);
7074
+ }
7075
+ }
5720
7076
  }
5721
- } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5722
- const response = await client.chat.completions.create(
5723
- {
5724
- model: modelForProvider,
5725
- messages: fireworksMessages,
5726
- ...responseFormat ? { response_format: responseFormat } : {}
7077
+ }, modelForProvider);
7078
+ } else if (provider === "chatgpt") {
7079
+ const chatGptInput = toChatGptInput(contents);
7080
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
7081
+ const openAiTools = toOpenAiTools(request.tools);
7082
+ const requestPayload = {
7083
+ model: modelForProvider,
7084
+ store: false,
7085
+ stream: true,
7086
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
7087
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
7088
+ input: chatGptInput.input,
7089
+ include: ["reasoning.encrypted_content"],
7090
+ reasoning: {
7091
+ effort: toOpenAiReasoningEffort(reasoningEffort),
7092
+ summary: "detailed"
5727
7093
  },
5728
- { signal }
5729
- );
5730
- modelVersion = typeof response.model === "string" ? response.model : request.model;
5731
- queue.push({ type: "model", modelVersion });
5732
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5733
- if (choice?.finish_reason === "content_filter") {
5734
- blocked = true;
7094
+ text: {
7095
+ format: request.openAiTextFormat ?? { type: "text" },
7096
+ verbosity: resolveOpenAiVerbosity(request.model)
7097
+ },
7098
+ ...openAiTools ? { tools: openAiTools } : {}
7099
+ };
7100
+ let sawResponseDelta = false;
7101
+ let sawThoughtDelta = false;
7102
+ const result2 = await collectChatGptCodexResponseWithRetry({
7103
+ request: requestPayload,
7104
+ signal,
7105
+ onDelta: (delta) => {
7106
+ if (delta.thoughtDelta) {
7107
+ sawThoughtDelta = true;
7108
+ pushDelta("thought", delta.thoughtDelta);
7109
+ }
7110
+ if (delta.textDelta) {
7111
+ sawResponseDelta = true;
7112
+ pushDelta("response", delta.textDelta);
7113
+ }
7114
+ }
7115
+ });
7116
+ blocked = blocked || result2.blocked;
7117
+ if (blocked) {
5735
7118
  queue.push({ type: "blocked" });
5736
7119
  }
5737
- const textOutput = extractFireworksMessageText(
5738
- choice?.message
5739
- );
5740
- if (textOutput.length > 0) {
5741
- pushDelta("response", textOutput);
7120
+ if (result2.model) {
7121
+ modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result2.model}`;
7122
+ queue.push({ type: "model", modelVersion });
5742
7123
  }
5743
- latestUsage = extractFireworksUsageTokens(response.usage);
5744
- }, modelForProvider);
5745
- } else {
5746
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5747
- const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5748
- const config = {
5749
- maxOutputTokens: 32e3,
5750
- ...thinkingConfig ? { thinkingConfig } : {},
5751
- ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5752
- ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5753
- ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5754
- ...request.imageAspectRatio || request.imageSize ? {
5755
- imageConfig: {
5756
- ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5757
- ...request.imageSize ? { imageSize: request.imageSize } : {}
5758
- }
5759
- } : {}
5760
- };
5761
- const geminiTools = toGeminiTools(request.tools);
5762
- if (geminiTools) {
5763
- config.tools = geminiTools;
5764
- }
5765
- await runGeminiCall(async (client) => {
5766
- const stream = await client.models.generateContentStream({
5767
- model: modelForProvider,
5768
- contents: geminiContents,
5769
- config
7124
+ latestUsage = extractChatGptUsageTokens(result2.usage);
7125
+ const fallbackText = typeof result2.text === "string" ? result2.text : "";
7126
+ const fallbackThoughts = typeof result2.reasoningSummaryText === "string" && result2.reasoningSummaryText.length > 0 ? result2.reasoningSummaryText : typeof result2.reasoningText === "string" ? result2.reasoningText : "";
7127
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
7128
+ pushDelta("thought", fallbackThoughts);
7129
+ }
7130
+ if (!sawResponseDelta && fallbackText.length > 0) {
7131
+ pushDelta("response", fallbackText);
7132
+ }
7133
+ } else if (provider === "fireworks") {
7134
+ if (request.tools && request.tools.length > 0) {
7135
+ throw new Error(
7136
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
7137
+ );
7138
+ }
7139
+ const fireworksMessages = toFireworksMessages(contents, {
7140
+ responseMimeType: request.responseMimeType,
7141
+ responseJsonSchema: request.responseJsonSchema
5770
7142
  });
5771
- let latestGrounding;
5772
- for await (const chunk of stream) {
5773
- if (chunk.modelVersion) {
5774
- modelVersion = chunk.modelVersion;
5775
- queue.push({ type: "model", modelVersion });
5776
- }
5777
- if (chunk.promptFeedback?.blockReason) {
7143
+ await runFireworksCall(async (client) => {
7144
+ const responseFormat = request.responseJsonSchema ? {
7145
+ type: "json_schema",
7146
+ json_schema: {
7147
+ name: "llm-response",
7148
+ schema: request.responseJsonSchema
7149
+ }
7150
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
7151
+ const response = await client.chat.completions.create(
7152
+ {
7153
+ model: modelForProvider,
7154
+ messages: fireworksMessages,
7155
+ ...responseFormat ? { response_format: responseFormat } : {}
7156
+ },
7157
+ { signal }
7158
+ );
7159
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
7160
+ queue.push({ type: "model", modelVersion });
7161
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
7162
+ if (choice?.finish_reason === "content_filter") {
5778
7163
  blocked = true;
5779
7164
  queue.push({ type: "blocked" });
5780
7165
  }
5781
- latestUsage = mergeTokenUpdates(
5782
- latestUsage,
5783
- extractGeminiUsageTokens(chunk.usageMetadata)
7166
+ const textOutput = extractFireworksMessageText(
7167
+ choice?.message
5784
7168
  );
5785
- const candidates = chunk.candidates;
5786
- if (!candidates || candidates.length === 0) {
5787
- continue;
5788
- }
5789
- const primary = candidates[0];
5790
- if (primary && isModerationFinish(primary.finishReason)) {
5791
- blocked = true;
5792
- queue.push({ type: "blocked" });
7169
+ if (textOutput.length > 0) {
7170
+ pushDelta("response", textOutput);
5793
7171
  }
5794
- for (const candidate of candidates) {
5795
- const candidateContent = candidate.content;
5796
- if (!candidateContent) {
5797
- continue;
7172
+ latestUsage = extractFireworksUsageTokens(response.usage);
7173
+ }, modelForProvider);
7174
+ } else {
7175
+ const geminiContents = await maybePrepareGeminiPromptContents(
7176
+ contents.map(convertLlmContentToGeminiContent)
7177
+ );
7178
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
7179
+ const config = {
7180
+ maxOutputTokens: 32e3,
7181
+ ...thinkingConfig ? { thinkingConfig } : {},
7182
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
7183
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
7184
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
7185
+ ...request.imageAspectRatio || request.imageSize ? {
7186
+ imageConfig: {
7187
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
7188
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
5798
7189
  }
5799
- if (candidate.groundingMetadata) {
5800
- latestGrounding = candidate.groundingMetadata;
7190
+ } : {}
7191
+ };
7192
+ const geminiTools = toGeminiTools(request.tools);
7193
+ if (geminiTools) {
7194
+ config.tools = geminiTools;
7195
+ }
7196
+ await runGeminiCall(async (client) => {
7197
+ const stream = await client.models.generateContentStream({
7198
+ model: modelForProvider,
7199
+ contents: geminiContents,
7200
+ config
7201
+ });
7202
+ let latestGrounding;
7203
+ for await (const chunk of stream) {
7204
+ if (chunk.modelVersion) {
7205
+ modelVersion = chunk.modelVersion;
7206
+ queue.push({ type: "model", modelVersion });
5801
7207
  }
5802
- const content2 = convertGeminiContentToLlmContent(candidateContent);
5803
- if (!responseRole) {
5804
- responseRole = content2.role;
7208
+ if (chunk.promptFeedback?.blockReason) {
7209
+ blocked = true;
7210
+ queue.push({ type: "blocked" });
5805
7211
  }
5806
- for (const part of content2.parts) {
5807
- if (part.type === "text") {
5808
- pushDelta(part.thought === true ? "thought" : "response", part.text);
5809
- } else {
5810
- pushInline(part.data, part.mimeType);
7212
+ latestUsage = mergeTokenUpdates(
7213
+ latestUsage,
7214
+ extractGeminiUsageTokens(chunk.usageMetadata)
7215
+ );
7216
+ const candidates = chunk.candidates;
7217
+ if (!candidates || candidates.length === 0) {
7218
+ continue;
7219
+ }
7220
+ const primary = candidates[0];
7221
+ if (primary && isModerationFinish(primary.finishReason)) {
7222
+ blocked = true;
7223
+ queue.push({ type: "blocked" });
7224
+ }
7225
+ for (const candidate of candidates) {
7226
+ const candidateContent = candidate.content;
7227
+ if (!candidateContent) {
7228
+ continue;
7229
+ }
7230
+ if (candidate.groundingMetadata) {
7231
+ latestGrounding = candidate.groundingMetadata;
7232
+ }
7233
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
7234
+ if (!responseRole) {
7235
+ responseRole = content2.role;
7236
+ }
7237
+ for (const part of content2.parts) {
7238
+ if (part.type === "text") {
7239
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
7240
+ } else if (part.type === "inlineData") {
7241
+ pushInline(part.data, part.mimeType);
7242
+ }
5811
7243
  }
5812
7244
  }
5813
7245
  }
7246
+ grounding = latestGrounding;
7247
+ }, modelForProvider);
7248
+ }
7249
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
7250
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
7251
+ const { text, thoughts } = extractTextByChannel(content);
7252
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
7253
+ const costUsd = estimateCallCostUsd({
7254
+ modelId: modelVersion,
7255
+ tokens: latestUsage,
7256
+ responseImages,
7257
+ imageSize: request.imageSize
7258
+ });
7259
+ if (latestUsage) {
7260
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
7261
+ }
7262
+ callLogger?.complete({
7263
+ responseText: text,
7264
+ attachments: outputAttachments,
7265
+ metadata: {
7266
+ provider,
7267
+ model: request.model,
7268
+ modelVersion,
7269
+ blocked,
7270
+ costUsd,
7271
+ usage: latestUsage,
7272
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
7273
+ responseChars: text.length,
7274
+ thoughtChars: thoughts.length,
7275
+ responseImages,
7276
+ uploads: getCurrentFileUploadMetrics()
5814
7277
  }
5815
- grounding = latestGrounding;
5816
- }, modelForProvider);
5817
- }
5818
- const mergedParts = mergeConsecutiveTextParts(responseParts);
5819
- const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5820
- const { text, thoughts } = extractTextByChannel(content);
5821
- const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5822
- const costUsd = estimateCallCostUsd({
5823
- modelId: modelVersion,
5824
- tokens: latestUsage,
5825
- responseImages,
5826
- imageSize: request.imageSize
5827
- });
5828
- if (latestUsage) {
5829
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5830
- }
5831
- callLogger?.complete({
5832
- responseText: text,
5833
- attachments: outputAttachments,
5834
- metadata: {
5835
- provider,
5836
- model: request.model,
5837
- modelVersion,
5838
- blocked,
5839
- costUsd,
5840
- usage: latestUsage,
5841
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5842
- responseChars: text.length,
5843
- thoughtChars: thoughts.length,
5844
- responseImages
5845
- }
5846
- });
5847
- return {
5848
- provider,
5849
- model: request.model,
5850
- modelVersion,
5851
- content,
5852
- text,
5853
- thoughts,
5854
- blocked,
5855
- usage: latestUsage,
5856
- costUsd,
5857
- grounding
5858
- };
5859
- } catch (error) {
5860
- const partialParts = mergeConsecutiveTextParts(responseParts);
5861
- const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
5862
- const { text: partialText } = extractTextByChannel(partialContent);
5863
- callLogger?.fail(error, {
5864
- responseText: partialText,
5865
- attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
5866
- metadata: {
7278
+ });
7279
+ return {
5867
7280
  provider,
5868
7281
  model: request.model,
5869
7282
  modelVersion,
7283
+ content,
7284
+ text,
7285
+ thoughts,
5870
7286
  blocked,
5871
7287
  usage: latestUsage,
5872
- partialResponseParts: responseParts.length,
5873
- responseImages
5874
- }
5875
- });
5876
- throw error;
5877
- }
7288
+ costUsd,
7289
+ grounding
7290
+ };
7291
+ } catch (error) {
7292
+ const partialParts = mergeConsecutiveTextParts(responseParts);
7293
+ const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
7294
+ const { text: partialText } = extractTextByChannel(partialContent);
7295
+ callLogger?.fail(error, {
7296
+ responseText: partialText,
7297
+ attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
7298
+ metadata: {
7299
+ provider,
7300
+ model: request.model,
7301
+ modelVersion,
7302
+ blocked,
7303
+ usage: latestUsage,
7304
+ partialResponseParts: responseParts.length,
7305
+ responseImages,
7306
+ uploads: getCurrentFileUploadMetrics()
7307
+ }
7308
+ });
7309
+ throw error;
7310
+ }
7311
+ });
7312
+ return result;
5878
7313
  }
5879
7314
  function streamText(request) {
5880
7315
  const queue = createAsyncQueue();
@@ -6157,7 +7592,12 @@ function normalizeToolLoopSteeringInput(input) {
6157
7592
  if (part.type === "text") {
6158
7593
  parts.push({ type: "text", text: part.text });
6159
7594
  } else {
6160
- parts.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
7595
+ parts.push({
7596
+ type: "inlineData",
7597
+ data: part.data,
7598
+ mimeType: part.mimeType,
7599
+ filename: part.filename
7600
+ });
6161
7601
  }
6162
7602
  }
6163
7603
  if (parts.length > 0) {
@@ -6345,9 +7785,10 @@ async function runToolLoop(request) {
6345
7785
  let reasoningSummary = "";
6346
7786
  let stepToolCallText;
6347
7787
  let stepToolCallPayload;
7788
+ const preparedInput = await maybePrepareOpenAiPromptInput(input);
6348
7789
  const stepRequestPayload = {
6349
7790
  model: providerInfo.model,
6350
- input,
7791
+ input: preparedInput,
6351
7792
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
6352
7793
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
6353
7794
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -6375,7 +7816,7 @@ async function runToolLoop(request) {
6375
7816
  const stream = client.responses.stream(
6376
7817
  {
6377
7818
  model: providerInfo.model,
6378
- input,
7819
+ input: preparedInput,
6379
7820
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
6380
7821
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
6381
7822
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -6552,27 +7993,29 @@ async function runToolLoop(request) {
6552
7993
  input: entry.value
6553
7994
  });
6554
7995
  }
6555
- const callResults = await Promise.all(
6556
- callInputs.map(async (entry) => {
6557
- return await toolCallContextStorage.run(
6558
- {
6559
- toolName: entry.toolName,
6560
- toolId: entry.toolId,
6561
- turn: entry.turn,
6562
- toolIndex: entry.toolIndex
6563
- },
6564
- async () => {
6565
- const { result, outputPayload } = await executeToolCall({
6566
- callKind: entry.call.kind,
7996
+ const callResults = await maybeSpillCombinedToolCallOutputs(
7997
+ await Promise.all(
7998
+ callInputs.map(async (entry) => {
7999
+ return await toolCallContextStorage.run(
8000
+ {
6567
8001
  toolName: entry.toolName,
6568
- tool: request.tools[entry.toolName],
6569
- rawInput: entry.value,
6570
- parseError: entry.parseError
6571
- });
6572
- return { entry, result, outputPayload };
6573
- }
6574
- );
6575
- })
8002
+ toolId: entry.toolId,
8003
+ turn: entry.turn,
8004
+ toolIndex: entry.toolIndex
8005
+ },
8006
+ async () => {
8007
+ const { result, outputPayload } = await executeToolCall({
8008
+ callKind: entry.call.kind,
8009
+ toolName: entry.toolName,
8010
+ tool: request.tools[entry.toolName],
8011
+ rawInput: entry.value,
8012
+ parseError: entry.parseError
8013
+ });
8014
+ return { entry, result, outputPayload };
8015
+ }
8016
+ );
8017
+ })
8018
+ )
6576
8019
  );
6577
8020
  const toolOutputs = [];
6578
8021
  let toolExecutionMs = 0;
@@ -6853,27 +8296,29 @@ async function runToolLoop(request) {
6853
8296
  input: entry.value
6854
8297
  });
6855
8298
  }
6856
- const callResults = await Promise.all(
6857
- callInputs.map(async (entry) => {
6858
- return await toolCallContextStorage.run(
6859
- {
6860
- toolName: entry.toolName,
6861
- toolId: entry.toolId,
6862
- turn: entry.turn,
6863
- toolIndex: entry.toolIndex
6864
- },
6865
- async () => {
6866
- const { result, outputPayload } = await executeToolCall({
6867
- callKind: entry.call.kind,
8299
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8300
+ await Promise.all(
8301
+ callInputs.map(async (entry) => {
8302
+ return await toolCallContextStorage.run(
8303
+ {
6868
8304
  toolName: entry.toolName,
6869
- tool: request.tools[entry.toolName],
6870
- rawInput: entry.value,
6871
- parseError: entry.parseError
6872
- });
6873
- return { entry, result, outputPayload };
6874
- }
6875
- );
6876
- })
8305
+ toolId: entry.toolId,
8306
+ turn: entry.turn,
8307
+ toolIndex: entry.toolIndex
8308
+ },
8309
+ async () => {
8310
+ const { result, outputPayload } = await executeToolCall({
8311
+ callKind: entry.call.kind,
8312
+ toolName: entry.toolName,
8313
+ tool: request.tools[entry.toolName],
8314
+ rawInput: entry.value,
8315
+ parseError: entry.parseError
8316
+ });
8317
+ return { entry, result, outputPayload };
8318
+ }
8319
+ );
8320
+ })
8321
+ )
6877
8322
  );
6878
8323
  let toolExecutionMs = 0;
6879
8324
  let waitToolMs = 0;
@@ -7145,27 +8590,29 @@ async function runToolLoop(request) {
7145
8590
  input: entry.value
7146
8591
  });
7147
8592
  }
7148
- const callResults = await Promise.all(
7149
- callInputs.map(async (entry) => {
7150
- return await toolCallContextStorage.run(
7151
- {
7152
- toolName: entry.toolName,
7153
- toolId: entry.toolId,
7154
- turn: entry.turn,
7155
- toolIndex: entry.toolIndex
7156
- },
7157
- async () => {
7158
- const { result, outputPayload } = await executeToolCall({
7159
- callKind: "function",
8593
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8594
+ await Promise.all(
8595
+ callInputs.map(async (entry) => {
8596
+ return await toolCallContextStorage.run(
8597
+ {
7160
8598
  toolName: entry.toolName,
7161
- tool: request.tools[entry.toolName],
7162
- rawInput: entry.value,
7163
- parseError: entry.parseError
7164
- });
7165
- return { entry, result, outputPayload };
7166
- }
7167
- );
7168
- })
8599
+ toolId: entry.toolId,
8600
+ turn: entry.turn,
8601
+ toolIndex: entry.toolIndex
8602
+ },
8603
+ async () => {
8604
+ const { result, outputPayload } = await executeToolCall({
8605
+ callKind: "function",
8606
+ toolName: entry.toolName,
8607
+ tool: request.tools[entry.toolName],
8608
+ rawInput: entry.value,
8609
+ parseError: entry.parseError
8610
+ });
8611
+ return { entry, result, outputPayload };
8612
+ }
8613
+ );
8614
+ })
8615
+ )
7169
8616
  );
7170
8617
  const assistantToolCalls = [];
7171
8618
  const toolMessages = [];
@@ -7304,9 +8751,10 @@ async function runToolLoop(request) {
7304
8751
  ...thinkingConfig ? { thinkingConfig } : {}
7305
8752
  };
7306
8753
  const onEvent = request.onEvent;
8754
+ const preparedGeminiContents = await maybePrepareGeminiPromptContents(geminiContents);
7307
8755
  const stepRequestPayload = {
7308
8756
  model: request.model,
7309
- contents: geminiContents,
8757
+ contents: preparedGeminiContents,
7310
8758
  config
7311
8759
  };
7312
8760
  const stepCallLogger = startLlmCallLoggerFromPayload({
@@ -7320,15 +8768,13 @@ async function runToolLoop(request) {
7320
8768
  async (client) => {
7321
8769
  const stream = await client.models.generateContentStream({
7322
8770
  model: request.model,
7323
- contents: geminiContents,
8771
+ contents: preparedGeminiContents,
7324
8772
  config
7325
8773
  });
7326
8774
  let responseText2 = "";
7327
8775
  let thoughtsText2 = "";
7328
8776
  const modelParts = [];
7329
8777
  const functionCalls = [];
7330
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
7331
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
7332
8778
  let latestUsageMetadata;
7333
8779
  let resolvedModelVersion;
7334
8780
  for await (const chunk of stream) {
@@ -7345,34 +8791,13 @@ async function runToolLoop(request) {
7345
8791
  continue;
7346
8792
  }
7347
8793
  const primary = candidates[0];
7348
- const parts = primary?.content?.parts;
7349
- if (!parts || parts.length === 0) {
8794
+ const parts = primary?.content?.parts ?? [];
8795
+ const chunkFunctionCalls = chunk.functionCalls ?? [];
8796
+ if (parts.length === 0 && chunkFunctionCalls.length === 0) {
7350
8797
  continue;
7351
8798
  }
7352
8799
  for (const part of parts) {
7353
8800
  modelParts.push(part);
7354
- const call = part.functionCall;
7355
- if (call) {
7356
- const id = typeof call.id === "string" ? call.id : "";
7357
- const shouldAdd = (() => {
7358
- if (id.length > 0) {
7359
- if (seenFunctionCallIds.has(id)) {
7360
- return false;
7361
- }
7362
- seenFunctionCallIds.add(id);
7363
- return true;
7364
- }
7365
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
7366
- if (seenFunctionCallKeys.has(key)) {
7367
- return false;
7368
- }
7369
- seenFunctionCallKeys.add(key);
7370
- return true;
7371
- })();
7372
- if (shouldAdd) {
7373
- functionCalls.push(call);
7374
- }
7375
- }
7376
8801
  if (typeof part.text === "string" && part.text.length > 0) {
7377
8802
  if (part.thought) {
7378
8803
  thoughtsText2 += part.text;
@@ -7385,6 +8810,15 @@ async function runToolLoop(request) {
7385
8810
  }
7386
8811
  }
7387
8812
  }
8813
+ if (chunkFunctionCalls.length > 0) {
8814
+ functionCalls.push(...chunkFunctionCalls);
8815
+ continue;
8816
+ }
8817
+ for (const part of parts) {
8818
+ if (part.functionCall) {
8819
+ functionCalls.push(part.functionCall);
8820
+ }
8821
+ }
7388
8822
  }
7389
8823
  return {
7390
8824
  responseText: responseText2,
@@ -7510,26 +8944,28 @@ async function runToolLoop(request) {
7510
8944
  input: entry.rawInput
7511
8945
  });
7512
8946
  }
7513
- const callResults = await Promise.all(
7514
- callInputs.map(async (entry) => {
7515
- return await toolCallContextStorage.run(
7516
- {
7517
- toolName: entry.toolName,
7518
- toolId: entry.toolId,
7519
- turn: entry.turn,
7520
- toolIndex: entry.toolIndex
7521
- },
7522
- async () => {
7523
- const { result, outputPayload } = await executeToolCall({
7524
- callKind: "function",
8947
+ const callResults = await maybeSpillCombinedToolCallOutputs(
8948
+ await Promise.all(
8949
+ callInputs.map(async (entry) => {
8950
+ return await toolCallContextStorage.run(
8951
+ {
7525
8952
  toolName: entry.toolName,
7526
- tool: request.tools[entry.toolName],
7527
- rawInput: entry.rawInput
7528
- });
7529
- return { entry, result, outputPayload };
7530
- }
7531
- );
7532
- })
8953
+ toolId: entry.toolId,
8954
+ turn: entry.turn,
8955
+ toolIndex: entry.toolIndex
8956
+ },
8957
+ async () => {
8958
+ const { result, outputPayload } = await executeToolCall({
8959
+ callKind: "function",
8960
+ toolName: entry.toolName,
8961
+ tool: request.tools[entry.toolName],
8962
+ rawInput: entry.rawInput
8963
+ });
8964
+ return { entry, result, outputPayload };
8965
+ }
8966
+ );
8967
+ })
8968
+ )
7533
8969
  );
7534
8970
  let toolExecutionMs = 0;
7535
8971
  let waitToolMs = 0;
@@ -7934,7 +9370,7 @@ ${lines}`;
7934
9370
 
7935
9371
  // src/agent.ts
7936
9372
  import { randomBytes as randomBytes3 } from "crypto";
7937
- import path7 from "path";
9373
+ import path9 from "path";
7938
9374
 
7939
9375
  // src/agent/subagents.ts
7940
9376
  import { randomBytes as randomBytes2 } from "crypto";
@@ -8409,26 +9845,26 @@ function resolveInputItemsText(items) {
8409
9845
  }
8410
9846
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
8411
9847
  const name = typeof item.name === "string" ? item.name.trim() : "";
8412
- const path8 = typeof item.path === "string" ? item.path.trim() : "";
9848
+ const path10 = typeof item.path === "string" ? item.path.trim() : "";
8413
9849
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
8414
9850
  if (itemType === "image") {
8415
9851
  lines.push("[image]");
8416
9852
  continue;
8417
9853
  }
8418
- if (itemType === "local_image" && path8) {
8419
- lines.push(`[local_image:${path8}]`);
9854
+ if (itemType === "local_image" && path10) {
9855
+ lines.push(`[local_image:${path10}]`);
8420
9856
  continue;
8421
9857
  }
8422
- if (itemType === "skill" && name && path8) {
8423
- lines.push(`[skill:$${name}](${path8})`);
9858
+ if (itemType === "skill" && name && path10) {
9859
+ lines.push(`[skill:$${name}](${path10})`);
8424
9860
  continue;
8425
9861
  }
8426
- if (itemType === "mention" && name && path8) {
8427
- lines.push(`[mention:$${name}](${path8})`);
9862
+ if (itemType === "mention" && name && path10) {
9863
+ lines.push(`[mention:$${name}](${path10})`);
8428
9864
  continue;
8429
9865
  }
8430
- if (path8 || imageUrl) {
8431
- lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
9866
+ if (path10 || imageUrl) {
9867
+ lines.push(`[${itemType || "input"}:${path10 || imageUrl}]`);
8432
9868
  continue;
8433
9869
  }
8434
9870
  if (name) {
@@ -8746,27 +10182,27 @@ function sleep2(ms) {
8746
10182
  }
8747
10183
 
8748
10184
  // src/tools/filesystemTools.ts
8749
- import path6 from "path";
8750
- import { Buffer as Buffer5 } from "buffer";
10185
+ import path8 from "path";
10186
+ import { Buffer as Buffer6 } from "buffer";
8751
10187
  import { z as z6 } from "zod";
8752
10188
 
8753
10189
  // src/tools/applyPatch.ts
8754
- import path5 from "path";
10190
+ import path7 from "path";
8755
10191
  import { z as z5 } from "zod";
8756
10192
 
8757
10193
  // src/tools/filesystem.ts
8758
10194
  import { promises as fs3 } from "fs";
8759
- import path4 from "path";
10195
+ import path6 from "path";
8760
10196
  var InMemoryAgentFilesystem = class {
8761
10197
  #files = /* @__PURE__ */ new Map();
8762
10198
  #dirs = /* @__PURE__ */ new Map();
8763
10199
  #clock = 0;
8764
10200
  constructor(initialFiles = {}) {
8765
- const root = path4.resolve("/");
10201
+ const root = path6.resolve("/");
8766
10202
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
8767
10203
  for (const [filePath, content] of Object.entries(initialFiles)) {
8768
- const absolutePath = path4.resolve(filePath);
8769
- this.#ensureDirSync(path4.dirname(absolutePath));
10204
+ const absolutePath = path6.resolve(filePath);
10205
+ this.#ensureDirSync(path6.dirname(absolutePath));
8770
10206
  this.#files.set(absolutePath, {
8771
10207
  content,
8772
10208
  mtimeMs: this.#nextMtime()
@@ -8774,7 +10210,7 @@ var InMemoryAgentFilesystem = class {
8774
10210
  }
8775
10211
  }
8776
10212
  async readTextFile(filePath) {
8777
- const absolutePath = path4.resolve(filePath);
10213
+ const absolutePath = path6.resolve(filePath);
8778
10214
  const file = this.#files.get(absolutePath);
8779
10215
  if (!file) {
8780
10216
  throw createNoSuchFileError("open", absolutePath);
@@ -8786,24 +10222,24 @@ var InMemoryAgentFilesystem = class {
8786
10222
  return Buffer.from(content, "utf8");
8787
10223
  }
8788
10224
  async writeTextFile(filePath, content) {
8789
- const absolutePath = path4.resolve(filePath);
8790
- const parentPath = path4.dirname(absolutePath);
10225
+ const absolutePath = path6.resolve(filePath);
10226
+ const parentPath = path6.dirname(absolutePath);
8791
10227
  if (!this.#dirs.has(parentPath)) {
8792
10228
  throw createNoSuchFileError("open", parentPath);
8793
10229
  }
8794
10230
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
8795
10231
  }
8796
10232
  async deleteFile(filePath) {
8797
- const absolutePath = path4.resolve(filePath);
10233
+ const absolutePath = path6.resolve(filePath);
8798
10234
  if (!this.#files.delete(absolutePath)) {
8799
10235
  throw createNoSuchFileError("unlink", absolutePath);
8800
10236
  }
8801
10237
  }
8802
10238
  async ensureDir(directoryPath) {
8803
- this.#ensureDirSync(path4.resolve(directoryPath));
10239
+ this.#ensureDirSync(path6.resolve(directoryPath));
8804
10240
  }
8805
10241
  async readDir(directoryPath) {
8806
- const absolutePath = path4.resolve(directoryPath);
10242
+ const absolutePath = path6.resolve(directoryPath);
8807
10243
  const directory = this.#dirs.get(absolutePath);
8808
10244
  if (!directory) {
8809
10245
  throw createNoSuchFileError("scandir", absolutePath);
@@ -8814,10 +10250,10 @@ var InMemoryAgentFilesystem = class {
8814
10250
  if (dirPath === absolutePath) {
8815
10251
  continue;
8816
10252
  }
8817
- if (path4.dirname(dirPath) !== absolutePath) {
10253
+ if (path6.dirname(dirPath) !== absolutePath) {
8818
10254
  continue;
8819
10255
  }
8820
- const name = path4.basename(dirPath);
10256
+ const name = path6.basename(dirPath);
8821
10257
  if (seenNames.has(name)) {
8822
10258
  continue;
8823
10259
  }
@@ -8830,10 +10266,10 @@ var InMemoryAgentFilesystem = class {
8830
10266
  });
8831
10267
  }
8832
10268
  for (const [filePath, fileRecord] of this.#files.entries()) {
8833
- if (path4.dirname(filePath) !== absolutePath) {
10269
+ if (path6.dirname(filePath) !== absolutePath) {
8834
10270
  continue;
8835
10271
  }
8836
- const name = path4.basename(filePath);
10272
+ const name = path6.basename(filePath);
8837
10273
  if (seenNames.has(name)) {
8838
10274
  continue;
8839
10275
  }
@@ -8849,7 +10285,7 @@ var InMemoryAgentFilesystem = class {
8849
10285
  return entries;
8850
10286
  }
8851
10287
  async stat(entryPath) {
8852
- const absolutePath = path4.resolve(entryPath);
10288
+ const absolutePath = path6.resolve(entryPath);
8853
10289
  const file = this.#files.get(absolutePath);
8854
10290
  if (file) {
8855
10291
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -8865,7 +10301,7 @@ var InMemoryAgentFilesystem = class {
8865
10301
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
8866
10302
  }
8867
10303
  #ensureDirSync(directoryPath) {
8868
- const absolutePath = path4.resolve(directoryPath);
10304
+ const absolutePath = path6.resolve(directoryPath);
8869
10305
  const parts = [];
8870
10306
  let cursor = absolutePath;
8871
10307
  for (; ; ) {
@@ -8873,7 +10309,7 @@ var InMemoryAgentFilesystem = class {
8873
10309
  break;
8874
10310
  }
8875
10311
  parts.push(cursor);
8876
- const parent = path4.dirname(cursor);
10312
+ const parent = path6.dirname(cursor);
8877
10313
  if (parent === cursor) {
8878
10314
  break;
8879
10315
  }
@@ -8907,7 +10343,7 @@ function createNodeAgentFilesystem() {
8907
10343
  const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
8908
10344
  const result = [];
8909
10345
  for (const entry of entries) {
8910
- const entryPath = path4.resolve(directoryPath, entry.name);
10346
+ const entryPath = path6.resolve(directoryPath, entry.name);
8911
10347
  const stats = await fs3.lstat(entryPath);
8912
10348
  result.push({
8913
10349
  name: entry.name,
@@ -9071,7 +10507,7 @@ function createApplyPatchTool(options = {}) {
9071
10507
  });
9072
10508
  }
9073
10509
  async function applyPatch(request) {
9074
- const cwd = path5.resolve(request.cwd ?? process.cwd());
10510
+ const cwd = path7.resolve(request.cwd ?? process.cwd());
9075
10511
  const adapter = request.fs ?? createNodeAgentFilesystem();
9076
10512
  const allowOutsideCwd = request.allowOutsideCwd === true;
9077
10513
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -9093,7 +10529,7 @@ async function applyPatch(request) {
9093
10529
  kind: "add",
9094
10530
  path: absolutePath2
9095
10531
  });
9096
- await adapter.ensureDir(path5.dirname(absolutePath2));
10532
+ await adapter.ensureDir(path7.dirname(absolutePath2));
9097
10533
  await adapter.writeTextFile(absolutePath2, operation.content);
9098
10534
  added.push(toDisplayPath(absolutePath2, cwd));
9099
10535
  continue;
@@ -9127,7 +10563,7 @@ async function applyPatch(request) {
9127
10563
  fromPath: absolutePath,
9128
10564
  toPath: destinationPath
9129
10565
  });
9130
- await adapter.ensureDir(path5.dirname(destinationPath));
10566
+ await adapter.ensureDir(path7.dirname(destinationPath));
9131
10567
  await adapter.writeTextFile(destinationPath, next);
9132
10568
  await adapter.deleteFile(absolutePath);
9133
10569
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -9158,22 +10594,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
9158
10594
  if (trimmed.length === 0) {
9159
10595
  throw new Error("apply_patch failed: empty file path");
9160
10596
  }
9161
- const absolutePath = path5.isAbsolute(trimmed) ? path5.resolve(trimmed) : path5.resolve(cwd, trimmed);
10597
+ const absolutePath = path7.isAbsolute(trimmed) ? path7.resolve(trimmed) : path7.resolve(cwd, trimmed);
9162
10598
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
9163
10599
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
9164
10600
  }
9165
10601
  return absolutePath;
9166
10602
  }
9167
10603
  function isPathInsideCwd(candidatePath, cwd) {
9168
- const relative = path5.relative(cwd, candidatePath);
9169
- return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
10604
+ const relative = path7.relative(cwd, candidatePath);
10605
+ return relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative);
9170
10606
  }
9171
10607
  function toDisplayPath(absolutePath, cwd) {
9172
- const relative = path5.relative(cwd, absolutePath);
10608
+ const relative = path7.relative(cwd, absolutePath);
9173
10609
  if (relative === "") {
9174
10610
  return ".";
9175
10611
  }
9176
- if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
10612
+ if (!relative.startsWith("..") && !path7.isAbsolute(relative)) {
9177
10613
  return relative;
9178
10614
  }
9179
10615
  return absolutePath;
@@ -9940,7 +11376,7 @@ async function readBinaryFile(filesystem, filePath) {
9940
11376
  return await filesystem.readBinaryFile(filePath);
9941
11377
  }
9942
11378
  const text = await filesystem.readTextFile(filePath);
9943
- return Buffer5.from(text, "utf8");
11379
+ return Buffer6.from(text, "utf8");
9944
11380
  }
9945
11381
  function detectImageMimeType(buffer, filePath) {
9946
11382
  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) {
@@ -9958,7 +11394,7 @@ function detectImageMimeType(buffer, filePath) {
9958
11394
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
9959
11395
  return "image/webp";
9960
11396
  }
9961
- const fromExtension = IMAGE_MIME_BY_EXTENSION[path6.extname(filePath).toLowerCase()];
11397
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[path8.extname(filePath).toLowerCase()];
9962
11398
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
9963
11399
  return fromExtension;
9964
11400
  }
@@ -9968,13 +11404,13 @@ function isPdfFile(buffer, filePath) {
9968
11404
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
9969
11405
  return true;
9970
11406
  }
9971
- return path6.extname(filePath).toLowerCase() === ".pdf";
11407
+ return path8.extname(filePath).toLowerCase() === ".pdf";
9972
11408
  }
9973
11409
  function isValidUtf8(buffer) {
9974
11410
  if (buffer.length === 0) {
9975
11411
  return true;
9976
11412
  }
9977
- return Buffer5.from(buffer.toString("utf8"), "utf8").equals(buffer);
11413
+ return Buffer6.from(buffer.toString("utf8"), "utf8").equals(buffer);
9978
11414
  }
9979
11415
  async function readFileGemini(input, options) {
9980
11416
  const runtime = resolveRuntime(options);
@@ -10006,7 +11442,7 @@ async function writeFileGemini(input, options) {
10006
11442
  action: "write",
10007
11443
  path: filePath
10008
11444
  });
10009
- await runtime.filesystem.ensureDir(path6.dirname(filePath));
11445
+ await runtime.filesystem.ensureDir(path8.dirname(filePath));
10010
11446
  await runtime.filesystem.writeTextFile(filePath, input.content);
10011
11447
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
10012
11448
  }
@@ -10027,7 +11463,7 @@ async function replaceFileContentGemini(input, options) {
10027
11463
  originalContent = await runtime.filesystem.readTextFile(filePath);
10028
11464
  } catch (error) {
10029
11465
  if (isNoEntError(error) && oldValue.length === 0) {
10030
- await runtime.filesystem.ensureDir(path6.dirname(filePath));
11466
+ await runtime.filesystem.ensureDir(path8.dirname(filePath));
10031
11467
  await runtime.filesystem.writeTextFile(filePath, newValue);
10032
11468
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
10033
11469
  }
@@ -10197,15 +11633,15 @@ async function globFilesGemini(input, options) {
10197
11633
  throw new Error(`Path is not a directory: ${dirPath}`);
10198
11634
  }
10199
11635
  const matcher = createGlobMatcher(input.pattern, input.case_sensitive === true);
10200
- const files = await collectSearchFiles({
11636
+ const files2 = await collectSearchFiles({
10201
11637
  filesystem: runtime.filesystem,
10202
11638
  searchPath: dirPath,
10203
11639
  rootKind: "directory",
10204
11640
  maxScannedFiles: runtime.grepMaxScannedFiles
10205
11641
  });
10206
11642
  const matched = [];
10207
- for (const filePath of files) {
10208
- const relativePath = normalizeSlashes(path6.relative(dirPath, filePath));
11643
+ for (const filePath of files2) {
11644
+ const relativePath = normalizeSlashes(path8.relative(dirPath, filePath));
10209
11645
  if (!matcher(relativePath)) {
10210
11646
  continue;
10211
11647
  }
@@ -10223,7 +11659,7 @@ async function globFilesGemini(input, options) {
10223
11659
  }
10224
11660
  function resolveRuntime(options) {
10225
11661
  return {
10226
- cwd: path6.resolve(options.cwd ?? process.cwd()),
11662
+ cwd: path8.resolve(options.cwd ?? process.cwd()),
10227
11663
  filesystem: options.fs ?? createNodeAgentFilesystem(),
10228
11664
  allowOutsideCwd: options.allowOutsideCwd === true,
10229
11665
  checkAccess: options.checkAccess,
@@ -10254,13 +11690,13 @@ function mapApplyPatchAction(action) {
10254
11690
  return "move";
10255
11691
  }
10256
11692
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
10257
- const absolutePath = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(cwd, inputPath);
11693
+ const absolutePath = path8.isAbsolute(inputPath) ? path8.resolve(inputPath) : path8.resolve(cwd, inputPath);
10258
11694
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
10259
11695
  return absolutePath;
10260
11696
  }
10261
- if (path6.isAbsolute(inputPath)) {
11697
+ if (path8.isAbsolute(inputPath)) {
10262
11698
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
10263
- const sandboxRootedPath = path6.resolve(cwd, sandboxRelativePath);
11699
+ const sandboxRootedPath = path8.resolve(cwd, sandboxRelativePath);
10264
11700
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
10265
11701
  return sandboxRootedPath;
10266
11702
  }
@@ -10268,25 +11704,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
10268
11704
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
10269
11705
  }
10270
11706
  function isPathInsideCwd2(candidatePath, cwd) {
10271
- const relative = path6.relative(cwd, candidatePath);
10272
- return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
11707
+ const relative = path8.relative(cwd, candidatePath);
11708
+ return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
10273
11709
  }
10274
11710
  function toDisplayPath2(absolutePath, cwd) {
10275
- const relative = path6.relative(cwd, absolutePath);
11711
+ const relative = path8.relative(cwd, absolutePath);
10276
11712
  if (relative === "") {
10277
11713
  return ".";
10278
11714
  }
10279
- if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
11715
+ if (!relative.startsWith("..") && !path8.isAbsolute(relative)) {
10280
11716
  return relative;
10281
11717
  }
10282
11718
  return absolutePath;
10283
11719
  }
10284
11720
  function toSandboxDisplayPath(absolutePath, cwd) {
10285
- const relative = path6.relative(cwd, absolutePath);
11721
+ const relative = path8.relative(cwd, absolutePath);
10286
11722
  if (relative === "") {
10287
11723
  return "/";
10288
11724
  }
10289
- if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
11725
+ if (!relative.startsWith("..") && !path8.isAbsolute(relative)) {
10290
11726
  return `/${normalizeSlashes(relative)}`;
10291
11727
  }
10292
11728
  return normalizeSlashes(absolutePath);
@@ -10362,7 +11798,7 @@ async function collectSearchFiles(params) {
10362
11798
  return [searchPath];
10363
11799
  }
10364
11800
  const queue = [searchPath];
10365
- const files = [];
11801
+ const files2 = [];
10366
11802
  while (queue.length > 0) {
10367
11803
  const current = queue.shift();
10368
11804
  if (!current) {
@@ -10377,13 +11813,13 @@ async function collectSearchFiles(params) {
10377
11813
  if (entry.kind !== "file") {
10378
11814
  continue;
10379
11815
  }
10380
- files.push(entry.path);
10381
- if (files.length >= maxScannedFiles) {
10382
- return files;
11816
+ files2.push(entry.path);
11817
+ if (files2.length >= maxScannedFiles) {
11818
+ return files2;
10383
11819
  }
10384
11820
  }
10385
11821
  }
10386
- return files;
11822
+ return files2;
10387
11823
  }
10388
11824
  function compileRegex(pattern, flags = "m") {
10389
11825
  try {
@@ -10402,7 +11838,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
10402
11838
  }));
10403
11839
  return (candidatePath) => {
10404
11840
  const normalizedPath = normalizeSlashes(candidatePath);
10405
- const basename = path6.posix.basename(normalizedPath);
11841
+ const basename = path8.posix.basename(normalizedPath);
10406
11842
  return compiled.some(
10407
11843
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
10408
11844
  );
@@ -10679,13 +12115,24 @@ async function runAgentLoopInternal(request, context) {
10679
12115
  }
10680
12116
  streamEventLogger?.appendEvent(event);
10681
12117
  } : void 0;
12118
+ let uploadMetrics = emptyFileUploadMetrics();
10682
12119
  try {
10683
- const result = await runToolLoop({
10684
- ...toolLoopRequestWithSteering,
10685
- ...instructions ? { instructions } : {},
10686
- ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
10687
- tools: mergedTools
12120
+ let result;
12121
+ await collectFileUploadMetrics(async () => {
12122
+ try {
12123
+ result = await runToolLoop({
12124
+ ...toolLoopRequestWithSteering,
12125
+ ...instructions ? { instructions } : {},
12126
+ ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
12127
+ tools: mergedTools
12128
+ });
12129
+ } finally {
12130
+ uploadMetrics = getCurrentFileUploadMetrics();
12131
+ }
10688
12132
  });
12133
+ if (!result) {
12134
+ throw new Error("runToolLoop returned no result.");
12135
+ }
10689
12136
  streamEventLogger?.flush();
10690
12137
  emitTelemetry({
10691
12138
  type: "agent.run.completed",
@@ -10694,7 +12141,10 @@ async function runAgentLoopInternal(request, context) {
10694
12141
  stepCount: result.steps.length,
10695
12142
  toolCallCount: countToolCalls(result),
10696
12143
  totalCostUsd: result.totalCostUsd,
10697
- usage: summarizeResultUsage(result)
12144
+ usage: summarizeResultUsage(result),
12145
+ uploadCount: uploadMetrics.count,
12146
+ uploadBytes: uploadMetrics.totalBytes,
12147
+ uploadLatencyMs: uploadMetrics.totalLatencyMs
10698
12148
  });
10699
12149
  loggingSession?.logLine(
10700
12150
  [
@@ -10703,7 +12153,10 @@ async function runAgentLoopInternal(request, context) {
10703
12153
  `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10704
12154
  `steps=${result.steps.length.toString()}`,
10705
12155
  `toolCalls=${countToolCalls(result).toString()}`,
10706
- `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
12156
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`,
12157
+ `uploadCount=${uploadMetrics.count.toString()}`,
12158
+ `uploadBytes=${uploadMetrics.totalBytes.toString()}`,
12159
+ `uploadLatencyMs=${uploadMetrics.totalLatencyMs.toString()}`
10707
12160
  ].join(" ")
10708
12161
  );
10709
12162
  for (const step of result.steps) {
@@ -10724,6 +12177,9 @@ async function runAgentLoopInternal(request, context) {
10724
12177
  type: "agent.run.completed",
10725
12178
  success: false,
10726
12179
  durationMs: Math.max(0, Date.now() - startedAtMs),
12180
+ uploadCount: uploadMetrics.count,
12181
+ uploadBytes: uploadMetrics.totalBytes,
12182
+ uploadLatencyMs: uploadMetrics.totalLatencyMs,
10727
12183
  error: toErrorMessage3(error)
10728
12184
  });
10729
12185
  loggingSession?.logLine(
@@ -10731,6 +12187,9 @@ async function runAgentLoopInternal(request, context) {
10731
12187
  `[agent:${runId}] run_completed`,
10732
12188
  `status=error`,
10733
12189
  `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
12190
+ `uploadCount=${uploadMetrics.count.toString()}`,
12191
+ `uploadBytes=${uploadMetrics.totalBytes.toString()}`,
12192
+ `uploadLatencyMs=${uploadMetrics.totalLatencyMs.toString()}`,
10734
12193
  `error=${toErrorMessage3(error)}`
10735
12194
  ].join(" ")
10736
12195
  );
@@ -10937,7 +12396,7 @@ function resolveWorkspaceDirForLogging(request) {
10937
12396
  if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
10938
12397
  const cwd = explicitSelection.options?.cwd;
10939
12398
  if (typeof cwd === "string" && cwd.trim().length > 0) {
10940
- return path7.resolve(cwd);
12399
+ return path9.resolve(cwd);
10941
12400
  }
10942
12401
  }
10943
12402
  return process.cwd();
@@ -10947,7 +12406,7 @@ function createRootAgentLoggingSession(request) {
10947
12406
  if (!selected) {
10948
12407
  return void 0;
10949
12408
  }
10950
- const workspaceDir = typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? path7.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request);
12409
+ const workspaceDir = typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? path9.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request);
10951
12410
  return createAgentLoggingSession({
10952
12411
  ...selected,
10953
12412
  workspaceDir,
@@ -11681,6 +13140,7 @@ export {
11681
13140
  CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
11682
13141
  CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
11683
13142
  CODEX_APPLY_PATCH_LARK_GRAMMAR,
13143
+ DEFAULT_FILE_TTL_SECONDS,
11684
13144
  FIREWORKS_DEFAULT_GLM_MODEL,
11685
13145
  FIREWORKS_DEFAULT_GPT_OSS_120B_MODEL,
11686
13146
  FIREWORKS_DEFAULT_KIMI_MODEL,
@@ -11721,10 +13181,12 @@ export {
11721
13181
  createViewImageTool,
11722
13182
  createWriteFileTool,
11723
13183
  customTool,
13184
+ emptyFileUploadMetrics,
11724
13185
  encodeChatGptAuthJson,
11725
13186
  encodeChatGptAuthJsonB64,
11726
13187
  estimateCallCostUsd,
11727
13188
  exchangeChatGptOauthCode,
13189
+ files,
11728
13190
  generateImageInBatches,
11729
13191
  generateImages,
11730
13192
  generateJson,