@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/README.md +127 -4
- package/dist/index.cjs +2039 -574
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -13
- package/dist/index.d.ts +85 -13
- package/dist/index.js +1999 -537
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/llm.ts
|
|
2
|
-
import { Buffer as
|
|
3
|
-
import { AsyncLocalStorage as
|
|
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
|
|
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 =
|
|
4293
|
+
omittedBytes = Buffer5.from(part.data, "base64").byteLength;
|
|
3541
4294
|
} catch {
|
|
3542
|
-
omittedBytes =
|
|
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
|
-
|
|
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
|
-
|
|
3611
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
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
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
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
|
-
|
|
4246
|
-
|
|
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
|
-
|
|
4249
|
-
|
|
5411
|
+
break;
|
|
5412
|
+
case "input_file":
|
|
4250
5413
|
parts.push({
|
|
4251
5414
|
type: "input_file",
|
|
4252
|
-
|
|
4253
|
-
file_data:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
4748
|
-
messages.push(`${
|
|
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
|
|
4867
|
-
|
|
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
|
|
4902
|
-
|
|
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
|
|
6552
|
+
return Buffer5.from(base64, "base64");
|
|
5135
6553
|
} catch {
|
|
5136
|
-
return
|
|
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 ?
|
|
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:
|
|
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
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
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
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
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
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
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
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
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 (
|
|
5682
|
-
|
|
5683
|
-
|
|
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
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
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
|
-
}
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
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
|
-
{
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
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
|
-
|
|
5738
|
-
|
|
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 =
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
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
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
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
|
-
|
|
5782
|
-
|
|
5783
|
-
extractGeminiUsageTokens(chunk.usageMetadata)
|
|
7166
|
+
const textOutput = extractFireworksMessageText(
|
|
7167
|
+
choice?.message
|
|
5784
7168
|
);
|
|
5785
|
-
|
|
5786
|
-
|
|
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
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
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
|
-
|
|
5800
|
-
|
|
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
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
7208
|
+
if (chunk.promptFeedback?.blockReason) {
|
|
7209
|
+
blocked = true;
|
|
7210
|
+
queue.push({ type: "blocked" });
|
|
5805
7211
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
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
|
-
|
|
5816
|
-
|
|
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
|
-
|
|
5873
|
-
|
|
5874
|
-
}
|
|
5875
|
-
})
|
|
5876
|
-
|
|
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({
|
|
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
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
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
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
}
|
|
6572
|
-
|
|
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
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
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
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
}
|
|
6873
|
-
|
|
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
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
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
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
}
|
|
7165
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
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
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
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
|
|
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
|
|
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" &&
|
|
8419
|
-
lines.push(`[local_image:${
|
|
9854
|
+
if (itemType === "local_image" && path10) {
|
|
9855
|
+
lines.push(`[local_image:${path10}]`);
|
|
8420
9856
|
continue;
|
|
8421
9857
|
}
|
|
8422
|
-
if (itemType === "skill" && name &&
|
|
8423
|
-
lines.push(`[skill:$${name}](${
|
|
9858
|
+
if (itemType === "skill" && name && path10) {
|
|
9859
|
+
lines.push(`[skill:$${name}](${path10})`);
|
|
8424
9860
|
continue;
|
|
8425
9861
|
}
|
|
8426
|
-
if (itemType === "mention" && name &&
|
|
8427
|
-
lines.push(`[mention:$${name}](${
|
|
9862
|
+
if (itemType === "mention" && name && path10) {
|
|
9863
|
+
lines.push(`[mention:$${name}](${path10})`);
|
|
8428
9864
|
continue;
|
|
8429
9865
|
}
|
|
8430
|
-
if (
|
|
8431
|
-
lines.push(`[${itemType || "input"}:${
|
|
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
|
|
8750
|
-
import { Buffer as
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
8769
|
-
this.#ensureDirSync(
|
|
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 =
|
|
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 =
|
|
8790
|
-
const parentPath =
|
|
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 =
|
|
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(
|
|
10239
|
+
this.#ensureDirSync(path6.resolve(directoryPath));
|
|
8804
10240
|
}
|
|
8805
10241
|
async readDir(directoryPath) {
|
|
8806
|
-
const absolutePath =
|
|
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 (
|
|
10253
|
+
if (path6.dirname(dirPath) !== absolutePath) {
|
|
8818
10254
|
continue;
|
|
8819
10255
|
}
|
|
8820
|
-
const name =
|
|
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 (
|
|
10269
|
+
if (path6.dirname(filePath) !== absolutePath) {
|
|
8834
10270
|
continue;
|
|
8835
10271
|
}
|
|
8836
|
-
const name =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
9169
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
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 =
|
|
10608
|
+
const relative = path7.relative(cwd, absolutePath);
|
|
9173
10609
|
if (relative === "") {
|
|
9174
10610
|
return ".";
|
|
9175
10611
|
}
|
|
9176
|
-
if (!relative.startsWith("..") && !
|
|
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
|
|
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[
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
10208
|
-
const relativePath = normalizeSlashes(
|
|
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:
|
|
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 =
|
|
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 (
|
|
11697
|
+
if (path8.isAbsolute(inputPath)) {
|
|
10262
11698
|
const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
|
|
10263
|
-
const sandboxRootedPath =
|
|
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 =
|
|
10272
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
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 =
|
|
11711
|
+
const relative = path8.relative(cwd, absolutePath);
|
|
10276
11712
|
if (relative === "") {
|
|
10277
11713
|
return ".";
|
|
10278
11714
|
}
|
|
10279
|
-
if (!relative.startsWith("..") && !
|
|
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 =
|
|
11721
|
+
const relative = path8.relative(cwd, absolutePath);
|
|
10286
11722
|
if (relative === "") {
|
|
10287
11723
|
return "/";
|
|
10288
11724
|
}
|
|
10289
|
-
if (!relative.startsWith("..") && !
|
|
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
|
|
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
|
-
|
|
10381
|
-
if (
|
|
10382
|
-
return
|
|
11816
|
+
files2.push(entry.path);
|
|
11817
|
+
if (files2.length >= maxScannedFiles) {
|
|
11818
|
+
return files2;
|
|
10383
11819
|
}
|
|
10384
11820
|
}
|
|
10385
11821
|
}
|
|
10386
|
-
return
|
|
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 =
|
|
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
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
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
|
|
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 ?
|
|
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,
|