@chrischall/gemini-mcp 1.4.0 → 1.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +64 -1
- package/dist/bundle.js +330 -27
- package/dist/client.js +6 -0
- package/dist/inputs.js +37 -1
- package/dist/jobs.js +46 -2
- package/dist/library.js +151 -0
- package/dist/media-cleanup.js +4 -1
- package/dist/media-endpoint.js +6 -0
- package/dist/media-url.js +2 -2
- package/dist/put-endpoint.js +122 -0
- package/dist/storage/media.js +23 -12
- package/dist/tools/generate.js +34 -10
- package/dist/tools/interact.js +3 -2
- package/dist/tools/library.js +186 -0
- package/dist/tools/music.js +3 -2
- package/dist/tools/set.js +37 -9
- package/dist/tools/shared.js +195 -2
- package/dist/tools/uploads.js +69 -0
- package/dist/tools/video.js +3 -2
- package/dist/upload-url.js +138 -0
- package/dist/version.js +1 -1
- package/dist/zip.js +113 -0
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -32805,7 +32805,7 @@ async function loadStdioDotenv() {
|
|
|
32805
32805
|
}
|
|
32806
32806
|
|
|
32807
32807
|
// src/version.ts
|
|
32808
|
-
var VERSION = "1.
|
|
32808
|
+
var VERSION = "1.5.0";
|
|
32809
32809
|
|
|
32810
32810
|
// src/tools/models.ts
|
|
32811
32811
|
init_dist();
|
|
@@ -33102,7 +33102,7 @@ async function fetchRemoteImage(requestedUrl, opts = {}) {
|
|
|
33102
33102
|
// src/inputs.ts
|
|
33103
33103
|
function hasImageInput(args) {
|
|
33104
33104
|
return Boolean(
|
|
33105
|
-
args.images?.length || args.images_base64?.length || args.images_url?.length || args.images_file_uris?.length || args.from_clipboard
|
|
33105
|
+
args.images?.length || args.images_base64?.length || args.images_url?.length || args.images_file_uris?.length || args.images_r2_keys?.length || args.from_clipboard
|
|
33106
33106
|
);
|
|
33107
33107
|
}
|
|
33108
33108
|
var ASSUMED_TTL_MS = 47 * 60 * 60 * 1e3;
|
|
@@ -33151,6 +33151,32 @@ async function resolveImageInputs(args, client2) {
|
|
|
33151
33151
|
(report.file_uris ??= []).push(file2.name);
|
|
33152
33152
|
inputs.push(input);
|
|
33153
33153
|
}
|
|
33154
|
+
const seenKeys = /* @__PURE__ */ new Map();
|
|
33155
|
+
for (const key of args.images_r2_keys ?? []) {
|
|
33156
|
+
const hit = seenKeys.get(key);
|
|
33157
|
+
if (hit) {
|
|
33158
|
+
inputs.push(hit);
|
|
33159
|
+
continue;
|
|
33160
|
+
}
|
|
33161
|
+
const stored = await client2.readStoredMedia(key);
|
|
33162
|
+
const entry = {
|
|
33163
|
+
r2_key: key.trim(),
|
|
33164
|
+
mime_type: stored.mimeType,
|
|
33165
|
+
bytes: stored.bytes.byteLength
|
|
33166
|
+
};
|
|
33167
|
+
(report.r2_keys ??= []).push(entry);
|
|
33168
|
+
let input;
|
|
33169
|
+
if (stored.bytes.byteLength <= IMAGE_INLINE_MAX_BYTES) {
|
|
33170
|
+
input = { base64: bytesToBase64(stored.bytes), mimeType: stored.mimeType };
|
|
33171
|
+
} else {
|
|
33172
|
+
const displayName = key.trim().split("/").pop() || "image";
|
|
33173
|
+
const uploaded = await client2.uploadBytes(stored.bytes, stored.mimeType, displayName);
|
|
33174
|
+
entry.file_uri = uploaded.name;
|
|
33175
|
+
input = { uri: uploaded.uri, mimeType: uploaded.mimeType };
|
|
33176
|
+
}
|
|
33177
|
+
seenKeys.set(key, input);
|
|
33178
|
+
inputs.push(input);
|
|
33179
|
+
}
|
|
33154
33180
|
return { inputs, report: Object.keys(report).length > 0 ? report : void 0 };
|
|
33155
33181
|
}
|
|
33156
33182
|
async function resolveLocalPath(path, client2, onDisk, report) {
|
|
@@ -33205,7 +33231,7 @@ async function resolveUrl(url2, client2, report) {
|
|
|
33205
33231
|
function requireImageInput(args) {
|
|
33206
33232
|
if (hasImageInput(args)) return;
|
|
33207
33233
|
throw new McpToolError(
|
|
33208
|
-
"Provide at least one input image via `images_url` (an https URL the server fetches), `images_file_uris` (from gemini_upload_file), `images` (a local path), `images_base64`, or `from_clipboard`."
|
|
33234
|
+
"Provide at least one input image via `images_url` (an https URL the server fetches), `images_file_uris` (from gemini_upload_file), `images_r2_keys` (a hosted-connector upload or earlier generation), `images` (a local path), `images_base64`, or `from_clipboard`."
|
|
33209
33235
|
);
|
|
33210
33236
|
}
|
|
33211
33237
|
|
|
@@ -33238,6 +33264,96 @@ function downloadFilename(key) {
|
|
|
33238
33264
|
return stripped || last;
|
|
33239
33265
|
}
|
|
33240
33266
|
|
|
33267
|
+
// src/zip.ts
|
|
33268
|
+
var CRC_TABLE = (() => {
|
|
33269
|
+
const table = new Uint32Array(256);
|
|
33270
|
+
for (let n = 0; n < 256; n++) {
|
|
33271
|
+
let c = n;
|
|
33272
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
33273
|
+
table[n] = c >>> 0;
|
|
33274
|
+
}
|
|
33275
|
+
return table;
|
|
33276
|
+
})();
|
|
33277
|
+
function crc32(bytes) {
|
|
33278
|
+
let crc = 4294967295;
|
|
33279
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
33280
|
+
crc = CRC_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
33281
|
+
}
|
|
33282
|
+
return (crc ^ 4294967295) >>> 0;
|
|
33283
|
+
}
|
|
33284
|
+
function dosDateTime(at) {
|
|
33285
|
+
const year = Math.max(1980, at.getUTCFullYear());
|
|
33286
|
+
return {
|
|
33287
|
+
time: at.getUTCHours() << 11 | at.getUTCMinutes() << 5 | at.getUTCSeconds() >> 1,
|
|
33288
|
+
date: year - 1980 << 9 | at.getUTCMonth() + 1 << 5 | at.getUTCDate()
|
|
33289
|
+
};
|
|
33290
|
+
}
|
|
33291
|
+
var LOCAL_HEADER = 67324752;
|
|
33292
|
+
var CENTRAL_HEADER = 33639248;
|
|
33293
|
+
var EOCD = 101010256;
|
|
33294
|
+
var VERSION2 = 20;
|
|
33295
|
+
var FLAG_UTF8 = 2048;
|
|
33296
|
+
function buildZip(entries, modifiedAt = new Date(Date.UTC(1980, 0, 1))) {
|
|
33297
|
+
const { time: time3, date: date5 } = dosDateTime(modifiedAt);
|
|
33298
|
+
const encoder = new TextEncoder();
|
|
33299
|
+
const parts = [];
|
|
33300
|
+
const central = [];
|
|
33301
|
+
let offset = 0;
|
|
33302
|
+
for (const entry of entries) {
|
|
33303
|
+
const name = encoder.encode(entry.name);
|
|
33304
|
+
const crc = crc32(entry.bytes);
|
|
33305
|
+
const size = entry.bytes.byteLength;
|
|
33306
|
+
const local = new Uint8Array(30 + name.byteLength);
|
|
33307
|
+
const lv = new DataView(local.buffer);
|
|
33308
|
+
lv.setUint32(0, LOCAL_HEADER, true);
|
|
33309
|
+
lv.setUint16(4, VERSION2, true);
|
|
33310
|
+
lv.setUint16(6, FLAG_UTF8, true);
|
|
33311
|
+
lv.setUint16(8, 0, true);
|
|
33312
|
+
lv.setUint16(10, time3, true);
|
|
33313
|
+
lv.setUint16(12, date5, true);
|
|
33314
|
+
lv.setUint32(14, crc, true);
|
|
33315
|
+
lv.setUint32(18, size, true);
|
|
33316
|
+
lv.setUint32(22, size, true);
|
|
33317
|
+
lv.setUint16(26, name.byteLength, true);
|
|
33318
|
+
lv.setUint16(28, 0, true);
|
|
33319
|
+
local.set(name, 30);
|
|
33320
|
+
const dir = new Uint8Array(46 + name.byteLength);
|
|
33321
|
+
const dv = new DataView(dir.buffer);
|
|
33322
|
+
dv.setUint32(0, CENTRAL_HEADER, true);
|
|
33323
|
+
dv.setUint16(4, VERSION2, true);
|
|
33324
|
+
dv.setUint16(6, VERSION2, true);
|
|
33325
|
+
dv.setUint16(8, FLAG_UTF8, true);
|
|
33326
|
+
dv.setUint16(10, 0, true);
|
|
33327
|
+
dv.setUint16(12, time3, true);
|
|
33328
|
+
dv.setUint16(14, date5, true);
|
|
33329
|
+
dv.setUint32(16, crc, true);
|
|
33330
|
+
dv.setUint32(20, size, true);
|
|
33331
|
+
dv.setUint32(24, size, true);
|
|
33332
|
+
dv.setUint16(28, name.byteLength, true);
|
|
33333
|
+
dv.setUint32(42, offset, true);
|
|
33334
|
+
dir.set(name, 46);
|
|
33335
|
+
parts.push(local, entry.bytes);
|
|
33336
|
+
central.push(dir);
|
|
33337
|
+
offset += local.byteLength + size;
|
|
33338
|
+
}
|
|
33339
|
+
const centralSize = central.reduce((n, c) => n + c.byteLength, 0);
|
|
33340
|
+
const eocd = new Uint8Array(22);
|
|
33341
|
+
const ev = new DataView(eocd.buffer);
|
|
33342
|
+
ev.setUint32(0, EOCD, true);
|
|
33343
|
+
ev.setUint16(8, entries.length, true);
|
|
33344
|
+
ev.setUint16(10, entries.length, true);
|
|
33345
|
+
ev.setUint32(12, centralSize, true);
|
|
33346
|
+
ev.setUint32(16, offset, true);
|
|
33347
|
+
const total = offset + centralSize + eocd.byteLength;
|
|
33348
|
+
const out = new Uint8Array(total);
|
|
33349
|
+
let cursor = 0;
|
|
33350
|
+
for (const part of [...parts, ...central, eocd]) {
|
|
33351
|
+
out.set(part, cursor);
|
|
33352
|
+
cursor += part.byteLength;
|
|
33353
|
+
}
|
|
33354
|
+
return out;
|
|
33355
|
+
}
|
|
33356
|
+
|
|
33241
33357
|
// src/tools/shared.ts
|
|
33242
33358
|
var DEFAULT_SINK = createDiskSink();
|
|
33243
33359
|
var ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "1:4", "4:1", "1:8", "8:1"];
|
|
@@ -33252,6 +33368,77 @@ var idempotencyKeySchema = external_exports.string().min(1).optional().describe(
|
|
|
33252
33368
|
var asyncSchema = external_exports.boolean().optional().describe(
|
|
33253
33369
|
"Run in the background and return a job_id immediately instead of the image, so a long (Pro/4K) generation cannot hit the host tools/call timeout (-32001). Poll gemini_get_result with the job_id to fetch the result (jobs are per-process and expire ~10 min after completion)."
|
|
33254
33370
|
);
|
|
33371
|
+
var maxWaitMsSchema = external_exports.number().int().positive().max(6e5).optional().describe(
|
|
33372
|
+
'Wait up to this many ms for the result; if generation is still running when the budget expires, return { job_id, status: "running" } immediately instead (poll gemini_get_result). Keeps fast results in-band while a slow batch can never trip the host tools/call timeout (-32001) \u2014 e.g. 20000 for multi-image sets. Ignored when async is set.'
|
|
33373
|
+
);
|
|
33374
|
+
function imagesR2KeysSchema(label = "Reference images") {
|
|
33375
|
+
return external_exports.array(external_exports.string().min(1)).optional().describe(
|
|
33376
|
+
`${label} by r2_key from THIS connector's store: a signed upload (gemini_get_upload_url \u2192 curl PUT) or an earlier generation's media[].r2_key. The server reads its own bucket directly \u2014 no bytes in the conversation, no signed URL, no ~48h Files API expiry. Hosted connector only.`
|
|
33377
|
+
);
|
|
33378
|
+
}
|
|
33379
|
+
var charactersSchema = external_exports.array(external_exports.string().min(1)).max(8).optional().describe(
|
|
33380
|
+
"Names of saved characters (see gemini_list_characters / gemini_save_character): each one's reference image and description are attached to the request automatically, keeping recurring subjects consistent without re-sending anything. Hosted connector only."
|
|
33381
|
+
);
|
|
33382
|
+
var styleSchema = external_exports.string().min(1).optional().describe(
|
|
33383
|
+
"Name of a saved style preset (see gemini_list_styles / gemini_save_style): its prompt fragment \u2014 and reference image, if it has one \u2014 is applied to the request automatically. Hosted connector only."
|
|
33384
|
+
);
|
|
33385
|
+
async function resolveCharacterRefs(client2, names, styleName) {
|
|
33386
|
+
if (!names?.length && !styleName) return { inputs: [] };
|
|
33387
|
+
const library = client2.library;
|
|
33388
|
+
if (!library) {
|
|
33389
|
+
throw new McpToolError(
|
|
33390
|
+
"`characters` / `style` reference this account's saved library, which exists on the hosted connector only. On this server pass the reference images directly (images / images_url / images_file_uris) and put the style text in the prompt."
|
|
33391
|
+
);
|
|
33392
|
+
}
|
|
33393
|
+
const inputs = [];
|
|
33394
|
+
const described = [];
|
|
33395
|
+
const toInput = async (image, displayName) => {
|
|
33396
|
+
if (image.bytes.byteLength <= IMAGE_INLINE_MAX_BYTES) {
|
|
33397
|
+
return { base64: bytesToBase64(image.bytes), mimeType: image.mimeType };
|
|
33398
|
+
}
|
|
33399
|
+
const uploaded = await client2.uploadBytes(image.bytes, image.mimeType, displayName);
|
|
33400
|
+
return { uri: uploaded.uri, mimeType: uploaded.mimeType };
|
|
33401
|
+
};
|
|
33402
|
+
for (const name of names ?? []) {
|
|
33403
|
+
const record2 = await library.getCharacter(name);
|
|
33404
|
+
const image = record2 && await library.readCharacterImage(name);
|
|
33405
|
+
if (!record2 || !image) {
|
|
33406
|
+
const available = (await library.listCharacters()).map((c) => c.name);
|
|
33407
|
+
throw new McpToolError(
|
|
33408
|
+
`No saved character "${name}". ` + (available.length ? `Saved characters: ${available.join(", ")}. ` : "None are saved yet. ") + "Save one with gemini_save_character."
|
|
33409
|
+
);
|
|
33410
|
+
}
|
|
33411
|
+
inputs.push(await toInput(image, name));
|
|
33412
|
+
described.push(`${record2.name} \u2014 ${record2.description}`);
|
|
33413
|
+
}
|
|
33414
|
+
let styleFragment;
|
|
33415
|
+
let styleImageAttached = false;
|
|
33416
|
+
if (styleName) {
|
|
33417
|
+
const record2 = await library.getStyle(styleName);
|
|
33418
|
+
if (!record2) {
|
|
33419
|
+
const available = (await library.listStyles()).map((s) => s.name);
|
|
33420
|
+
throw new McpToolError(
|
|
33421
|
+
`No saved style "${styleName}". ` + (available.length ? `Saved styles: ${available.join(", ")}. ` : "None are saved yet. ") + "Save one with gemini_save_style."
|
|
33422
|
+
);
|
|
33423
|
+
}
|
|
33424
|
+
styleFragment = record2.prompt_fragment;
|
|
33425
|
+
if (record2.image_key) {
|
|
33426
|
+
const image = await library.readStyleImage(styleName);
|
|
33427
|
+
if (image) {
|
|
33428
|
+
inputs.push(await toInput(image, styleName));
|
|
33429
|
+
styleImageAttached = true;
|
|
33430
|
+
}
|
|
33431
|
+
}
|
|
33432
|
+
}
|
|
33433
|
+
const characterLine = described.length ? `Use the attached reference images for these characters, in order: ${described.map((d, i) => `(${i + 1}) ${d}`).join("; ")}. Keep each character visually consistent with their reference image.` + (styleImageAttached ? ` Attached image (${described.length + 1}) is a style reference, not a character.` : "") : styleImageAttached ? "The first attached image is a style reference." : void 0;
|
|
33434
|
+
const meta3 = {};
|
|
33435
|
+
if (names?.length) meta3.characters = names;
|
|
33436
|
+
if (styleName) meta3.style = styleName;
|
|
33437
|
+
return { inputs, characterLine, styleFragment, meta: Object.keys(meta3).length ? meta3 : void 0 };
|
|
33438
|
+
}
|
|
33439
|
+
function composePrompt(prompt, refs) {
|
|
33440
|
+
return [refs.characterLine, prompt, refs.styleFragment ? `Style: ${refs.styleFragment}` : void 0].filter(Boolean).join("\n\n");
|
|
33441
|
+
}
|
|
33255
33442
|
function imagesUrlSchema(label = "Reference images") {
|
|
33256
33443
|
return external_exports.array(external_exports.string().url()).optional().describe(
|
|
33257
33444
|
`${label} as public https URLs \u2014 the SERVER downloads them, so no image bytes travel through the conversation. Preferred over images_base64, which costs ~14k tokens per photo and breaks if a file read was truncated. Max ${wholeMb(IMAGE_URL_MAX_BYTES)}MB each; must be a directly-linked image (Content-Type image/*).`
|
|
@@ -33277,7 +33464,8 @@ var sharedImageSchema = {
|
|
|
33277
33464
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)"),
|
|
33278
33465
|
timeout_ms: timeoutMsSchema,
|
|
33279
33466
|
idempotency_key: idempotencyKeySchema,
|
|
33280
|
-
async: asyncSchema
|
|
33467
|
+
async: asyncSchema,
|
|
33468
|
+
max_wait_ms: maxWaitMsSchema
|
|
33281
33469
|
};
|
|
33282
33470
|
async function resolveVideoInput(args, client2) {
|
|
33283
33471
|
if (args.video_url && args.video_path) {
|
|
@@ -33364,7 +33552,8 @@ async function emitMedia(named, kind, opts, meta3, onWritten) {
|
|
|
33364
33552
|
}
|
|
33365
33553
|
const sink = opts.sink ?? DEFAULT_SINK;
|
|
33366
33554
|
const persisted = await sink.persist(named.map((n) => ({ base: n.base, base64: n.media.base64, mimeType: n.media.mimeType })), {
|
|
33367
|
-
output_dir: opts.output_dir
|
|
33555
|
+
output_dir: opts.output_dir,
|
|
33556
|
+
urlTtlMs: opts.urlTtlMs
|
|
33368
33557
|
});
|
|
33369
33558
|
const refs = persisted.map((p) => p.ref);
|
|
33370
33559
|
if (sink.persistsFiles) await onWritten?.(refs);
|
|
@@ -33393,6 +33582,40 @@ async function emitMedia(named, kind, opts, meta3, onWritten) {
|
|
|
33393
33582
|
async function emit(named, opts, meta3, onWritten) {
|
|
33394
33583
|
return emitMedia(named.map((n) => ({ media: n.image, base: n.base })), "image", opts, meta3, onWritten);
|
|
33395
33584
|
}
|
|
33585
|
+
var SET_URL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
33586
|
+
var BUNDLE_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
33587
|
+
async function persistBundle(named, sink, base) {
|
|
33588
|
+
if (!sink || sink.persistsFiles || named.length < 2) return {};
|
|
33589
|
+
const totalBytes = named.reduce((sum, n) => sum + Math.floor(n.image.base64.length * 3 / 4), 0);
|
|
33590
|
+
if (totalBytes > BUNDLE_MAX_TOTAL_BYTES) {
|
|
33591
|
+
return {
|
|
33592
|
+
bundle_skipped: `The set totals ~${wholeMb(totalBytes)}MB decoded, over the ${wholeMb(BUNDLE_MAX_TOTAL_BYTES)}MB bundling cap (zipping peaks at ~4x that in Worker memory). Download the images individually via their media urls / curl_hints.`
|
|
33593
|
+
};
|
|
33594
|
+
}
|
|
33595
|
+
try {
|
|
33596
|
+
const { mediaExt: mediaExt2 } = await Promise.resolve().then(() => (init_images(), images_exports));
|
|
33597
|
+
const zip = buildZip(named.map((n) => ({ name: `${n.base}.${mediaExt2(n.image.mimeType)}`, bytes: base64ToBytes(n.image.base64) })));
|
|
33598
|
+
const [persisted] = await sink.persist(
|
|
33599
|
+
[{ base: `${base}-bundle`, base64: bytesToBase64(zip), mimeType: "application/zip" }],
|
|
33600
|
+
{ urlTtlMs: SET_URL_TTL_MS }
|
|
33601
|
+
);
|
|
33602
|
+
if (!persisted || persisted.unavailable) {
|
|
33603
|
+
return { bundle_skipped: "The bundle zip could not be stored; the images themselves are unaffected \u2014 download them individually via their media urls / curl_hints." };
|
|
33604
|
+
}
|
|
33605
|
+
return {
|
|
33606
|
+
bundle_url: persisted.ref,
|
|
33607
|
+
bundle: {
|
|
33608
|
+
url: persisted.ref,
|
|
33609
|
+
...persisted.key ? { r2_key: persisted.key } : {},
|
|
33610
|
+
...persisted.expiresAt ? { expires_at: persisted.expiresAt } : {},
|
|
33611
|
+
files: named.map((n) => n.base),
|
|
33612
|
+
...persisted.key ? { curl_hint: `curl -sS -o ${downloadFilename(persisted.key)} "${persisted.ref}"` } : {}
|
|
33613
|
+
}
|
|
33614
|
+
};
|
|
33615
|
+
} catch {
|
|
33616
|
+
return { bundle_skipped: "Bundling failed; the images themselves are unaffected \u2014 download them individually via their media urls / curl_hints." };
|
|
33617
|
+
}
|
|
33618
|
+
}
|
|
33396
33619
|
function assertLocalInputsAvailable(sink, args) {
|
|
33397
33620
|
if ((sink ?? DEFAULT_SINK).persistsFiles) return;
|
|
33398
33621
|
const unavailable = [];
|
|
@@ -33457,6 +33680,23 @@ async function refreshMedia(result, sink) {
|
|
|
33457
33680
|
});
|
|
33458
33681
|
body.media = entries;
|
|
33459
33682
|
if (mediaKeyName) body[mediaKeyName] = refs;
|
|
33683
|
+
const bundle = body.bundle;
|
|
33684
|
+
if (bundle && typeof bundle.r2_key === "string") {
|
|
33685
|
+
try {
|
|
33686
|
+
const fresh = await sink.resign(bundle.r2_key);
|
|
33687
|
+
if (fresh) {
|
|
33688
|
+
const { expires_at: _staleBundle, ...rest } = bundle;
|
|
33689
|
+
body.bundle = {
|
|
33690
|
+
...rest,
|
|
33691
|
+
url: fresh.ref,
|
|
33692
|
+
...fresh.expiresAt ? { expires_at: fresh.expiresAt } : {},
|
|
33693
|
+
curl_hint: `curl -sS -o ${downloadFilename(fresh.key ?? "media")} "${fresh.ref}"`
|
|
33694
|
+
};
|
|
33695
|
+
body.bundle_url = fresh.ref;
|
|
33696
|
+
}
|
|
33697
|
+
} catch {
|
|
33698
|
+
}
|
|
33699
|
+
}
|
|
33460
33700
|
body.media_urls_refreshed = true;
|
|
33461
33701
|
return { ...result, content: [{ ...first, text: JSON.stringify(body, null, 2) }, ...(result.content ?? []).slice(1)] };
|
|
33462
33702
|
}
|
|
@@ -33486,6 +33726,22 @@ function jobHandle(jobId, status) {
|
|
|
33486
33726
|
hint: `Generation running in the background. Poll gemini_get_result with job_id "${jobId}" until status is "done" (results are per-process and expire ~10 min after completion).`
|
|
33487
33727
|
});
|
|
33488
33728
|
}
|
|
33729
|
+
function awaitWithBudget(promise2, waitMs) {
|
|
33730
|
+
return new Promise((resolve2, reject) => {
|
|
33731
|
+
const timer = setTimeout(() => resolve2(void 0), waitMs);
|
|
33732
|
+
timer.unref?.();
|
|
33733
|
+
promise2.then(
|
|
33734
|
+
(result) => {
|
|
33735
|
+
clearTimeout(timer);
|
|
33736
|
+
resolve2(result);
|
|
33737
|
+
},
|
|
33738
|
+
(err) => {
|
|
33739
|
+
clearTimeout(timer);
|
|
33740
|
+
reject(err);
|
|
33741
|
+
}
|
|
33742
|
+
);
|
|
33743
|
+
});
|
|
33744
|
+
}
|
|
33489
33745
|
var JobRegistry = class {
|
|
33490
33746
|
/**
|
|
33491
33747
|
* Lets a replayed result have its media URLs re-minted. Set by the session
|
|
@@ -33538,7 +33794,7 @@ var JobRegistry = class {
|
|
|
33538
33794
|
* different registry and therefore a genuinely separate generation.
|
|
33539
33795
|
*/
|
|
33540
33796
|
async dispatch(opts, work) {
|
|
33541
|
-
const { toolName, fingerprint, idempotencyKey, async } = opts;
|
|
33797
|
+
const { toolName, fingerprint, idempotencyKey, async, waitMs } = opts;
|
|
33542
33798
|
const now = Date.now();
|
|
33543
33799
|
let hit;
|
|
33544
33800
|
if (idempotencyKey !== void 0) {
|
|
@@ -33552,7 +33808,9 @@ var JobRegistry = class {
|
|
|
33552
33808
|
}
|
|
33553
33809
|
if (hit) {
|
|
33554
33810
|
if (async) return jobHandle(hit.jobId, hit.status);
|
|
33555
|
-
|
|
33811
|
+
const settled = waitMs !== void 0 && hit.status === "running" ? await awaitWithBudget(hit.promise, waitMs) : await hit.promise;
|
|
33812
|
+
if (settled === void 0) return jobHandle(hit.jobId, "running");
|
|
33813
|
+
return annotateReused(await refreshMedia(settled, this.resigner), hit.jobId);
|
|
33556
33814
|
}
|
|
33557
33815
|
const jobId = randomUUID();
|
|
33558
33816
|
this.evict(now);
|
|
@@ -33576,6 +33834,10 @@ var JobRegistry = class {
|
|
|
33576
33834
|
if (this.runningByFingerprint.get(fingerprint) === jobId) this.runningByFingerprint.delete(fingerprint);
|
|
33577
33835
|
});
|
|
33578
33836
|
if (async) return jobHandle(jobId, "running");
|
|
33837
|
+
if (waitMs !== void 0) {
|
|
33838
|
+
const settled = await awaitWithBudget(promise2, waitMs);
|
|
33839
|
+
return settled ?? jobHandle(jobId, "running");
|
|
33840
|
+
}
|
|
33579
33841
|
return promise2;
|
|
33580
33842
|
}
|
|
33581
33843
|
/**
|
|
@@ -33643,7 +33905,10 @@ function registerGenerateTools(server, client2) {
|
|
|
33643
33905
|
images: external_exports.array(external_exports.string().min(1)).optional().describe("Paths to reference input images (image-conditioned generation)"),
|
|
33644
33906
|
images_url: imagesUrlSchema(),
|
|
33645
33907
|
images_file_uris: imagesFileUrisSchema(),
|
|
33908
|
+
images_r2_keys: imagesR2KeysSchema(),
|
|
33646
33909
|
images_base64: external_exports.array(external_exports.string().min(1)).optional().describe("Reference images as base64 strings or data URIs. Last resort: prefer images_url or images_file_uris, which keep image bytes out of the conversation"),
|
|
33910
|
+
characters: charactersSchema,
|
|
33911
|
+
style: styleSchema,
|
|
33647
33912
|
video_url: external_exports.string().url().optional().describe("Public YouTube URL (or a previously uploaded Files API uri) as a video reference (video\u2192image; use a Flash model e.g. gemini-3.1-flash-image)"),
|
|
33648
33913
|
video_path: videoPathSchema,
|
|
33649
33914
|
confirm: schemaConfirm,
|
|
@@ -33669,13 +33934,19 @@ function registerGenerateTools(server, client2) {
|
|
|
33669
33934
|
from_clipboard: args.from_clipboard,
|
|
33670
33935
|
images_url: args.images_url,
|
|
33671
33936
|
images_file_uris: args.images_file_uris,
|
|
33937
|
+
images_r2_keys: args.images_r2_keys,
|
|
33938
|
+
characters: args.characters,
|
|
33939
|
+
style: args.style,
|
|
33672
33940
|
video_url: args.video_url,
|
|
33673
33941
|
video_path: args.video_path
|
|
33674
33942
|
});
|
|
33675
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_image_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
33943
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_image_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
33676
33944
|
const seed = pickSeed(args.seed);
|
|
33677
33945
|
const slug = args.filename ? baseName(args.filename) : slugify2(args.prompt);
|
|
33678
|
-
const
|
|
33946
|
+
const charRefs = await resolveCharacterRefs(client2, args.characters, args.style);
|
|
33947
|
+
const prompt = composePrompt(args.prompt, charRefs);
|
|
33948
|
+
const { inputs: directInputs, report } = await withProgressHeartbeat(extra, "Resolving reference images", () => resolveImageInputs(args, client2));
|
|
33949
|
+
const refInputs = [...charRefs.inputs, ...directInputs];
|
|
33679
33950
|
const video = await withProgressHeartbeat(extra, "Uploading video to the Gemini Files API", () => resolveVideoInput(args, client2));
|
|
33680
33951
|
const named = [];
|
|
33681
33952
|
let capturedText;
|
|
@@ -33686,7 +33957,7 @@ function registerGenerateTools(server, client2) {
|
|
|
33686
33957
|
for (let i = 0; i < count; i++) {
|
|
33687
33958
|
try {
|
|
33688
33959
|
const result = await client2.generate({
|
|
33689
|
-
prompt
|
|
33960
|
+
prompt,
|
|
33690
33961
|
images: refInputs.length > 0 ? refInputs : void 0,
|
|
33691
33962
|
model: args.model,
|
|
33692
33963
|
aspectRatio: args.aspect_ratio,
|
|
@@ -33713,6 +33984,7 @@ function registerGenerateTools(server, client2) {
|
|
|
33713
33984
|
if (capturedText) meta3.text = capturedText;
|
|
33714
33985
|
if (capturedGrounding) meta3.grounding = capturedGrounding;
|
|
33715
33986
|
if (report) meta3.image_inputs = report;
|
|
33987
|
+
if (charRefs.meta) Object.assign(meta3, charRefs.meta);
|
|
33716
33988
|
if (failed.length) {
|
|
33717
33989
|
meta3.failed_images = failed;
|
|
33718
33990
|
meta3.partial = `${failed.length} of ${count} image(s) failed; the ${named.length} that succeeded are returned above. Re-run with count set to the shortfall rather than the whole batch \u2014 each failure message is in failed_images.`;
|
|
@@ -33735,7 +34007,10 @@ function registerGenerateTools(server, client2) {
|
|
|
33735
34007
|
images: external_exports.array(external_exports.string().min(1)).optional().describe("Paths to input image file(s) (1 = edit, 2+ = compose)"),
|
|
33736
34008
|
images_url: imagesUrlSchema("Input images"),
|
|
33737
34009
|
images_file_uris: imagesFileUrisSchema("Input images"),
|
|
34010
|
+
images_r2_keys: imagesR2KeysSchema("Input images"),
|
|
33738
34011
|
images_base64: external_exports.array(external_exports.string().min(1)).optional().describe("Input images as base64 strings or data URIs. Last resort: prefer images_url or images_file_uris, which keep image bytes out of the conversation"),
|
|
34012
|
+
characters: charactersSchema,
|
|
34013
|
+
style: styleSchema,
|
|
33739
34014
|
filename: external_exports.string().optional().describe("Base filename for the output image (extension stripped; default: slugified prompt)"),
|
|
33740
34015
|
confirm: schemaConfirm,
|
|
33741
34016
|
...sharedImageSchema
|
|
@@ -33743,7 +34018,7 @@ function registerGenerateTools(server, client2) {
|
|
|
33743
34018
|
},
|
|
33744
34019
|
async (args, extra) => {
|
|
33745
34020
|
assertLocalInputsAvailable(client2.mediaSink, args);
|
|
33746
|
-
requireImageInput(args);
|
|
34021
|
+
if (!args.characters?.length) requireImageInput(args);
|
|
33747
34022
|
const gate = await previewLocalInputsUnlessConfirmed(args.confirm, SEND_LOCAL_ACTION, GENERATE_ENDPOINT, args.images);
|
|
33748
34023
|
if (gate) return gate;
|
|
33749
34024
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
@@ -33758,14 +34033,20 @@ function registerGenerateTools(server, client2) {
|
|
|
33758
34033
|
images_base64: args.images_base64,
|
|
33759
34034
|
from_clipboard: args.from_clipboard,
|
|
33760
34035
|
images_url: args.images_url,
|
|
33761
|
-
images_file_uris: args.images_file_uris
|
|
34036
|
+
images_file_uris: args.images_file_uris,
|
|
34037
|
+
images_r2_keys: args.images_r2_keys,
|
|
34038
|
+
characters: args.characters,
|
|
34039
|
+
style: args.style
|
|
33762
34040
|
});
|
|
33763
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_image_edit", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
34041
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_image_edit", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
33764
34042
|
const seed = pickSeed(args.seed);
|
|
33765
34043
|
const slug = args.filename ? baseName(args.filename) : slugify2(args.prompt);
|
|
33766
|
-
const
|
|
34044
|
+
const charRefs = await resolveCharacterRefs(client2, args.characters, args.style);
|
|
34045
|
+
const prompt = composePrompt(args.prompt, charRefs);
|
|
34046
|
+
const { inputs: directInputs, report } = await withProgressHeartbeat(extra, "Resolving input images", () => resolveImageInputs(args, client2));
|
|
34047
|
+
const inputs = [...charRefs.inputs, ...directInputs];
|
|
33767
34048
|
const result = await withProgressHeartbeat(extra, `Editing image (${model})`, () => client2.generate({
|
|
33768
|
-
prompt
|
|
34049
|
+
prompt,
|
|
33769
34050
|
images: inputs,
|
|
33770
34051
|
model: args.model,
|
|
33771
34052
|
aspectRatio: args.aspect_ratio,
|
|
@@ -33780,6 +34061,7 @@ function registerGenerateTools(server, client2) {
|
|
|
33780
34061
|
if (text) meta3.text = text;
|
|
33781
34062
|
if (result.grounding) meta3.grounding = result.grounding;
|
|
33782
34063
|
if (report) meta3.image_inputs = report;
|
|
34064
|
+
if (charRefs.meta) Object.assign(meta3, charRefs.meta);
|
|
33783
34065
|
meta3.hint = REFINE_HINT;
|
|
33784
34066
|
const risk = timeoutRiskHint({ model, imageSize: args.image_size, persistsFiles: client2.mediaSink?.persistsFiles });
|
|
33785
34067
|
if (risk) meta3.timeout_risk = risk;
|
|
@@ -33797,7 +34079,7 @@ function registerSetTools(server, client2) {
|
|
|
33797
34079
|
server.registerTool(
|
|
33798
34080
|
"gemini_image_set",
|
|
33799
34081
|
{
|
|
33800
|
-
description:
|
|
34082
|
+
description: 'Generate a consistent SET of images: a master image from master_prompt, then one image per scene that references the master so the subject/style stays consistent. Provide `scenes` (explicit per-image prompts) OR `count` (variations of the master). Scene generations run in parallel (reference_mode "master", the default). On the hosted connector: saved `characters` and a saved `style` can seed the whole set by name, multi-image results include a `bundle_url` zip of every image (one curl instead of N), and `max_wait_ms` returns a pollable job handle if the batch runs long.',
|
|
33801
34083
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
33802
34084
|
inputSchema: {
|
|
33803
34085
|
master_prompt: external_exports.string().min(1).describe("Prompt for the master/reference image"),
|
|
@@ -33808,6 +34090,9 @@ function registerSetTools(server, client2) {
|
|
|
33808
34090
|
master_images: external_exports.array(external_exports.string().min(1)).optional().describe("Reference image paths passed to the master generation call"),
|
|
33809
34091
|
master_images_url: imagesUrlSchema("Reference images passed to the master AND to every scene call (fetched once)"),
|
|
33810
34092
|
master_images_file_uris: imagesFileUrisSchema("Reference images passed to the master AND to every scene call"),
|
|
34093
|
+
master_images_r2_keys: imagesR2KeysSchema("Reference images passed to the master AND to every scene call"),
|
|
34094
|
+
characters: charactersSchema,
|
|
34095
|
+
style: styleSchema,
|
|
33811
34096
|
master_images_base64: external_exports.array(external_exports.string().min(1)).optional().describe("Reference images as base64 strings or data URIs for master generation. Last resort: prefer master_images_url or master_images_file_uris, which keep image bytes out of the conversation"),
|
|
33812
34097
|
confirm: schemaConfirm,
|
|
33813
34098
|
...sharedImageSchema
|
|
@@ -33835,27 +34120,34 @@ function registerSetTools(server, client2) {
|
|
|
33835
34120
|
master_images_base64: args.master_images_base64,
|
|
33836
34121
|
from_clipboard: args.from_clipboard,
|
|
33837
34122
|
master_images_url: args.master_images_url,
|
|
33838
|
-
master_images_file_uris: args.master_images_file_uris
|
|
34123
|
+
master_images_file_uris: args.master_images_file_uris,
|
|
34124
|
+
master_images_r2_keys: args.master_images_r2_keys,
|
|
34125
|
+
characters: args.characters,
|
|
34126
|
+
style: args.style
|
|
33839
34127
|
});
|
|
33840
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_image_set", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
34128
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_image_set", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
33841
34129
|
const seed = pickSeed(args.seed);
|
|
33842
34130
|
const cfg = { model: args.model, aspectRatio: args.aspect_ratio, imageSize: args.image_size, thinkingLevel: args.thinking_level, googleSearch: args.google_search, timeoutMs: args.timeout_ms };
|
|
33843
34131
|
const slug = args.basename ? baseName(args.basename) : slugify2(args.master_prompt);
|
|
34132
|
+
const charRefs = await resolveCharacterRefs(client2, args.characters, args.style);
|
|
34133
|
+
const masterPrompt = composePrompt(args.master_prompt, charRefs);
|
|
34134
|
+
const styleOnly = { styleFragment: charRefs.styleFragment };
|
|
33844
34135
|
const carried = await resolveImageInputs(
|
|
33845
|
-
{ images_url: args.master_images_url, images_file_uris: args.master_images_file_uris },
|
|
34136
|
+
{ images_url: args.master_images_url, images_file_uris: args.master_images_file_uris, images_r2_keys: args.master_images_r2_keys },
|
|
33846
34137
|
client2
|
|
33847
34138
|
);
|
|
34139
|
+
const carriedRefs = [...charRefs.inputs, ...carried.inputs];
|
|
33848
34140
|
const masterOnly = await resolveImageInputs(
|
|
33849
34141
|
{ images: args.master_images, images_base64: args.master_images_base64, from_clipboard: args.from_clipboard },
|
|
33850
34142
|
client2
|
|
33851
34143
|
);
|
|
33852
|
-
const masterRefInputs = [...masterOnly.inputs, ...carried.inputs];
|
|
34144
|
+
const masterRefInputs = [...charRefs.inputs, ...masterOnly.inputs, ...carried.inputs];
|
|
33853
34145
|
const failed = [];
|
|
33854
34146
|
const report = carried.report || masterOnly.report ? { ...masterOnly.report, ...carried.report } : void 0;
|
|
33855
34147
|
const named = [];
|
|
33856
34148
|
const masterResult = await withProgressHeartbeat(extra, `Generating image set (${model})`, async () => {
|
|
33857
34149
|
const result = await client2.generate({
|
|
33858
|
-
prompt:
|
|
34150
|
+
prompt: masterPrompt,
|
|
33859
34151
|
images: masterRefInputs.length > 0 ? masterRefInputs : void 0,
|
|
33860
34152
|
seed,
|
|
33861
34153
|
...cfg
|
|
@@ -33876,7 +34168,7 @@ function registerSetTools(server, client2) {
|
|
|
33876
34168
|
let ref = master;
|
|
33877
34169
|
for (let i = 0; i < scenePrompts.length; i++) {
|
|
33878
34170
|
try {
|
|
33879
|
-
const { images: [img] } = await client2.generate({ prompt: scenePrompts[i], images: [ref, ...
|
|
34171
|
+
const { images: [img] } = await client2.generate({ prompt: composePrompt(scenePrompts[i], styleOnly), images: [ref, ...carriedRefs], seed: seed + i + 1, ...cfg });
|
|
33880
34172
|
named.push({ image: img, base: sceneName(i) });
|
|
33881
34173
|
ref = img;
|
|
33882
34174
|
} catch (err) {
|
|
@@ -33885,7 +34177,7 @@ function registerSetTools(server, client2) {
|
|
|
33885
34177
|
}
|
|
33886
34178
|
} else {
|
|
33887
34179
|
const settled = await Promise.allSettled(
|
|
33888
|
-
scenePrompts.map((p, i) => client2.generate({ prompt: p, images: [master, ...
|
|
34180
|
+
scenePrompts.map((p, i) => client2.generate({ prompt: composePrompt(p, styleOnly), images: [master, ...carriedRefs], seed: seed + i + 1, ...cfg }).then((r) => r.images[0]))
|
|
33889
34181
|
);
|
|
33890
34182
|
settled.forEach((outcome, i) => {
|
|
33891
34183
|
if (outcome.status === "fulfilled") named.push({ image: outcome.value, base: sceneName(i) });
|
|
@@ -33899,13 +34191,15 @@ function registerSetTools(server, client2) {
|
|
|
33899
34191
|
if (masterText) meta3.text = masterText;
|
|
33900
34192
|
if (masterResult.grounding) meta3.grounding = masterResult.grounding;
|
|
33901
34193
|
if (report) meta3.image_inputs = report;
|
|
34194
|
+
if (charRefs.meta) Object.assign(meta3, charRefs.meta);
|
|
33902
34195
|
if (failed.length) {
|
|
33903
34196
|
meta3.failed_scenes = failed;
|
|
33904
34197
|
meta3.partial = `${failed.length} of ${failed.length + named.length - 1} scene(s) failed; the master and the ${named.length - 1} scene(s) that succeeded are returned above. Re-run just the failed prompts rather than the whole set \u2014 each failure message is in failed_scenes.`;
|
|
33905
34198
|
}
|
|
33906
34199
|
const risk = timeoutRiskHint({ model, imageSize: args.image_size, count: named.length, persistsFiles: client2.mediaSink?.persistsFiles });
|
|
33907
34200
|
if (risk) meta3.timeout_risk = risk;
|
|
33908
|
-
|
|
34201
|
+
if (!args.inline) Object.assign(meta3, await persistBundle(named, client2.mediaSink, slug));
|
|
34202
|
+
return emit(named, { ...args, sink: client2.mediaSink, urlTtlMs: SET_URL_TTL_MS }, meta3);
|
|
33909
34203
|
});
|
|
33910
34204
|
}
|
|
33911
34205
|
);
|
|
@@ -34070,8 +34364,14 @@ var GeminiClient = class {
|
|
|
34070
34364
|
* server is single-user, so its one singleton client is one session.
|
|
34071
34365
|
*/
|
|
34072
34366
|
session = new SessionState();
|
|
34367
|
+
/** Per-account character/style library — hosted connector only (see options). */
|
|
34368
|
+
library;
|
|
34369
|
+
/** Signed upload-URL minter — hosted connector only (see options). */
|
|
34370
|
+
uploadUrls;
|
|
34073
34371
|
constructor(opts = {}) {
|
|
34074
34372
|
this.mediaSink = opts.mediaSink ?? createDiskSink();
|
|
34373
|
+
this.library = opts.library;
|
|
34374
|
+
this.uploadUrls = opts.uploadUrls;
|
|
34075
34375
|
this.session.jobs.resigner = this.mediaSink;
|
|
34076
34376
|
this.explicitApiKey = opts.apiKey?.trim() || void 0;
|
|
34077
34377
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
@@ -34671,6 +34971,7 @@ function registerInteractTools(server, client2) {
|
|
|
34671
34971
|
timeout_ms: timeoutMsSchema,
|
|
34672
34972
|
idempotency_key: idempotencyKeySchema,
|
|
34673
34973
|
async: asyncSchema,
|
|
34974
|
+
max_wait_ms: maxWaitMsSchema,
|
|
34674
34975
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)"),
|
|
34675
34976
|
confirm: schemaConfirm
|
|
34676
34977
|
}
|
|
@@ -34717,7 +35018,7 @@ function registerInteractTools(server, client2) {
|
|
|
34717
35018
|
video_url: args.video_url,
|
|
34718
35019
|
video_path: args.video_path
|
|
34719
35020
|
});
|
|
34720
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_interact", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
35021
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_interact", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
34721
35022
|
const { inputs, report } = await resolveImageInputs({ ...args, images }, client2);
|
|
34722
35023
|
const video = await withProgressHeartbeat(extra, "Uploading video to the Gemini Files API", () => resolveVideoInput(args, client2));
|
|
34723
35024
|
const callOpts = {
|
|
@@ -34821,6 +35122,7 @@ function registerVideoTools(server, client2) {
|
|
|
34821
35122
|
timeout_ms: timeoutMsSchema,
|
|
34822
35123
|
idempotency_key: idempotencyKeySchema,
|
|
34823
35124
|
async: asyncSchema,
|
|
35125
|
+
max_wait_ms: maxWaitMsSchema,
|
|
34824
35126
|
confirm: schemaConfirm
|
|
34825
35127
|
}
|
|
34826
35128
|
},
|
|
@@ -34850,7 +35152,7 @@ function registerVideoTools(server, client2) {
|
|
|
34850
35152
|
images_file_uris: args.images_file_uris,
|
|
34851
35153
|
previous_interaction_id: previousInteractionId
|
|
34852
35154
|
});
|
|
34853
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_video_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
35155
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_video_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
34854
35156
|
const { inputs, report } = await resolveImageInputs(args, client2);
|
|
34855
35157
|
const r = await withProgressHeartbeat(extra, `Generating video (${model})`, () => client2.generateVideo({
|
|
34856
35158
|
input: args.prompt,
|
|
@@ -34910,6 +35212,7 @@ function registerMusicTools(server, client2) {
|
|
|
34910
35212
|
timeout_ms: timeoutMsSchema,
|
|
34911
35213
|
idempotency_key: idempotencyKeySchema,
|
|
34912
35214
|
async: asyncSchema,
|
|
35215
|
+
max_wait_ms: maxWaitMsSchema,
|
|
34913
35216
|
confirm: schemaConfirm
|
|
34914
35217
|
}
|
|
34915
35218
|
},
|
|
@@ -34943,7 +35246,7 @@ function registerMusicTools(server, client2) {
|
|
|
34943
35246
|
images_file_uris: args.images_file_uris,
|
|
34944
35247
|
previous_interaction_id: previousInteractionId
|
|
34945
35248
|
});
|
|
34946
|
-
return client2.session.jobs.dispatch({ toolName: "gemini_music_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
35249
|
+
return client2.session.jobs.dispatch({ toolName: "gemini_music_generate", fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
|
|
34947
35250
|
const { inputs, report } = await resolveImageInputs(args, client2);
|
|
34948
35251
|
const r = await withProgressHeartbeat(extra, `Generating music (${model})`, () => client2.generateMusic({
|
|
34949
35252
|
input: args.prompt,
|
package/dist/client.js
CHANGED
|
@@ -153,8 +153,14 @@ export class GeminiClient {
|
|
|
153
153
|
* server is single-user, so its one singleton client is one session.
|
|
154
154
|
*/
|
|
155
155
|
session = new SessionState();
|
|
156
|
+
/** Per-account character/style library — hosted connector only (see options). */
|
|
157
|
+
library;
|
|
158
|
+
/** Signed upload-URL minter — hosted connector only (see options). */
|
|
159
|
+
uploadUrls;
|
|
156
160
|
constructor(opts = {}) {
|
|
157
161
|
this.mediaSink = opts.mediaSink ?? createDiskSink();
|
|
162
|
+
this.library = opts.library;
|
|
163
|
+
this.uploadUrls = opts.uploadUrls;
|
|
158
164
|
// A replayed idempotent result must not hand back an expired media URL, so
|
|
159
165
|
// the job registry needs a way to re-sign from the stable r2_key.
|
|
160
166
|
this.session.jobs.resigner = this.mediaSink;
|