dsh-modellix 0.1.1 → 0.2.1
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/CHANGELOG.md +60 -0
- package/README.md +107 -167
- package/README.zh-CN.md +105 -165
- package/cordis.patch.yml +0 -13
- package/docs/assets/chat-media-generation-en.webp +0 -0
- package/docs/assets/chat-media-generation-zh.webp +0 -0
- package/docs/assets/design-results-drawer-en.webp +0 -0
- package/docs/assets/design-results-drawer-zh.webp +0 -0
- package/docs/assets/llm-model-selector-en.webp +0 -0
- package/docs/assets/llm-model-selector-zh.webp +0 -0
- package/docs/assets/media-players-en.webp +0 -0
- package/docs/assets/media-players-zh.webp +0 -0
- package/docs/assets/settings-ready-en.webp +0 -0
- package/docs/assets/settings-ready-zh.webp +0 -0
- package/docs/assets/web-tools-auto-en.webp +0 -0
- package/docs/assets/web-tools-auto-zh.webp +0 -0
- package/docs/en-US/LOCAL_USAGE.md +20 -16
- package/docs/en-US/RELEASE_CHECKLIST.md +270 -0
- package/docs/en-US/USER_GUIDE.md +186 -394
- package/docs/zh-CN/LOCAL_USAGE.md +20 -16
- package/docs/zh-CN/RELEASE_CHECKLIST.md +270 -0
- package/docs/zh-CN/USER_GUIDE.md +185 -393
- package/lib/client.d.ts +18 -4
- package/lib/client.js +1271 -454
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +166 -112
- package/lib/index.js +1008 -197
- package/lib/index.js.map +1 -1
- package/package.json +72 -48
- package/docs/assets/design-desktop-en.webp +0 -0
- package/docs/assets/design-desktop-zh.webp +0 -0
- package/docs/assets/design-proposal-en.webp +0 -0
- package/docs/assets/design-proposal-zh.webp +0 -0
- package/docs/assets/design-results-media-en.webp +0 -0
- package/docs/assets/design-results-media-zh.webp +0 -0
- package/docs/assets/web-tools-en.webp +0 -0
- package/docs/assets/web-tools-zh.webp +0 -0
package/lib/index.js
CHANGED
|
@@ -2,8 +2,11 @@ import z from "@deepseek-ai/schemastery";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
5
|
-
import { SettingsConflictError
|
|
5
|
+
import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
|
|
6
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
7
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
6
8
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
9
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
10
|
import { defineDomain } from "@deepseek-ai/dsh-storage-domain";
|
|
8
11
|
import { z as z$1 } from "zod";
|
|
9
12
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -1248,8 +1251,8 @@ var ModelCatalogClient = class {
|
|
|
1248
1251
|
}
|
|
1249
1252
|
};
|
|
1250
1253
|
function parseCatalogPage(payload, query, source) {
|
|
1251
|
-
const envelope = record$
|
|
1252
|
-
const data = record$
|
|
1254
|
+
const envelope = record$2(payload);
|
|
1255
|
+
const data = record$2(envelope?.data) ?? envelope;
|
|
1253
1256
|
const rawItems = Array.isArray(payload) ? payload : firstArray(data?.items, data?.models, data?.list, envelope?.items, envelope?.models);
|
|
1254
1257
|
if (rawItems === null) throw new DesignError("UNEXPECTED_RESPONSE", "The model catalog response does not contain a model list");
|
|
1255
1258
|
const items = rawItems.map((item) => parseModel(item, query.category)).filter((item) => item !== null && item.categories.includes(query.category));
|
|
@@ -1267,9 +1270,9 @@ function parseCatalogPage(payload, query, source) {
|
|
|
1267
1270
|
};
|
|
1268
1271
|
}
|
|
1269
1272
|
function parseModel(value, requestedCategory) {
|
|
1270
|
-
const item = record$
|
|
1273
|
+
const item = record$2(value);
|
|
1271
1274
|
if (item === null) return null;
|
|
1272
|
-
const provider = identifier(item.provider ?? item.provider_id ?? record$
|
|
1275
|
+
const provider = identifier(item.provider ?? item.provider_id ?? record$2(item.provider_info)?.slug);
|
|
1273
1276
|
let modelId = identifier(item.model_id ?? item.modelId ?? item.id ?? item.name);
|
|
1274
1277
|
const rawSlug = stringValue(item.slug ?? item.model);
|
|
1275
1278
|
let slugProvider = null;
|
|
@@ -1282,9 +1285,11 @@ function parseModel(value, requestedCategory) {
|
|
|
1282
1285
|
}
|
|
1283
1286
|
const resolvedProvider = provider ?? slugProvider;
|
|
1284
1287
|
if (resolvedProvider === null || modelId === null) return null;
|
|
1285
|
-
const
|
|
1288
|
+
const rawTaskType = item.task_type ?? item.taskType ?? item.type;
|
|
1289
|
+
const categories = parseCategories(item.categories ?? item.category ?? rawTaskType, requestedCategory);
|
|
1286
1290
|
const displayName = stringValue(item.display_name ?? item.displayName ?? item.title) ?? modelId;
|
|
1287
1291
|
const description = stringValue(item.description ?? item.summary);
|
|
1292
|
+
const taskType = routingType(rawTaskType);
|
|
1288
1293
|
const thumbnailUrl = safeHttpsUrl$2(item.thumbnail_url ?? item.thumbnailUrl ?? item.cover_url ?? item.cover);
|
|
1289
1294
|
return {
|
|
1290
1295
|
provider: resolvedProvider,
|
|
@@ -1292,10 +1297,16 @@ function parseModel(value, requestedCategory) {
|
|
|
1292
1297
|
slug: `${resolvedProvider}/${modelId}`,
|
|
1293
1298
|
displayName,
|
|
1294
1299
|
categories,
|
|
1300
|
+
...taskType === null ? {} : { taskType },
|
|
1295
1301
|
...description === null ? {} : { description },
|
|
1296
1302
|
...thumbnailUrl === null ? {} : { thumbnailUrl }
|
|
1297
1303
|
};
|
|
1298
1304
|
}
|
|
1305
|
+
function routingType(value) {
|
|
1306
|
+
if (typeof value !== "string") return null;
|
|
1307
|
+
const normalized = value.trim().toLowerCase();
|
|
1308
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+){0,7}$/u.test(normalized) ? normalized.slice(0, 64) : null;
|
|
1309
|
+
}
|
|
1299
1310
|
function parseCategories(value, fallback) {
|
|
1300
1311
|
if (value === void 0 || value === null) return [fallback];
|
|
1301
1312
|
const candidates = Array.isArray(value) ? value : [value];
|
|
@@ -1358,7 +1369,7 @@ function safeHttpsUrl$2(value) {
|
|
|
1358
1369
|
return null;
|
|
1359
1370
|
}
|
|
1360
1371
|
}
|
|
1361
|
-
function record$
|
|
1372
|
+
function record$2(value) {
|
|
1362
1373
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1363
1374
|
}
|
|
1364
1375
|
function firstArray(...values) {
|
|
@@ -1409,7 +1420,7 @@ var ModelSchemaClient = class {
|
|
|
1409
1420
|
const url = new URL(`/portal/v1/models/${encodeURIComponent(modelId)}`, PUBLIC_ORIGIN);
|
|
1410
1421
|
url.searchParams.set("provider", provider);
|
|
1411
1422
|
const payload = await requestNoAuthorization(this.#fetch, url, signal, this.#requestTimeoutMs);
|
|
1412
|
-
const data = asRecord$
|
|
1423
|
+
const data = asRecord$5(payload.data) ?? payload;
|
|
1413
1424
|
return {
|
|
1414
1425
|
provider,
|
|
1415
1426
|
modelId,
|
|
@@ -1429,7 +1440,7 @@ function extractAllowedSubmitUrl(document, provider, modelId) {
|
|
|
1429
1440
|
assertIdentifier(modelId, "modelId");
|
|
1430
1441
|
const servers = document.servers;
|
|
1431
1442
|
if (!Array.isArray(servers) || servers.length === 0) throw new DesignError("SCHEMA_INVALID", "The public model schema does not publish a submission server");
|
|
1432
|
-
const server = asRecord$
|
|
1443
|
+
const server = asRecord$5(servers[0]);
|
|
1433
1444
|
if (typeof server?.url !== "string") throw new DesignError("SCHEMA_INVALID", "The public model schema server URL is missing");
|
|
1434
1445
|
let url;
|
|
1435
1446
|
try {
|
|
@@ -1445,13 +1456,13 @@ function parseSchemaData(value) {
|
|
|
1445
1456
|
if (typeof value === "string") {
|
|
1446
1457
|
if (value.length > 2 * 1024 * 1024) throw new DesignError("SCHEMA_INVALID", "schema_data exceeds the size limit");
|
|
1447
1458
|
try {
|
|
1448
|
-
const object = asRecord$
|
|
1459
|
+
const object = asRecord$5(JSON.parse(value));
|
|
1449
1460
|
if (object !== null) return object;
|
|
1450
1461
|
} catch (cause) {
|
|
1451
1462
|
throw new DesignError("SCHEMA_INVALID", "schema_data is not valid JSON", { cause });
|
|
1452
1463
|
}
|
|
1453
1464
|
}
|
|
1454
|
-
const object = asRecord$
|
|
1465
|
+
const object = asRecord$5(value);
|
|
1455
1466
|
if (object === null) throw new DesignError("SCHEMA_INVALID", "The portal model detail does not contain schema_data");
|
|
1456
1467
|
return object;
|
|
1457
1468
|
}
|
|
@@ -1484,14 +1495,164 @@ async function requestNoAuthorization(fetchPort, url, signal, requestTimeoutMs =
|
|
|
1484
1495
|
});
|
|
1485
1496
|
throw new DesignError("SCHEMA_INVALID", "The model schema is not valid JSON", { cause });
|
|
1486
1497
|
}
|
|
1487
|
-
const document = asRecord$
|
|
1498
|
+
const document = asRecord$5(parsed);
|
|
1488
1499
|
if (document === null) throw new DesignError("SCHEMA_INVALID", "The model schema must be an object");
|
|
1489
1500
|
return document;
|
|
1490
1501
|
}
|
|
1491
1502
|
function assertIdentifier(value, field) {
|
|
1492
1503
|
if (!IDENTIFIER$1.test(value)) throw new DesignError("INVALID_ARGUMENT", `${field} must be a Modellix provider/model identifier`);
|
|
1493
1504
|
}
|
|
1494
|
-
function asRecord$
|
|
1505
|
+
function asRecord$5(value) {
|
|
1506
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1507
|
+
}
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/design/media-upload-client.ts
|
|
1510
|
+
const MEDIA_UPLOAD_URL = "https://api.modellix.ai/api/v1/media/files";
|
|
1511
|
+
const MAX_MEDIA_FILE_BYTES = 16 * 1024 * 1024;
|
|
1512
|
+
const MAX_UPLOAD_RESPONSE_BYTES = 512 * 1024;
|
|
1513
|
+
const DEFAULT_UPLOAD_TIMEOUT_MS = 3e4;
|
|
1514
|
+
const SAFE_FILE_ID = /^[A-Za-z0-9._~:+@=-]{1,512}$/u;
|
|
1515
|
+
var MediaUploadClient = class {
|
|
1516
|
+
#fetch;
|
|
1517
|
+
#timeoutMs;
|
|
1518
|
+
constructor(options = {}) {
|
|
1519
|
+
this.#fetch = options.fetch ?? fetch;
|
|
1520
|
+
this.#timeoutMs = options.timeoutMs ?? DEFAULT_UPLOAD_TIMEOUT_MS;
|
|
1521
|
+
}
|
|
1522
|
+
async upload(input) {
|
|
1523
|
+
if (input.bytes.byteLength < 1 || input.bytes.byteLength > MAX_MEDIA_FILE_BYTES) throw new DesignError("INVALID_ARGUMENT", "Media file size must be between 1 byte and 16 MiB");
|
|
1524
|
+
if (input.apiKey.trim() === "" || /[\r\n]/u.test(input.apiKey)) throw new DesignError("INVALID_ARGUMENT", "Modellix credential is missing or malformed");
|
|
1525
|
+
input.signal?.throwIfAborted();
|
|
1526
|
+
const filename = safeFilename(input.filename, input.mimeType);
|
|
1527
|
+
const form = new FormData();
|
|
1528
|
+
form.append("file", new Blob([input.bytes], { type: input.mimeType }), filename);
|
|
1529
|
+
const deadline = requestDeadline(input.signal, this.#timeoutMs);
|
|
1530
|
+
let response;
|
|
1531
|
+
try {
|
|
1532
|
+
response = await this.#fetch(MEDIA_UPLOAD_URL, {
|
|
1533
|
+
method: "POST",
|
|
1534
|
+
headers: new Headers({
|
|
1535
|
+
accept: "application/json",
|
|
1536
|
+
authorization: `Bearer ${input.apiKey}`
|
|
1537
|
+
}),
|
|
1538
|
+
body: form,
|
|
1539
|
+
redirect: "error",
|
|
1540
|
+
signal: deadline.signal
|
|
1541
|
+
});
|
|
1542
|
+
} catch (cause) {
|
|
1543
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The media upload outcome is unknown; do not retry automatically", { cause });
|
|
1544
|
+
}
|
|
1545
|
+
if (response.redirected) {
|
|
1546
|
+
response.body?.cancel().catch(() => void 0);
|
|
1547
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The media upload outcome is unknown; do not retry automatically", { status: response.status });
|
|
1548
|
+
}
|
|
1549
|
+
if (!response.ok) {
|
|
1550
|
+
const ambiguous = response.status === 408 || response.status === 409 || response.status === 425 || response.status === 429 || response.status >= 500;
|
|
1551
|
+
response.body?.cancel().catch(() => void 0);
|
|
1552
|
+
throw new DesignError(ambiguous ? "SUBMIT_UNKNOWN" : "SUBMIT_REJECTED", ambiguous ? "The media upload outcome is unknown; do not retry automatically" : `The media upload was rejected with HTTP ${String(response.status)}`, { status: response.status });
|
|
1553
|
+
}
|
|
1554
|
+
let payload;
|
|
1555
|
+
try {
|
|
1556
|
+
payload = await readBoundedResponseJson(response, MAX_UPLOAD_RESPONSE_BYTES, deadline.signal);
|
|
1557
|
+
return parseMediaUploadRecord(payload);
|
|
1558
|
+
} catch (cause) {
|
|
1559
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The upload succeeded but its file metadata could not be read; do not retry automatically", { cause });
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
async function prepareMediaUploadFromSession(agent, attachmentId, signal) {
|
|
1564
|
+
if (agent === void 0) throw new DesignError("INVALID_ARGUMENT", "Session attachments require an active Agent");
|
|
1565
|
+
const ref = findSessionAttachment(agent, attachmentId);
|
|
1566
|
+
if (ref === null) throw new DesignError("INVALID_ARGUMENT", "The attachment is not present in this conversation");
|
|
1567
|
+
return {
|
|
1568
|
+
bytes: (await agent.ctx.attachments.readImage(ref, signal)).data,
|
|
1569
|
+
filename: safeFilename(ref.name ?? `attachment-${String(ref.attachmentId)}`, ref.mediaType),
|
|
1570
|
+
mimeType: ref.mediaType
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
async function prepareMediaUploadFromPath(cwd, inputPath) {
|
|
1574
|
+
if (cwd === void 0 || cwd.trim() === "") throw new DesignError("INVALID_ARGUMENT", "A session workspace is required for path uploads");
|
|
1575
|
+
if (inputPath.trim() === "" || inputPath.length > 32767) throw new DesignError("INVALID_ARGUMENT", "Media path is missing or too long");
|
|
1576
|
+
const workspace = await realpath(cwd);
|
|
1577
|
+
const candidate = resolve(workspace, inputPath);
|
|
1578
|
+
const candidateStats = await lstat(candidate).catch((cause) => {
|
|
1579
|
+
throw new DesignError("INVALID_ARGUMENT", "The media file could not be inspected", { cause });
|
|
1580
|
+
});
|
|
1581
|
+
if (candidateStats.isSymbolicLink()) throw new DesignError("INVALID_ARGUMENT", "Media upload input must not be a symbolic link");
|
|
1582
|
+
const resolved = await realpath(candidate).catch((cause) => {
|
|
1583
|
+
throw new DesignError("INVALID_ARGUMENT", "The media file could not be resolved", { cause });
|
|
1584
|
+
});
|
|
1585
|
+
if (!within(workspace, resolved)) throw new DesignError("INVALID_ARGUMENT", "Media path must stay inside the session workspace");
|
|
1586
|
+
const before = candidateStats;
|
|
1587
|
+
if (!before.isFile() || before.isSymbolicLink()) throw new DesignError("INVALID_ARGUMENT", "Media upload input must be a regular file");
|
|
1588
|
+
if (before.size < 1 || before.size > MAX_MEDIA_FILE_BYTES) throw new DesignError("INVALID_ARGUMENT", "Media file size must be between 1 byte and 16 MiB");
|
|
1589
|
+
const handle = await open(resolved, "r");
|
|
1590
|
+
let bytes;
|
|
1591
|
+
try {
|
|
1592
|
+
const opened = await handle.stat();
|
|
1593
|
+
if (!opened.isFile() || opened.size !== before.size || opened.dev !== before.dev || opened.ino !== before.ino) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being opened");
|
|
1594
|
+
bytes = await handle.readFile();
|
|
1595
|
+
} finally {
|
|
1596
|
+
await handle.close();
|
|
1597
|
+
}
|
|
1598
|
+
if (bytes.byteLength !== before.size) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being read");
|
|
1599
|
+
const mimeType = detectImageMimeType(bytes);
|
|
1600
|
+
return {
|
|
1601
|
+
bytes,
|
|
1602
|
+
filename: safeFilename(basename(resolved), mimeType),
|
|
1603
|
+
mimeType
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function findSessionAttachment(agent, attachmentId) {
|
|
1607
|
+
for (const message of agent.session.deriveMessages().toReversed()) for (const block of message.content.toReversed()) if (block.type === "image" && String(block.attachment.attachmentId) === attachmentId) return block.attachment;
|
|
1608
|
+
return null;
|
|
1609
|
+
}
|
|
1610
|
+
function within(root, candidate) {
|
|
1611
|
+
const suffix = relative(root, candidate);
|
|
1612
|
+
return suffix === "" || !suffix.startsWith("..") && !isAbsolute(suffix);
|
|
1613
|
+
}
|
|
1614
|
+
function detectImageMimeType(bytes) {
|
|
1615
|
+
if (bytes.length >= 8 && bytes.slice(0, 8).every((value, index) => value === [
|
|
1616
|
+
137,
|
|
1617
|
+
80,
|
|
1618
|
+
78,
|
|
1619
|
+
71,
|
|
1620
|
+
13,
|
|
1621
|
+
10,
|
|
1622
|
+
26,
|
|
1623
|
+
10
|
|
1624
|
+
][index])) return "image/png";
|
|
1625
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
1626
|
+
const ascii = (start, end) => new TextDecoder("ascii").decode(bytes.slice(start, end));
|
|
1627
|
+
if (bytes.length >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") return "image/webp";
|
|
1628
|
+
throw new DesignError("INVALID_ARGUMENT", "Unsupported media type; use PNG, JPEG, or WebP");
|
|
1629
|
+
}
|
|
1630
|
+
function safeFilename(value, mimeType) {
|
|
1631
|
+
const extension = mimeType === "image/png" ? ".png" : mimeType === "image/webp" ? ".webp" : ".jpg";
|
|
1632
|
+
const headerSafe = value.normalize("NFC").replaceAll(/["\\/]/gu, "_");
|
|
1633
|
+
const cleaned = Array.from(headerSafe, (character) => {
|
|
1634
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
1635
|
+
return codePoint <= 31 || codePoint === 127 ? "_" : character;
|
|
1636
|
+
}).join("").trim().slice(0, 180);
|
|
1637
|
+
return cleaned === "" ? `upload${extension}` : cleaned;
|
|
1638
|
+
}
|
|
1639
|
+
function parseMediaUploadRecord(payload) {
|
|
1640
|
+
const root = record$1(payload);
|
|
1641
|
+
const data = record$1(root?.data) ?? root;
|
|
1642
|
+
if (data === null || typeof data.file_id !== "string" || !SAFE_FILE_ID.test(data.file_id) || typeof data.type !== "string" || typeof data.filename !== "string" || typeof data.size !== "number" || !Number.isSafeInteger(data.size) || data.size < 0 || typeof data.url !== "string") throw new TypeError("Media upload response is malformed");
|
|
1643
|
+
const url = new URL(data.url);
|
|
1644
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "") throw new TypeError("Media upload URL is unsafe");
|
|
1645
|
+
const createdAt = typeof data.created_at === "string" && Number.isFinite(Date.parse(data.created_at)) ? new Date(data.created_at).toISOString() : typeof data.created_at === "number" && Number.isFinite(data.created_at) ? new Date(data.created_at).toISOString() : null;
|
|
1646
|
+
return {
|
|
1647
|
+
fileId: data.file_id,
|
|
1648
|
+
type: data.type.slice(0, 128),
|
|
1649
|
+
url: url.href,
|
|
1650
|
+
filename: data.filename.slice(0, 512),
|
|
1651
|
+
size: data.size,
|
|
1652
|
+
createdAt
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function record$1(value) {
|
|
1495
1656
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1496
1657
|
}
|
|
1497
1658
|
//#endregion
|
|
@@ -2065,11 +2226,11 @@ function parsePlannerResponse(text, status) {
|
|
|
2065
2226
|
} catch (_error) {
|
|
2066
2227
|
throw plannerError("PLANNER_RESPONSE_INVALID", "Design planner returned malformed JSON", status);
|
|
2067
2228
|
}
|
|
2068
|
-
const root = asRecord$
|
|
2229
|
+
const root = asRecord$4(envelope);
|
|
2069
2230
|
const choices = root === null ? null : root.choices;
|
|
2070
|
-
const choice = Array.isArray(choices) ? asRecord$
|
|
2231
|
+
const choice = Array.isArray(choices) ? asRecord$4(choices[0]) : null;
|
|
2071
2232
|
if (choice?.finish_reason === "length") throw plannerError("PLANNER_RESPONSE_INVALID", "Design planner response was incomplete", status);
|
|
2072
|
-
const message = asRecord$
|
|
2233
|
+
const message = asRecord$4(choice?.message);
|
|
2073
2234
|
if (typeof message?.content !== "string") throw plannerError("PLANNER_RESPONSE_INVALID", "Design planner response did not contain structured output", status);
|
|
2074
2235
|
let content;
|
|
2075
2236
|
try {
|
|
@@ -2080,7 +2241,7 @@ function parsePlannerResponse(text, status) {
|
|
|
2080
2241
|
return parseWireResult(content, status);
|
|
2081
2242
|
}
|
|
2082
2243
|
function parseWireResult(value, status) {
|
|
2083
|
-
const root = asRecord$
|
|
2244
|
+
const root = asRecord$4(value);
|
|
2084
2245
|
if (root === null || !hasExactKeys(root, [
|
|
2085
2246
|
"set",
|
|
2086
2247
|
"unset",
|
|
@@ -2091,7 +2252,7 @@ function parseWireResult(value, status) {
|
|
|
2091
2252
|
if (clarification !== null && (typeof clarification !== "string" || clarification.trim() === "" || clarification.length > MAX_CLARIFICATION_LENGTH)) throw invalidWireResult(status);
|
|
2092
2253
|
return {
|
|
2093
2254
|
set: root.set.map((entry) => {
|
|
2094
|
-
const item = asRecord$
|
|
2255
|
+
const item = asRecord$4(entry);
|
|
2095
2256
|
if (item === null || !hasExactKeys(item, ["path", "value"]) || typeof item.path !== "string") throw invalidWireResult(status);
|
|
2096
2257
|
return {
|
|
2097
2258
|
path: item.path,
|
|
@@ -2185,7 +2346,7 @@ async function cancelBody(response) {
|
|
|
2185
2346
|
function statusError(status) {
|
|
2186
2347
|
switch (status) {
|
|
2187
2348
|
case 401: return plannerError("PLANNER_UNAUTHORIZED", "Design planner authentication was rejected", status);
|
|
2188
|
-
case 402: return plannerError("PLANNER_BILLING_BLOCKED", "Design planner
|
|
2349
|
+
case 402: return plannerError("PLANNER_BILLING_BLOCKED", "The Design planner request is unavailable for the current account", status);
|
|
2189
2350
|
case 403: return plannerError("PLANNER_FORBIDDEN", "Design planner access was forbidden", status);
|
|
2190
2351
|
case 429: return plannerError("PLANNER_RATE_LIMITED", "Design planner is rate limited", status);
|
|
2191
2352
|
default:
|
|
@@ -2194,7 +2355,7 @@ function statusError(status) {
|
|
|
2194
2355
|
}
|
|
2195
2356
|
}
|
|
2196
2357
|
function submitUnknown(status) {
|
|
2197
|
-
return plannerError("SUBMIT_UNKNOWN", "The
|
|
2358
|
+
return plannerError("SUBMIT_UNKNOWN", "The Design planner outcome is unknown; do not retry automatically", status);
|
|
2198
2359
|
}
|
|
2199
2360
|
function validateApiKey$1(value) {
|
|
2200
2361
|
if (typeof value !== "string" || value.trim() === "" || value.length > 16384 || /[\r\n]/u.test(value)) throw plannerError("MISSING_API_KEY", "A Modellix API key is required");
|
|
@@ -2209,7 +2370,7 @@ function safeStringify(value) {
|
|
|
2209
2370
|
throw plannerError("INVALID_ARGUMENT", "Design planner input must be JSON-compatible");
|
|
2210
2371
|
}
|
|
2211
2372
|
}
|
|
2212
|
-
function asRecord$
|
|
2373
|
+
function asRecord$4(value) {
|
|
2213
2374
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2214
2375
|
}
|
|
2215
2376
|
function invalidWireResult(status) {
|
|
@@ -2301,11 +2462,11 @@ var PredictionClient = class {
|
|
|
2301
2462
|
model,
|
|
2302
2463
|
...requestId === null ? {} : { requestId }
|
|
2303
2464
|
});
|
|
2304
|
-
throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
2465
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The generation request outcome is unknown; do not retry automatically", { cause });
|
|
2305
2466
|
}
|
|
2306
2467
|
if (response.redirected) {
|
|
2307
2468
|
response.body?.cancel().catch(() => void 0);
|
|
2308
|
-
throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
2469
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The generation request outcome is unknown; do not retry automatically", { status: response.status });
|
|
2309
2470
|
}
|
|
2310
2471
|
if (!response.ok) {
|
|
2311
2472
|
const ambiguous = isAmbiguousSubmitStatus(response.status);
|
|
@@ -2317,21 +2478,21 @@ var PredictionClient = class {
|
|
|
2317
2478
|
status: response.status,
|
|
2318
2479
|
...requestId === null ? {} : { requestId }
|
|
2319
2480
|
});
|
|
2320
|
-
throw new DesignError(ambiguous ? "SUBMIT_UNKNOWN" : "SUBMIT_REJECTED", ambiguous ? "The
|
|
2481
|
+
throw new DesignError(ambiguous ? "SUBMIT_UNKNOWN" : "SUBMIT_REJECTED", ambiguous ? "The generation request outcome is unknown; do not retry automatically" : `The generation request was rejected with HTTP ${response.status}`, { status: response.status });
|
|
2321
2482
|
}
|
|
2322
2483
|
let payload;
|
|
2323
2484
|
try {
|
|
2324
2485
|
payload = await readBoundedResponseJson(response, MAX_PREDICTION_RESPONSE_BYTES, deadline.signal);
|
|
2325
2486
|
} catch (cause) {
|
|
2326
|
-
throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
2487
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The generation request succeeded but its task identifier could not be read", { cause });
|
|
2327
2488
|
}
|
|
2328
2489
|
let task;
|
|
2329
2490
|
try {
|
|
2330
2491
|
task = parsePredictionTask(payload);
|
|
2331
2492
|
} catch (cause) {
|
|
2332
|
-
throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
2493
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The generation request succeeded but its task response was invalid", { cause });
|
|
2333
2494
|
}
|
|
2334
|
-
if (task === null) throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
2495
|
+
if (task === null) throw new DesignError("SUBMIT_UNKNOWN", "The generation request response did not contain a valid task identifier");
|
|
2335
2496
|
this.#log({
|
|
2336
2497
|
level: "info",
|
|
2337
2498
|
event: "design.submit.accepted",
|
|
@@ -2449,14 +2610,14 @@ function validateSubmitEndpoint(endpoint, modelSlug) {
|
|
|
2449
2610
|
return url;
|
|
2450
2611
|
}
|
|
2451
2612
|
function parsePredictionTask(payload, expectedTaskId) {
|
|
2452
|
-
const root = asRecord$
|
|
2613
|
+
const root = asRecord$3(payload);
|
|
2453
2614
|
if (root === null) return null;
|
|
2454
|
-
const envelope = asRecord$
|
|
2455
|
-
const data = asRecord$
|
|
2615
|
+
const envelope = asRecord$3(root.data) ?? root;
|
|
2616
|
+
const data = asRecord$3(envelope.task) ?? asRecord$3(root.task) ?? envelope;
|
|
2456
2617
|
const taskId = safeId(data.task_id) ?? safeId(data.taskId) ?? safeId(data.id) ?? safeId(root.task_id) ?? safeId(root.taskId) ?? (expectedTaskId === void 0 ? null : safeId(expectedTaskId));
|
|
2457
2618
|
if (taskId === null) return null;
|
|
2458
2619
|
if (expectedTaskId !== void 0 && taskId !== expectedTaskId) throw new DesignError("UNEXPECTED_RESPONSE", "Task status response identifier does not match the requested task");
|
|
2459
|
-
const result = asRecord$
|
|
2620
|
+
const result = asRecord$3(data.result) ?? asRecord$3(root.result);
|
|
2460
2621
|
const expiresAt = timestamp(data.result_expires_at ?? data.resultExpiresAt ?? data.expires_at ?? data.expiresAt ?? result?.result_expires_at ?? result?.resultExpiresAt ?? result?.expires_at ?? result?.expiresAt);
|
|
2461
2622
|
const resourceContainer = data.result_resources ?? data.resources ?? result?.result_resources ?? result?.resources ?? data.output ?? result?.output ?? root.resources ?? root.output ?? result;
|
|
2462
2623
|
return {
|
|
@@ -2473,7 +2634,7 @@ function parseResources(value, inheritedExpiresAt = null) {
|
|
|
2473
2634
|
}
|
|
2474
2635
|
function parseResourcesAtDepth(value, inheritedExpiresAt, depth) {
|
|
2475
2636
|
if (depth > DESIGN_WIRE_LIMITS.maxJsonDepth) throw new DesignError("UNEXPECTED_RESPONSE", "Prediction resources exceed the structural depth limit");
|
|
2476
|
-
const envelope = asRecord$
|
|
2637
|
+
const envelope = asRecord$3(value);
|
|
2477
2638
|
if (envelope !== null && !hasResourceUrl(envelope)) {
|
|
2478
2639
|
const nested = envelope.resources ?? envelope.result_resources ?? envelope.output;
|
|
2479
2640
|
if (nested !== void 0 && nested !== value) return parseResourcesAtDepth(nested, inheritedExpiresAt, depth + 1);
|
|
@@ -2503,7 +2664,7 @@ function parseResourcesAtDepth(value, inheritedExpiresAt, depth) {
|
|
|
2503
2664
|
type: kind,
|
|
2504
2665
|
url: candidate
|
|
2505
2666
|
}, inheritedExpiresAt) : parseResource({
|
|
2506
|
-
...asRecord$
|
|
2667
|
+
...asRecord$3(candidate),
|
|
2507
2668
|
type: kind
|
|
2508
2669
|
}, inheritedExpiresAt);
|
|
2509
2670
|
if (resource !== null) appendResource(grouped, resource);
|
|
@@ -2535,7 +2696,7 @@ function parseResource(value, inheritedExpiresAt) {
|
|
|
2535
2696
|
expiresAt: inheritedExpiresAt
|
|
2536
2697
|
};
|
|
2537
2698
|
}
|
|
2538
|
-
const resource = asRecord$
|
|
2699
|
+
const resource = asRecord$3(value);
|
|
2539
2700
|
if (resource === null) return null;
|
|
2540
2701
|
const url = safeHttpsUrl$1(resource.url ?? resource.uri ?? resource.image_url ?? resource.video_url ?? resource.audio_url);
|
|
2541
2702
|
const mimeType = boundedString(resource.mime_type ?? resource.mimeType);
|
|
@@ -2645,7 +2806,7 @@ function retryAfterMs(value, nowMs) {
|
|
|
2645
2806
|
function isAbortError$1(value) {
|
|
2646
2807
|
return value instanceof Error && value.name === "AbortError";
|
|
2647
2808
|
}
|
|
2648
|
-
function asRecord$
|
|
2809
|
+
function asRecord$3(value) {
|
|
2649
2810
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2650
2811
|
}
|
|
2651
2812
|
var TaskReadFailure = class extends Error {
|
|
@@ -2829,7 +2990,7 @@ const UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set([
|
|
|
2829
2990
|
"prototype"
|
|
2830
2991
|
]);
|
|
2831
2992
|
function parseDesignSchema(input, limits = {}) {
|
|
2832
|
-
const document = asRecord$
|
|
2993
|
+
const document = asRecord$2(input);
|
|
2833
2994
|
if (document === null) throw new DesignError("SCHEMA_INVALID", "Schema input must be an object");
|
|
2834
2995
|
const normalizedLimits = normalizeLimits(limits);
|
|
2835
2996
|
switch (inspectJsonBudget(document, {
|
|
@@ -2885,14 +3046,14 @@ function parseDesignSchema(input, limits = {}) {
|
|
|
2885
3046
|
detectReferenceCycles(discovered.schema, "#", context, [], 0);
|
|
2886
3047
|
const root = expandSchema(discovered.schema, "#", context, [], 0);
|
|
2887
3048
|
const required = stringSet(root.required);
|
|
2888
|
-
const properties = asRecord$
|
|
3049
|
+
const properties = asRecord$2(root.properties);
|
|
2889
3050
|
let fields;
|
|
2890
3051
|
if (properties === null) {
|
|
2891
3052
|
const rootField = compileField("body", "", root, true, context, 0);
|
|
2892
3053
|
fields = rootField === null ? [] : [rootField];
|
|
2893
3054
|
} else {
|
|
2894
3055
|
fields = Object.entries(properties).map(([key, schema], index) => {
|
|
2895
|
-
const object = asRecord$
|
|
3056
|
+
const object = asRecord$2(schema);
|
|
2896
3057
|
return object === null ? invalidProperty(key, index, context) : compileField(key, `/${escapePointerToken$1(key)}`, object, required.has(key), context, 1, index);
|
|
2897
3058
|
}).filter((field) => field !== null);
|
|
2898
3059
|
fields = sortFields(fields);
|
|
@@ -2911,7 +3072,7 @@ function parseDesignSchema(input, limits = {}) {
|
|
|
2911
3072
|
};
|
|
2912
3073
|
}
|
|
2913
3074
|
function discoverPostBody(document, diagnostics) {
|
|
2914
|
-
const rootPost = asRecord$
|
|
3075
|
+
const rootPost = asRecord$2(document.post);
|
|
2915
3076
|
if (rootPost !== null) {
|
|
2916
3077
|
const schema = requestBodySchema(rootPost);
|
|
2917
3078
|
if (schema !== null) return {
|
|
@@ -2923,11 +3084,11 @@ function discoverPostBody(document, diagnostics) {
|
|
|
2923
3084
|
operationPath: null
|
|
2924
3085
|
};
|
|
2925
3086
|
}
|
|
2926
|
-
const paths = asRecord$
|
|
3087
|
+
const paths = asRecord$2(document.paths);
|
|
2927
3088
|
if (paths !== null) {
|
|
2928
3089
|
const candidates = [];
|
|
2929
3090
|
for (const path of Object.keys(paths).sort()) {
|
|
2930
|
-
const post = asRecord$
|
|
3091
|
+
const post = asRecord$2(asRecord$2(paths[path])?.post);
|
|
2931
3092
|
if (post === null) continue;
|
|
2932
3093
|
const schema = requestBodySchema(post);
|
|
2933
3094
|
if (schema !== null) candidates.push({
|
|
@@ -2940,7 +3101,7 @@ function discoverPostBody(document, diagnostics) {
|
|
|
2940
3101
|
path: "#/paths",
|
|
2941
3102
|
keyword: "post",
|
|
2942
3103
|
blocking: true,
|
|
2943
|
-
message: "Multiple POST bodies were found; Design cannot safely select a
|
|
3104
|
+
message: "Multiple POST bodies were found; Design cannot safely select a submission operation"
|
|
2944
3105
|
});
|
|
2945
3106
|
if (candidates[0] !== void 0) return candidates[0];
|
|
2946
3107
|
}
|
|
@@ -2956,8 +3117,8 @@ function discoverPostBody(document, diagnostics) {
|
|
|
2956
3117
|
return null;
|
|
2957
3118
|
}
|
|
2958
3119
|
function requestBodySchema(operation) {
|
|
2959
|
-
const content = asRecord$
|
|
2960
|
-
return asRecord$
|
|
3120
|
+
const content = asRecord$2(asRecord$2(operation.requestBody)?.content);
|
|
3121
|
+
return asRecord$2((asRecord$2(content?.["application/json"]) ?? asRecord$2(content?.["application/*+json"]))?.schema);
|
|
2961
3122
|
}
|
|
2962
3123
|
function looksLikeJsonSchema(value) {
|
|
2963
3124
|
return [
|
|
@@ -2996,14 +3157,14 @@ function compileField(key, path, rawSchema, required, context, depth, _sourceInd
|
|
|
2996
3157
|
reportUnsupportedKeywords(schema, path, context);
|
|
2997
3158
|
const nullable = schema.nullable === true || schemaTypes(schema).includes("null");
|
|
2998
3159
|
const kind = inferKind(schema, key);
|
|
2999
|
-
const nestedProperties = asRecord$
|
|
3160
|
+
const nestedProperties = asRecord$2(schema.properties);
|
|
3000
3161
|
const nestedRequired = stringSet(schema.required);
|
|
3001
3162
|
const properties = nestedProperties === null ? [] : sortFields(Object.entries(nestedProperties).map(([childKey, childSchema], index) => {
|
|
3002
|
-
const object = asRecord$
|
|
3163
|
+
const object = asRecord$2(childSchema);
|
|
3003
3164
|
if (object === null) return invalidProperty(childKey, index, context, path);
|
|
3004
3165
|
return compileField(childKey, `${path}/${escapePointerToken$1(childKey)}`, object, nestedRequired.has(childKey), context, depth + 1, index);
|
|
3005
3166
|
}).filter((field) => field !== null));
|
|
3006
|
-
const itemSchema = asRecord$
|
|
3167
|
+
const itemSchema = asRecord$2(schema.items);
|
|
3007
3168
|
const item = itemSchema === null ? null : compileField("items", `${path}/*`, itemSchema, false, context, depth + 1);
|
|
3008
3169
|
const variants = compileVariants(key, path, schema, required, context, depth);
|
|
3009
3170
|
const defaultValue = jsonCompatible(schema.default);
|
|
@@ -3035,7 +3196,7 @@ function compileVariants(key, path, schema, required, context, depth) {
|
|
|
3035
3196
|
const candidates = schema[combinator];
|
|
3036
3197
|
if (!Array.isArray(candidates)) continue;
|
|
3037
3198
|
candidates.forEach((candidate, index) => {
|
|
3038
|
-
const branch = asRecord$
|
|
3199
|
+
const branch = asRecord$2(candidate);
|
|
3039
3200
|
if (branch === null) {
|
|
3040
3201
|
context.diagnostics.push({
|
|
3041
3202
|
code: "INVALID_KEYWORD",
|
|
@@ -3106,7 +3267,7 @@ function expandSchema(rawSchema, path, context, refStack, depth) {
|
|
|
3106
3267
|
if (Array.isArray(schema.allOf)) {
|
|
3107
3268
|
let merged = omitKeys(schema, ["allOf"]);
|
|
3108
3269
|
schema.allOf.forEach((candidate, index) => {
|
|
3109
|
-
const branch = asRecord$
|
|
3270
|
+
const branch = asRecord$2(candidate);
|
|
3110
3271
|
if (branch === null) {
|
|
3111
3272
|
context.diagnostics.push({
|
|
3112
3273
|
code: "INVALID_KEYWORD",
|
|
@@ -3144,12 +3305,12 @@ function detectReferenceCycles(schema, path, context, refStack, depth) {
|
|
|
3144
3305
|
if (target !== null) detectReferenceCycles(target, reference, context, [...refStack, reference], depth + 1);
|
|
3145
3306
|
}
|
|
3146
3307
|
}
|
|
3147
|
-
const properties = asRecord$
|
|
3308
|
+
const properties = asRecord$2(schema.properties);
|
|
3148
3309
|
for (const [key, value] of Object.entries(properties ?? {})) {
|
|
3149
|
-
const child = asRecord$
|
|
3310
|
+
const child = asRecord$2(value);
|
|
3150
3311
|
if (child !== null) detectReferenceCycles(child, `${path}/properties/${escapePointerToken$1(key)}`, context, refStack, depth + 1);
|
|
3151
3312
|
}
|
|
3152
|
-
const item = asRecord$
|
|
3313
|
+
const item = asRecord$2(schema.items);
|
|
3153
3314
|
if (item !== null) detectReferenceCycles(item, `${path}/items`, context, refStack, depth + 1);
|
|
3154
3315
|
for (const combinator of [
|
|
3155
3316
|
"allOf",
|
|
@@ -3159,7 +3320,7 @@ function detectReferenceCycles(schema, path, context, refStack, depth) {
|
|
|
3159
3320
|
const candidates = schema[combinator];
|
|
3160
3321
|
if (!Array.isArray(candidates)) continue;
|
|
3161
3322
|
candidates.forEach((candidate, index) => {
|
|
3162
|
-
const child = asRecord$
|
|
3323
|
+
const child = asRecord$2(candidate);
|
|
3163
3324
|
if (child !== null) detectReferenceCycles(child, `${path}/${combinator}/${index}`, context, refStack, depth + 1);
|
|
3164
3325
|
});
|
|
3165
3326
|
}
|
|
@@ -3178,13 +3339,13 @@ function mergeSchemas(left, right, path, context) {
|
|
|
3178
3339
|
...left,
|
|
3179
3340
|
...right
|
|
3180
3341
|
};
|
|
3181
|
-
const leftProperties = asRecord$
|
|
3182
|
-
const rightProperties = asRecord$
|
|
3342
|
+
const leftProperties = asRecord$2(left.properties);
|
|
3343
|
+
const rightProperties = asRecord$2(right.properties);
|
|
3183
3344
|
if (leftProperties !== null || rightProperties !== null) {
|
|
3184
3345
|
const properties = Object.assign(Object.create(null), leftProperties ?? {});
|
|
3185
3346
|
for (const [key, value] of Object.entries(rightProperties ?? {})) {
|
|
3186
|
-
const existing = asRecord$
|
|
3187
|
-
const incoming = asRecord$
|
|
3347
|
+
const existing = asRecord$2(properties[key]);
|
|
3348
|
+
const incoming = asRecord$2(value);
|
|
3188
3349
|
properties[key] = existing !== null && incoming !== null ? { allOf: [existing, incoming] } : value;
|
|
3189
3350
|
}
|
|
3190
3351
|
result.properties = properties;
|
|
@@ -3229,12 +3390,12 @@ function resolvePointer(root, reference) {
|
|
|
3229
3390
|
if (!/^(?:0|[1-9]\d*)$/.test(token)) return null;
|
|
3230
3391
|
current = current[Number(token)];
|
|
3231
3392
|
} else {
|
|
3232
|
-
const object = asRecord$
|
|
3393
|
+
const object = asRecord$2(current);
|
|
3233
3394
|
if (object === null || !Object.hasOwn(object, token)) return null;
|
|
3234
3395
|
current = object[token];
|
|
3235
3396
|
}
|
|
3236
3397
|
}
|
|
3237
|
-
return asRecord$
|
|
3398
|
+
return asRecord$2(current);
|
|
3238
3399
|
}
|
|
3239
3400
|
function decodePointerToken(value) {
|
|
3240
3401
|
if (/~(?:[^01]|$)/.test(value)) return null;
|
|
@@ -3267,8 +3428,8 @@ function inferKind(schema, key) {
|
|
|
3267
3428
|
if (detectMediaKind(schema) !== null) return "media";
|
|
3268
3429
|
const type = schemaTypes(schema).filter((type) => type !== "null")[0];
|
|
3269
3430
|
if (type === "string" || type === "number" || type === "integer" || type === "boolean" || type === "object" || type === "array") return type;
|
|
3270
|
-
if (asRecord$
|
|
3271
|
-
if (asRecord$
|
|
3431
|
+
if (asRecord$2(schema.properties) !== null) return "object";
|
|
3432
|
+
if (asRecord$2(schema.items) !== null) return "array";
|
|
3272
3433
|
if (Object.hasOwn(schema, "default")) {
|
|
3273
3434
|
const defaultKind = scalarKind(schema.default);
|
|
3274
3435
|
if (defaultKind !== null) return defaultKind;
|
|
@@ -3330,6 +3491,7 @@ function safePattern(value) {
|
|
|
3330
3491
|
return typeof value === "string" && value.length <= 512 ? value : null;
|
|
3331
3492
|
}
|
|
3332
3493
|
function isSafeRegularExpression(pattern) {
|
|
3494
|
+
if (pattern === "^\\d+\\*\\d+$") return true;
|
|
3333
3495
|
if (!pattern.startsWith("^") || !pattern.endsWith("$") || pattern.length < 2) return false;
|
|
3334
3496
|
let index = 1;
|
|
3335
3497
|
let variableQuantifiers = 0;
|
|
@@ -3504,7 +3666,7 @@ function jsonCompatible(value) {
|
|
|
3504
3666
|
const items = value.map(jsonCompatible);
|
|
3505
3667
|
return items.every((item) => item !== void 0) ? items : void 0;
|
|
3506
3668
|
}
|
|
3507
|
-
const object = asRecord$
|
|
3669
|
+
const object = asRecord$2(value);
|
|
3508
3670
|
if (object === null) return;
|
|
3509
3671
|
const result = Object.create(null);
|
|
3510
3672
|
for (const [key, item] of Object.entries(object)) {
|
|
@@ -3519,7 +3681,7 @@ function stableStringify(value) {
|
|
|
3519
3681
|
}
|
|
3520
3682
|
function sortJson(value) {
|
|
3521
3683
|
if (Array.isArray(value)) return value.map(sortJson);
|
|
3522
|
-
const object = asRecord$
|
|
3684
|
+
const object = asRecord$2(value);
|
|
3523
3685
|
if (object === null) {
|
|
3524
3686
|
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
|
|
3525
3687
|
throw new TypeError("Value is not JSON-compatible");
|
|
@@ -3529,7 +3691,7 @@ function sortJson(value) {
|
|
|
3529
3691
|
function sha256(value) {
|
|
3530
3692
|
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
3531
3693
|
}
|
|
3532
|
-
function asRecord$
|
|
3694
|
+
function asRecord$2(value) {
|
|
3533
3695
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
3534
3696
|
}
|
|
3535
3697
|
function boundedText(value) {
|
|
@@ -3571,9 +3733,10 @@ var DesignTaskRepository = class {
|
|
|
3571
3733
|
this.#maxEvents = boundedInteger(options.maxEvents ?? DEFAULT_MAX_EVENTS, 1, 1e5, "maxEvents");
|
|
3572
3734
|
this.#maxBytes = boundedInteger(options.maxBytes ?? DEFAULT_MAX_BYTES, 1024, 32 * 1024 * 1024, "maxBytes");
|
|
3573
3735
|
}
|
|
3574
|
-
async recordSubmitIntent(requestId, modelSlug, credentialEpoch) {
|
|
3736
|
+
async recordSubmitIntent(requestId, modelSlug, sessionId, credentialEpoch) {
|
|
3575
3737
|
requireId(requestId, "requestId");
|
|
3576
3738
|
requireModelSlug(modelSlug);
|
|
3739
|
+
requireId(sessionId, "sessionId");
|
|
3577
3740
|
requireEpoch(credentialEpoch);
|
|
3578
3741
|
const wal = await this.#loadWal();
|
|
3579
3742
|
if (replayDesignWal(wal.events).some((record) => record.requestId === requestId)) throw new DesignError("STORAGE_INVALID", "requestId already exists in the Design WAL");
|
|
@@ -3583,6 +3746,7 @@ var DesignTaskRepository = class {
|
|
|
3583
3746
|
timestamp: this.#clock.now(),
|
|
3584
3747
|
requestId,
|
|
3585
3748
|
modelSlug,
|
|
3749
|
+
sessionId,
|
|
3586
3750
|
credentialEpoch
|
|
3587
3751
|
});
|
|
3588
3752
|
}
|
|
@@ -3715,11 +3879,13 @@ function replayDesignWal(events) {
|
|
|
3715
3879
|
case "submit-intent":
|
|
3716
3880
|
requireId(event.requestId, "requestId");
|
|
3717
3881
|
requireModelSlug(event.modelSlug);
|
|
3882
|
+
if (event.sessionId !== void 0) requireId(event.sessionId, "sessionId");
|
|
3718
3883
|
if (event.credentialEpoch !== void 0) requireEpoch(event.credentialEpoch);
|
|
3719
3884
|
if (records.has(event.requestId)) throw new DesignError("STORAGE_INVALID", "Design WAL has a duplicate requestId");
|
|
3720
3885
|
records.set(event.requestId, {
|
|
3721
3886
|
requestId: event.requestId,
|
|
3722
3887
|
modelSlug: event.modelSlug,
|
|
3888
|
+
sessionId: event.sessionId ?? null,
|
|
3723
3889
|
credentialEpoch: event.credentialEpoch ?? null,
|
|
3724
3890
|
taskId: null,
|
|
3725
3891
|
state: "submitting",
|
|
@@ -3863,6 +4029,7 @@ function checkpointRecords(records, now) {
|
|
|
3863
4029
|
timestamp: record.createdAt,
|
|
3864
4030
|
requestId: record.requestId,
|
|
3865
4031
|
modelSlug: record.modelSlug,
|
|
4032
|
+
...record.sessionId === null ? {} : { sessionId: record.sessionId },
|
|
3866
4033
|
...record.credentialEpoch === null ? {} : { credentialEpoch: record.credentialEpoch }
|
|
3867
4034
|
});
|
|
3868
4035
|
if (record.taskId !== null) {
|
|
@@ -3913,7 +4080,7 @@ function shouldRetainCheckpoint(record, now) {
|
|
|
3913
4080
|
return record.updatedAt + DEFAULT_RESULT_TTL_MS > now;
|
|
3914
4081
|
}
|
|
3915
4082
|
function parseWal(value, maxEvents) {
|
|
3916
|
-
const root = asRecord(value);
|
|
4083
|
+
const root = asRecord$1(value);
|
|
3917
4084
|
if (root?.version !== 1 || !Array.isArray(root.events) || root.events.length > maxEvents) throw new DesignError("STORAGE_INVALID", "Stored Design WAL has an invalid envelope");
|
|
3918
4085
|
return {
|
|
3919
4086
|
version: 1,
|
|
@@ -3998,7 +4165,7 @@ function boundedInteger(value, minimum, maximum, field) {
|
|
|
3998
4165
|
if (!Number.isInteger(value) || value < minimum || value > maximum) throw new DesignError("INVALID_ARGUMENT", `${field} must be an integer from ${minimum} through ${maximum}`);
|
|
3999
4166
|
return value;
|
|
4000
4167
|
}
|
|
4001
|
-
function asRecord(value) {
|
|
4168
|
+
function asRecord$1(value) {
|
|
4002
4169
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
4003
4170
|
}
|
|
4004
4171
|
//#endregion
|
|
@@ -4913,7 +5080,10 @@ function createModellixWebProviders(options) {
|
|
|
4913
5080
|
}
|
|
4914
5081
|
/** Registers only providers; the Harness-owned web_search/web_fetch Tools remain untouched. */
|
|
4915
5082
|
function registerModellixWebProviders(registry, options) {
|
|
4916
|
-
|
|
5083
|
+
return registerModellixWebProviderInstances(registry, createModellixWebProviders(options));
|
|
5084
|
+
}
|
|
5085
|
+
/** Register already-created providers so Host tools can share the exact same instances. */
|
|
5086
|
+
function registerModellixWebProviderInstances(registry, providers) {
|
|
4917
5087
|
const disposeSearch = registry.registerSearchProvider(providers.search);
|
|
4918
5088
|
let disposeFetch;
|
|
4919
5089
|
try {
|
|
@@ -5138,14 +5308,14 @@ function redactedCause(error) {
|
|
|
5138
5308
|
function messageFor(contract) {
|
|
5139
5309
|
switch (contract.code) {
|
|
5140
5310
|
case "MODELLIX_API_KEY_INVALID": return "Modellix rejected the configured API Key";
|
|
5141
|
-
case "MODELLIX_BILLING_BLOCKED": return "The Modellix account cannot run this
|
|
5311
|
+
case "MODELLIX_BILLING_BLOCKED": return "The Modellix account cannot run this Web request";
|
|
5142
5312
|
case "MODELLIX_RATE_LIMITED": return "The Modellix Web request was rate limited";
|
|
5143
5313
|
case "MODELLIX_CANCELED": return "The Modellix Web request was canceled";
|
|
5144
5314
|
case "MODELLIX_OFFLINE": return "The Modellix Web service could not be reached";
|
|
5145
5315
|
case "MODELLIX_TIMEOUT": return "The Modellix Web request timed out";
|
|
5146
5316
|
case "MODELLIX_SERVER_ERROR": return "The Modellix Web service is temporarily unavailable";
|
|
5147
5317
|
case "MODELLIX_BAD_REQUEST": return "The Modellix Web request is invalid";
|
|
5148
|
-
case "MODELLIX_SUBMIT_UNKNOWN": return "The
|
|
5318
|
+
case "MODELLIX_SUBMIT_UNKNOWN": return "The Modellix Web request outcome is unknown; it was not retried";
|
|
5149
5319
|
default: return "The Modellix Web request failed";
|
|
5150
5320
|
}
|
|
5151
5321
|
}
|
|
@@ -5508,7 +5678,7 @@ var DesignHostController = class {
|
|
|
5508
5678
|
const body = buildInvocationBody(schema, parameters);
|
|
5509
5679
|
const requestId = `request_${randomUUID().replaceAll("-", "")}`;
|
|
5510
5680
|
signal?.throwIfAborted();
|
|
5511
|
-
await this.#repository.recordSubmitIntent(requestId, input.modelId, credential.credentialEpoch);
|
|
5681
|
+
await this.#repository.recordSubmitIntent(requestId, input.modelId, input.sessionId, credential.credentialEpoch);
|
|
5512
5682
|
session.draft = {
|
|
5513
5683
|
modelId: input.modelId,
|
|
5514
5684
|
revision: draft.revision + 1,
|
|
@@ -5548,7 +5718,7 @@ var DesignHostController = class {
|
|
|
5548
5718
|
try {
|
|
5549
5719
|
await this.#repository.markSubmitUnknown(requestId);
|
|
5550
5720
|
} catch {}
|
|
5551
|
-
throw new DesignError("SUBMIT_UNKNOWN", "The
|
|
5721
|
+
throw new DesignError("SUBMIT_UNKNOWN", "The generation request succeeded but its task could not be persisted", { cause: firstPersistenceError });
|
|
5552
5722
|
}
|
|
5553
5723
|
}
|
|
5554
5724
|
return this.#snapshot(session, await this.#loadModelsSafely(signal));
|
|
@@ -5620,6 +5790,8 @@ var DesignHostController = class {
|
|
|
5620
5790
|
id: model.slug,
|
|
5621
5791
|
label: model.displayName,
|
|
5622
5792
|
kind: model.categories[0] ?? "unknown",
|
|
5793
|
+
...model.taskType === void 0 ? {} : { taskType: model.taskType },
|
|
5794
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
5623
5795
|
featured: featuredSlugs.has(model.slug),
|
|
5624
5796
|
available: true,
|
|
5625
5797
|
unavailableReason: null
|
|
@@ -5633,7 +5805,7 @@ var DesignHostController = class {
|
|
|
5633
5805
|
const credential = await this.#resolveCredential();
|
|
5634
5806
|
const credentialReady = credential !== void 0;
|
|
5635
5807
|
const models = ensureSelectedModel(currentModels, session.selectedModelId);
|
|
5636
|
-
const tasks = [...await this.#repository.listTasks()].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
5808
|
+
const tasks = [...await this.#repository.listTasks()].filter((task) => task.sessionId === session.sessionId).sort((left, right) => right.updatedAt - left.updatedAt);
|
|
5637
5809
|
return {
|
|
5638
5810
|
version: 1,
|
|
5639
5811
|
enabled: this.#isEnabled(),
|
|
@@ -5657,6 +5829,7 @@ var DesignHostController = class {
|
|
|
5657
5829
|
if (oldest !== void 0) this.#sessions.delete(oldest[0]);
|
|
5658
5830
|
}
|
|
5659
5831
|
const created = {
|
|
5832
|
+
sessionId,
|
|
5660
5833
|
selectedModelId: null,
|
|
5661
5834
|
draft: null,
|
|
5662
5835
|
proposal: null,
|
|
@@ -6038,6 +6211,33 @@ function stableJson(value) {
|
|
|
6038
6211
|
const object = value;
|
|
6039
6212
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
|
|
6040
6213
|
}
|
|
6214
|
+
//#endregion
|
|
6215
|
+
//#region src/host/agent-routing-context.ts
|
|
6216
|
+
function createModellixRoutingMessage(state) {
|
|
6217
|
+
const sections = [];
|
|
6218
|
+
const nonMediaWebRouting = state.webEnabled ? "use the Modellix Web tools for those questions" : "leave those questions to the Harness-native web_search or web_fetch tools when available";
|
|
6219
|
+
if (state.webEnabled) sections.push({
|
|
6220
|
+
name: "Web routing",
|
|
6221
|
+
text: "Use modellix_web_search automatically for current, changing, external, or source-verification questions. Use modellix_web_fetch automatically when the user supplies a public URL or a Modellix search result needs full-page reading. Prefer these explicit Modellix tools over the native web_search and web_fetch tools, and never call both tool families for the same operation. Do not wait for the user to name a tool, do not browse when the user explicitly says not to, and never automatically repeat a failed or unknown Modellix Web request."
|
|
6222
|
+
});
|
|
6223
|
+
if (state.mediaEnabled) sections.push({
|
|
6224
|
+
name: "Media routing",
|
|
6225
|
+
text: `Use Modellix media tools automatically only when the user intends to create, edit, transform, animate, upload, or inspect generated image, video, or audio work. Do not use media tools for documentation, pricing, general model-capability, or public API-reference questions; ${nonMediaWebRouting} unless the user explicitly requests a live Modellix Design model Schema. For media work, use modellix_media_list then modellix_media_schema when a compatible model is not already known. Treat every returned field description and allowed value as authoritative; do not guess, translate, or replace a published parameter format, and omit optional fields when the user did not request them. A request that refers to the previous/current work, conversation attachments, or prior Modellix result URLs, asks to change or animate it, or asks to preserve its composition, subject, identity, or structure is not a new text-to-media request: select an image-edit/image-to-image, image-to-video, or video-to-video model as appropriate, inspect its schema, and place the latest relevant Modellix result URL in the declared media input field. Never replace that source-dependent operation with text-to-image or text-to-video. Reuse usable URLs and upload local inputs with modellix_media_upload_file. After submitting, inspect the task at most once in the same turn. Hard response rule: while a task is nonterminal, ordinary assistant text may say only that the submission was accepted and that the result card and Modellix Design own the current status. It must not say that the task is queued, running, generating, processing, pending, or any equivalent current-state wording, because ordinary assistant text is immutable and becomes stale. Never launch a replacement or parallel generation while a matching task is active unless the user explicitly requests another result, and never repeat an unknown generation or upload outcome.`
|
|
6226
|
+
});
|
|
6227
|
+
if (sections.length === 0) return null;
|
|
6228
|
+
return createUserMessage({
|
|
6229
|
+
content: [{
|
|
6230
|
+
type: "text",
|
|
6231
|
+
text: sections.map((section) => `${section.name}: ${section.text}`).join("\n")
|
|
6232
|
+
}],
|
|
6233
|
+
source: {
|
|
6234
|
+
kind: "plugin",
|
|
6235
|
+
plugin: "dsh-modellix",
|
|
6236
|
+
form: "snapshot",
|
|
6237
|
+
sections
|
|
6238
|
+
}
|
|
6239
|
+
});
|
|
6240
|
+
}
|
|
6041
6241
|
/** One atomic singleton keeps the Design WAL independent of Settings. */
|
|
6042
6242
|
const modellixDesignDomainSpec = defineDomain({
|
|
6043
6243
|
name: "modellix_design",
|
|
@@ -6083,10 +6283,12 @@ function assertStorageKey(key) {
|
|
|
6083
6283
|
}
|
|
6084
6284
|
//#endregion
|
|
6085
6285
|
//#region src/host/design-tool.ts
|
|
6086
|
-
const
|
|
6087
|
-
const
|
|
6088
|
-
const
|
|
6089
|
-
const
|
|
6286
|
+
const MODELLIX_MEDIA_LIST_TOOL = "modellix_media_list";
|
|
6287
|
+
const MODELLIX_MEDIA_SCHEMA_TOOL = "modellix_media_schema";
|
|
6288
|
+
const MODELLIX_MEDIA_PREPARE_TOOL = "modellix_media_prepare";
|
|
6289
|
+
const MODELLIX_MEDIA_UPLOAD_FILE_TOOL = "modellix_media_upload_file";
|
|
6290
|
+
const MODELLIX_MEDIA_GENERATE_TOOL = "modellix_media_generate";
|
|
6291
|
+
const MODELLIX_MEDIA_GET_RESULT_TOOL = "modellix_media_get_result";
|
|
6090
6292
|
const MODEL_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
6091
6293
|
const SAFE_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
6092
6294
|
const MAX_QUERY_LENGTH = 512;
|
|
@@ -6096,54 +6298,43 @@ const DEFAULT_MODEL_LIMIT = 20;
|
|
|
6096
6298
|
const MAX_MODEL_LIMIT = 50;
|
|
6097
6299
|
const MAX_SCHEMA_FIELDS = 128;
|
|
6098
6300
|
/**
|
|
6099
|
-
* Build the
|
|
6301
|
+
* Build the six stable, namespaced Modellix media tools. The caller owns
|
|
6100
6302
|
* visibility and must only register these definitions while Design is enabled.
|
|
6101
6303
|
*/
|
|
6102
|
-
function createModellixDesignToolDefinitions(controller) {
|
|
6304
|
+
function createModellixDesignToolDefinitions(controller, uploadClient) {
|
|
6305
|
+
const uploads = uploadClient ?? new MediaUploadClient();
|
|
6103
6306
|
return [
|
|
6104
|
-
|
|
6307
|
+
createListTool(controller),
|
|
6308
|
+
createSchemaTool(controller),
|
|
6105
6309
|
createPrepareTool(controller),
|
|
6310
|
+
createUploadFileTool(uploads, controller.resolveCredential),
|
|
6106
6311
|
createGenerateTool(controller),
|
|
6107
|
-
|
|
6312
|
+
createGetResultTool(controller)
|
|
6108
6313
|
];
|
|
6109
6314
|
}
|
|
6110
6315
|
/**
|
|
6111
|
-
* Register
|
|
6112
|
-
* disposer removes both definitions and the gate, allowing the runtime to
|
|
6316
|
+
* Register media tools. The returned disposer removes every definition, allowing the runtime to
|
|
6113
6317
|
* mirror the live Design toggle without leaving model-visible stale tools.
|
|
6114
6318
|
*/
|
|
6115
6319
|
function registerModellixDesignTools(ctx, controller) {
|
|
6116
6320
|
const disposers = [];
|
|
6117
6321
|
try {
|
|
6118
|
-
disposers.push(ctx.on("tools/pre-execute", async (exec, next) => {
|
|
6119
|
-
if (exec.name !== "modellix_design_prepare" && exec.name !== "modellix_design_generate") return next();
|
|
6120
|
-
const downstream = await next();
|
|
6121
|
-
if (downstream.kind !== "allow") return downstream;
|
|
6122
|
-
return {
|
|
6123
|
-
kind: "ask",
|
|
6124
|
-
reason: exec.name === "modellix_design_prepare" ? "This sends one Modellix LLM request to prepare a parameter proposal and may consume balance. Review the instruction and allow it once to continue. It will not generate media." : "This submits one paid Modellix Design generation request. Review the arguments and allow it once to confirm."
|
|
6125
|
-
};
|
|
6126
|
-
}));
|
|
6127
6322
|
for (const definition of createModellixDesignToolDefinitions(controller)) disposers.push(ctx.tools.register(definition));
|
|
6128
6323
|
} catch (error) {
|
|
6129
|
-
disposeAll(disposers);
|
|
6324
|
+
disposeAll$1(disposers);
|
|
6130
6325
|
throw error;
|
|
6131
6326
|
}
|
|
6132
|
-
return () => disposeAll(disposers);
|
|
6327
|
+
return () => disposeAll$1(disposers);
|
|
6133
6328
|
}
|
|
6134
|
-
function
|
|
6329
|
+
function createListTool(controller) {
|
|
6135
6330
|
return defineTool({
|
|
6136
|
-
name:
|
|
6137
|
-
description: "
|
|
6331
|
+
name: MODELLIX_MEDIA_LIST_TOOL,
|
|
6332
|
+
description: "List and search the live Modellix media model catalog. Use this before generation when no compatible provider/model slug is already known. This read-only tool never accepts credentials.",
|
|
6138
6333
|
parameters: {
|
|
6139
6334
|
query: {
|
|
6140
6335
|
type: "string",
|
|
6141
6336
|
description: "Optional case-insensitive model, provider, or media-kind search."
|
|
6142
6337
|
},
|
|
6143
|
-
model: {
|
|
6144
|
-
type: "string",
|
|
6145
|
-
description: "Optional exact provider/model slug whose current schema should be summarized."
|
|
6146
|
-
},
|
|
6147
6338
|
limit: {
|
|
6148
6339
|
type: "integer",
|
|
6149
6340
|
description: `Maximum models to return (default ${String(DEFAULT_MODEL_LIMIT)}, maximum ${String(MAX_MODEL_LIMIT)}).`
|
|
@@ -6161,12 +6352,12 @@ function createModelsTool(controller) {
|
|
|
6161
6352
|
},
|
|
6162
6353
|
service: {
|
|
6163
6354
|
type: "string",
|
|
6164
|
-
const: "
|
|
6355
|
+
const: "media",
|
|
6165
6356
|
required: true
|
|
6166
6357
|
},
|
|
6167
6358
|
operation: {
|
|
6168
6359
|
type: "string",
|
|
6169
|
-
const: "
|
|
6360
|
+
const: "list",
|
|
6170
6361
|
required: true
|
|
6171
6362
|
},
|
|
6172
6363
|
models: {
|
|
@@ -6194,6 +6385,8 @@ function createModelsTool(controller) {
|
|
|
6194
6385
|
],
|
|
6195
6386
|
required: true
|
|
6196
6387
|
},
|
|
6388
|
+
taskType: { type: "string" },
|
|
6389
|
+
description: { type: "string" },
|
|
6197
6390
|
featured: {
|
|
6198
6391
|
type: "boolean",
|
|
6199
6392
|
required: true
|
|
@@ -6209,8 +6402,74 @@ function createModelsTool(controller) {
|
|
|
6209
6402
|
truncated: {
|
|
6210
6403
|
type: "boolean",
|
|
6211
6404
|
required: true
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
},
|
|
6408
|
+
render: (_args, value) => [{
|
|
6409
|
+
type: "text",
|
|
6410
|
+
text: formatModels(value)
|
|
6411
|
+
}]
|
|
6412
|
+
},
|
|
6413
|
+
async execute(args, exec) {
|
|
6414
|
+
assertOnlyKeys$1(args, ["query", "limit"]);
|
|
6415
|
+
throwIfAborted(exec.signal);
|
|
6416
|
+
const sessionId = sessionIdFrom(exec);
|
|
6417
|
+
const query = optionalBoundedText(args.query, "query", MAX_QUERY_LENGTH)?.toLowerCase() ?? "";
|
|
6418
|
+
const limit = boundedLimit(args.limit);
|
|
6419
|
+
const snapshot = await controller.handle("design/read", {
|
|
6420
|
+
version: 1,
|
|
6421
|
+
sessionId
|
|
6422
|
+
}, exec.signal);
|
|
6423
|
+
requireReady(snapshot);
|
|
6424
|
+
throwIfAborted(exec.signal);
|
|
6425
|
+
return projectModels(snapshot, query, limit);
|
|
6426
|
+
},
|
|
6427
|
+
presentCall: (args) => ({
|
|
6428
|
+
card: "generic",
|
|
6429
|
+
title: "Browse Modellix media models",
|
|
6430
|
+
kind: "search",
|
|
6431
|
+
...args.query === void 0 ? {} : { rawInput: args.query }
|
|
6432
|
+
})
|
|
6433
|
+
});
|
|
6434
|
+
}
|
|
6435
|
+
function createSchemaTool(controller) {
|
|
6436
|
+
return defineTool({
|
|
6437
|
+
name: MODELLIX_MEDIA_SCHEMA_TOOL,
|
|
6438
|
+
description: "Get the current Modellix API schema for one exact media model. Use the returned field paths and contract hash before prepare or generate; do not guess model parameters.",
|
|
6439
|
+
parameters: { model: {
|
|
6440
|
+
type: "string",
|
|
6441
|
+
required: true,
|
|
6442
|
+
description: "Exact provider/model slug returned by modellix_media_list."
|
|
6443
|
+
} },
|
|
6444
|
+
output: {
|
|
6445
|
+
schema: {
|
|
6446
|
+
type: "object",
|
|
6447
|
+
additionalProperties: false,
|
|
6448
|
+
properties: {
|
|
6449
|
+
version: {
|
|
6450
|
+
type: "integer",
|
|
6451
|
+
const: 1,
|
|
6452
|
+
required: true
|
|
6453
|
+
},
|
|
6454
|
+
service: {
|
|
6455
|
+
type: "string",
|
|
6456
|
+
const: "media",
|
|
6457
|
+
required: true
|
|
6212
6458
|
},
|
|
6213
|
-
|
|
6459
|
+
operation: {
|
|
6460
|
+
type: "string",
|
|
6461
|
+
const: "schema",
|
|
6462
|
+
required: true
|
|
6463
|
+
},
|
|
6464
|
+
modelId: {
|
|
6465
|
+
type: "string",
|
|
6466
|
+
required: true
|
|
6467
|
+
},
|
|
6468
|
+
available: {
|
|
6469
|
+
type: "boolean",
|
|
6470
|
+
required: true
|
|
6471
|
+
},
|
|
6472
|
+
unavailableReason: { type: "string" },
|
|
6214
6473
|
schema: {
|
|
6215
6474
|
type: "object",
|
|
6216
6475
|
additionalProperties: false,
|
|
@@ -6269,54 +6528,148 @@ function createModelsTool(controller) {
|
|
|
6269
6528
|
},
|
|
6270
6529
|
render: (_args, value) => [{
|
|
6271
6530
|
type: "text",
|
|
6272
|
-
text:
|
|
6531
|
+
text: formatSchema(value)
|
|
6273
6532
|
}]
|
|
6274
6533
|
},
|
|
6275
6534
|
async execute(args, exec) {
|
|
6276
|
-
assertOnlyKeys(args, [
|
|
6277
|
-
|
|
6278
|
-
"model",
|
|
6279
|
-
"limit"
|
|
6280
|
-
]);
|
|
6281
|
-
throwIfAborted(exec.signal);
|
|
6535
|
+
assertOnlyKeys$1(args, ["model"]);
|
|
6536
|
+
const modelId = requireModel(args.model);
|
|
6282
6537
|
const sessionId = sessionIdFrom(exec);
|
|
6283
|
-
const
|
|
6284
|
-
const limit = boundedLimit(args.limit);
|
|
6285
|
-
const requestedModel = args.model === void 0 ? void 0 : requireModel(args.model);
|
|
6286
|
-
let snapshot = await controller.handle("design/read", {
|
|
6538
|
+
const read = await controller.handle("design/read", {
|
|
6287
6539
|
version: 1,
|
|
6288
6540
|
sessionId
|
|
6289
6541
|
}, exec.signal);
|
|
6290
|
-
requireReady(
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6542
|
+
requireReady(read);
|
|
6543
|
+
requireCatalogModel(read, modelId);
|
|
6544
|
+
throwIfAborted(exec.signal);
|
|
6545
|
+
try {
|
|
6546
|
+
return projectSchema(requireDraft(await controller.handle("design/select-model", {
|
|
6295
6547
|
version: 1,
|
|
6296
6548
|
sessionId,
|
|
6297
|
-
modelId
|
|
6298
|
-
}, exec.signal);
|
|
6549
|
+
modelId
|
|
6550
|
+
}, exec.signal), modelId));
|
|
6551
|
+
} catch (error) {
|
|
6552
|
+
if (!isUnsupportedSchema(error)) throw error;
|
|
6553
|
+
return projectUnsupportedSchema(modelId);
|
|
6299
6554
|
}
|
|
6300
|
-
throwIfAborted(exec.signal);
|
|
6301
|
-
return projectModels(snapshot, query, limit, requestedModel);
|
|
6302
6555
|
},
|
|
6303
6556
|
presentCall: (args) => ({
|
|
6304
6557
|
card: "generic",
|
|
6305
|
-
title:
|
|
6306
|
-
kind: "
|
|
6307
|
-
|
|
6558
|
+
title: `Inspect ${args.model} schema`,
|
|
6559
|
+
kind: "read"
|
|
6560
|
+
})
|
|
6561
|
+
});
|
|
6562
|
+
}
|
|
6563
|
+
function createUploadFileTool(client, resolveCredential) {
|
|
6564
|
+
return defineTool({
|
|
6565
|
+
name: MODELLIX_MEDIA_UPLOAD_FILE_TOOL,
|
|
6566
|
+
description: "Upload one image from the current conversation or session workspace to Modellix and return a temporary URL for media model inputs. Supply exactly one of attachment_id or path. Reuse returned URLs and never retry an unknown upload outcome automatically.",
|
|
6567
|
+
parameters: {
|
|
6568
|
+
attachment_id: {
|
|
6569
|
+
type: "string",
|
|
6570
|
+
description: "Attachment ID from an image in this conversation."
|
|
6571
|
+
},
|
|
6572
|
+
path: {
|
|
6573
|
+
type: "string",
|
|
6574
|
+
description: "Image path inside the current session workspace."
|
|
6575
|
+
}
|
|
6576
|
+
},
|
|
6577
|
+
output: {
|
|
6578
|
+
schema: {
|
|
6579
|
+
type: "object",
|
|
6580
|
+
additionalProperties: false,
|
|
6581
|
+
properties: {
|
|
6582
|
+
version: {
|
|
6583
|
+
type: "integer",
|
|
6584
|
+
const: 1,
|
|
6585
|
+
required: true
|
|
6586
|
+
},
|
|
6587
|
+
service: {
|
|
6588
|
+
type: "string",
|
|
6589
|
+
const: "media",
|
|
6590
|
+
required: true
|
|
6591
|
+
},
|
|
6592
|
+
operation: {
|
|
6593
|
+
type: "string",
|
|
6594
|
+
const: "upload_file",
|
|
6595
|
+
required: true
|
|
6596
|
+
},
|
|
6597
|
+
fileId: {
|
|
6598
|
+
type: "string",
|
|
6599
|
+
required: true
|
|
6600
|
+
},
|
|
6601
|
+
type: {
|
|
6602
|
+
type: "string",
|
|
6603
|
+
required: true
|
|
6604
|
+
},
|
|
6605
|
+
url: {
|
|
6606
|
+
type: "string",
|
|
6607
|
+
required: true
|
|
6608
|
+
},
|
|
6609
|
+
filename: {
|
|
6610
|
+
type: "string",
|
|
6611
|
+
required: true
|
|
6612
|
+
},
|
|
6613
|
+
size: {
|
|
6614
|
+
type: "integer",
|
|
6615
|
+
required: true
|
|
6616
|
+
},
|
|
6617
|
+
createdAt: { type: "string" },
|
|
6618
|
+
noAutomaticRetry: {
|
|
6619
|
+
type: "boolean",
|
|
6620
|
+
const: true,
|
|
6621
|
+
required: true
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
},
|
|
6625
|
+
render: (_args, value) => [{
|
|
6626
|
+
type: "text",
|
|
6627
|
+
text: `Uploaded ${value.filename} for Modellix media input: ${value.url}\nReuse this URL; do not upload the same file again unless the URL is no longer usable.`
|
|
6628
|
+
}]
|
|
6629
|
+
},
|
|
6630
|
+
async execute(args, exec) {
|
|
6631
|
+
assertOnlyKeys$1(args, ["attachment_id", "path"]);
|
|
6632
|
+
const attachmentId = optionalBoundedText(args.attachment_id, "attachment_id", 512);
|
|
6633
|
+
const path = optionalBoundedText(args.path, "path", 32767);
|
|
6634
|
+
if (attachmentId === void 0 === (path === void 0)) throw new DesignError("INVALID_ARGUMENT", "Provide exactly one of attachment_id or path");
|
|
6635
|
+
const prepared = attachmentId === void 0 ? await prepareMediaUploadFromPath(exec.agent?.session.header.cwd, path) : await prepareMediaUploadFromSession(exec.agent, attachmentId, exec.signal);
|
|
6636
|
+
throwIfAborted(exec.signal);
|
|
6637
|
+
const apiKey = await resolveCredential?.();
|
|
6638
|
+
if (apiKey === void 0) throw new DesignError("MISSING_API_KEY", "A Modellix API key is required");
|
|
6639
|
+
const uploaded = await client.upload({
|
|
6640
|
+
...prepared,
|
|
6641
|
+
apiKey,
|
|
6642
|
+
signal: exec.signal
|
|
6643
|
+
});
|
|
6644
|
+
return {
|
|
6645
|
+
version: 1,
|
|
6646
|
+
service: "media",
|
|
6647
|
+
operation: "upload_file",
|
|
6648
|
+
fileId: uploaded.fileId,
|
|
6649
|
+
type: uploaded.type,
|
|
6650
|
+
url: uploaded.url,
|
|
6651
|
+
filename: uploaded.filename,
|
|
6652
|
+
size: uploaded.size,
|
|
6653
|
+
...uploaded.createdAt === null ? {} : { createdAt: uploaded.createdAt },
|
|
6654
|
+
noAutomaticRetry: true
|
|
6655
|
+
};
|
|
6656
|
+
},
|
|
6657
|
+
presentCall: () => ({
|
|
6658
|
+
card: "generic",
|
|
6659
|
+
title: "Upload media to Modellix",
|
|
6660
|
+
kind: "execute"
|
|
6308
6661
|
})
|
|
6309
6662
|
});
|
|
6310
6663
|
}
|
|
6311
6664
|
function createPrepareTool(controller) {
|
|
6312
6665
|
return defineTool({
|
|
6313
|
-
name:
|
|
6314
|
-
description: "
|
|
6666
|
+
name: MODELLIX_MEDIA_PREPARE_TOOL,
|
|
6667
|
+
description: "Convert a natural-language media request into a schema-constrained parameter proposal for one exact provider/model slug. Use this for Modellix Design parameter editing; it never submits media generation.",
|
|
6315
6668
|
parameters: {
|
|
6316
6669
|
model: {
|
|
6317
6670
|
type: "string",
|
|
6318
6671
|
required: true,
|
|
6319
|
-
description: "Exact provider/model slug from
|
|
6672
|
+
description: "Exact provider/model slug from modellix_media_list."
|
|
6320
6673
|
},
|
|
6321
6674
|
instruction: {
|
|
6322
6675
|
type: "string",
|
|
@@ -6336,7 +6689,7 @@ function createPrepareTool(controller) {
|
|
|
6336
6689
|
},
|
|
6337
6690
|
service: {
|
|
6338
6691
|
type: "string",
|
|
6339
|
-
const: "
|
|
6692
|
+
const: "media",
|
|
6340
6693
|
required: true
|
|
6341
6694
|
},
|
|
6342
6695
|
operation: {
|
|
@@ -6402,7 +6755,7 @@ function createPrepareTool(controller) {
|
|
|
6402
6755
|
}]
|
|
6403
6756
|
},
|
|
6404
6757
|
async execute(args, exec) {
|
|
6405
|
-
assertOnlyKeys(args, ["model", "instruction"]);
|
|
6758
|
+
assertOnlyKeys$1(args, ["model", "instruction"]);
|
|
6406
6759
|
throwIfAborted(exec.signal);
|
|
6407
6760
|
const modelId = requireModel(args.model);
|
|
6408
6761
|
const instruction = requiredBoundedText(args.instruction, "instruction", MAX_INSTRUCTION_LENGTH);
|
|
@@ -6434,7 +6787,7 @@ function createPrepareTool(controller) {
|
|
|
6434
6787
|
if (proposal === null) throw new DesignError("UNEXPECTED_RESPONSE", "Design did not return a parameter proposal");
|
|
6435
6788
|
return {
|
|
6436
6789
|
version: 1,
|
|
6437
|
-
service: "
|
|
6790
|
+
service: "media",
|
|
6438
6791
|
operation: "prepare",
|
|
6439
6792
|
modelId,
|
|
6440
6793
|
irContractHash: draft.irContractHash,
|
|
@@ -6460,8 +6813,8 @@ function createPrepareTool(controller) {
|
|
|
6460
6813
|
}
|
|
6461
6814
|
function createGenerateTool(controller) {
|
|
6462
6815
|
return defineTool({
|
|
6463
|
-
name:
|
|
6464
|
-
description: "
|
|
6816
|
+
name: MODELLIX_MEDIA_GENERATE_TOOL,
|
|
6817
|
+
description: "Generate or edit image, video, or audio with one exact Modellix media model. Choose text-to-media for a new work; reuse the latest relevant media URL or uploaded file for edit/image-to-video/video-to-video requests. Use schema fields from modellix_media_schema. Never include an API key, never retry an unknown submission outcome, and never start a replacement or parallel generation while a matching task is queued or running unless the user explicitly asks for another result.",
|
|
6465
6818
|
parameters: {
|
|
6466
6819
|
model: {
|
|
6467
6820
|
type: "string",
|
|
@@ -6490,7 +6843,7 @@ function createGenerateTool(controller) {
|
|
|
6490
6843
|
},
|
|
6491
6844
|
service: {
|
|
6492
6845
|
type: "string",
|
|
6493
|
-
const: "
|
|
6846
|
+
const: "media",
|
|
6494
6847
|
required: true
|
|
6495
6848
|
},
|
|
6496
6849
|
operation: {
|
|
@@ -6568,10 +6921,11 @@ function createGenerateTool(controller) {
|
|
|
6568
6921
|
render: (_args, value) => [{
|
|
6569
6922
|
type: "text",
|
|
6570
6923
|
text: formatGeneration(value)
|
|
6571
|
-
}]
|
|
6924
|
+
}],
|
|
6925
|
+
presentationMeta: (_args, value) => value
|
|
6572
6926
|
},
|
|
6573
6927
|
async execute(args, exec) {
|
|
6574
|
-
assertOnlyKeys(args, [
|
|
6928
|
+
assertOnlyKeys$1(args, [
|
|
6575
6929
|
"model",
|
|
6576
6930
|
"prompt",
|
|
6577
6931
|
"input"
|
|
@@ -6609,7 +6963,7 @@ function createGenerateTool(controller) {
|
|
|
6609
6963
|
if (!(error instanceof DesignError) || error.code !== "SUBMIT_UNKNOWN") throw error;
|
|
6610
6964
|
return {
|
|
6611
6965
|
version: 1,
|
|
6612
|
-
service: "
|
|
6966
|
+
service: "media",
|
|
6613
6967
|
operation: "generate",
|
|
6614
6968
|
modelId,
|
|
6615
6969
|
submitted: true,
|
|
@@ -6618,7 +6972,7 @@ function createGenerateTool(controller) {
|
|
|
6618
6972
|
resources: [],
|
|
6619
6973
|
diagnostic: {
|
|
6620
6974
|
code: "submit-unknown",
|
|
6621
|
-
message: "The
|
|
6975
|
+
message: "The generation outcome is unknown. Do not retry automatically; inspect Modellix Design results or the Modellix console."
|
|
6622
6976
|
}
|
|
6623
6977
|
};
|
|
6624
6978
|
}
|
|
@@ -6630,10 +6984,10 @@ function createGenerateTool(controller) {
|
|
|
6630
6984
|
})
|
|
6631
6985
|
});
|
|
6632
6986
|
}
|
|
6633
|
-
function
|
|
6987
|
+
function createGetResultTool(controller) {
|
|
6634
6988
|
return defineTool({
|
|
6635
|
-
name:
|
|
6636
|
-
description: "Refresh and inspect a Design
|
|
6989
|
+
name: MODELLIX_MEDIA_GET_RESULT_TOOL,
|
|
6990
|
+
description: "Refresh and inspect an existing Modellix media generation at most once per Agent turn. For a nonterminal result, the assistant may only confirm submission and direct the user to the live result card or Modellix Design; it must not write queued, running, generating, processing, pending, or equivalent status wording in ordinary assistant text. Accepts a remote task ID or local submit-unknown request ID and never resubmits or replaces generation.",
|
|
6637
6991
|
parameters: { task_id: {
|
|
6638
6992
|
type: "string",
|
|
6639
6993
|
required: true,
|
|
@@ -6651,12 +7005,12 @@ function createTaskTool(controller) {
|
|
|
6651
7005
|
},
|
|
6652
7006
|
service: {
|
|
6653
7007
|
type: "string",
|
|
6654
|
-
const: "
|
|
7008
|
+
const: "media",
|
|
6655
7009
|
required: true
|
|
6656
7010
|
},
|
|
6657
7011
|
operation: {
|
|
6658
7012
|
type: "string",
|
|
6659
|
-
const: "
|
|
7013
|
+
const: "get_result",
|
|
6660
7014
|
required: true
|
|
6661
7015
|
},
|
|
6662
7016
|
found: {
|
|
@@ -6740,10 +7094,11 @@ function createTaskTool(controller) {
|
|
|
6740
7094
|
render: (_args, value) => [{
|
|
6741
7095
|
type: "text",
|
|
6742
7096
|
text: formatTask(value)
|
|
6743
|
-
}]
|
|
7097
|
+
}],
|
|
7098
|
+
presentationMeta: (_args, value) => value
|
|
6744
7099
|
},
|
|
6745
7100
|
async execute(args, exec) {
|
|
6746
|
-
assertOnlyKeys(args, ["task_id"]);
|
|
7101
|
+
assertOnlyKeys$1(args, ["task_id"]);
|
|
6747
7102
|
throwIfAborted(exec.signal);
|
|
6748
7103
|
const taskId = requiredBoundedText(args.task_id, "task_id", 256);
|
|
6749
7104
|
if (!/^[A-Za-z0-9._:-]{1,256}$/u.test(taskId)) throw new DesignError("INVALID_ARGUMENT", "task_id is malformed");
|
|
@@ -6756,13 +7111,13 @@ function createTaskTool(controller) {
|
|
|
6756
7111
|
const job = snapshot.jobs.find((candidate) => candidate.jobId === taskId);
|
|
6757
7112
|
return job === void 0 ? {
|
|
6758
7113
|
version: 1,
|
|
6759
|
-
service: "
|
|
6760
|
-
operation: "
|
|
7114
|
+
service: "media",
|
|
7115
|
+
operation: "get_result",
|
|
6761
7116
|
found: false
|
|
6762
7117
|
} : {
|
|
6763
7118
|
version: 1,
|
|
6764
|
-
service: "
|
|
6765
|
-
operation: "
|
|
7119
|
+
service: "media",
|
|
7120
|
+
operation: "get_result",
|
|
6766
7121
|
found: true,
|
|
6767
7122
|
job: projectJob(job)
|
|
6768
7123
|
};
|
|
@@ -6774,29 +7129,40 @@ function createTaskTool(controller) {
|
|
|
6774
7129
|
})
|
|
6775
7130
|
});
|
|
6776
7131
|
}
|
|
6777
|
-
function projectModels(snapshot, query, limit
|
|
7132
|
+
function projectModels(snapshot, query, limit) {
|
|
6778
7133
|
const matches = snapshot.models.filter((model) => query === "" || [
|
|
6779
7134
|
model.id,
|
|
6780
7135
|
model.label,
|
|
6781
|
-
model.kind
|
|
6782
|
-
|
|
7136
|
+
model.kind,
|
|
7137
|
+
model.taskType,
|
|
7138
|
+
model.description
|
|
7139
|
+
].some((value) => value?.toLowerCase().includes(query) === true));
|
|
6783
7140
|
const models = matches.slice(0, limit).map((model) => ({
|
|
6784
7141
|
modelId: model.id,
|
|
6785
7142
|
label: model.label,
|
|
6786
7143
|
kind: model.kind,
|
|
7144
|
+
...model.taskType === void 0 ? {} : { taskType: model.taskType },
|
|
7145
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
6787
7146
|
featured: model.featured,
|
|
6788
7147
|
available: model.available,
|
|
6789
7148
|
...model.unavailableReason === null ? {} : { unavailableReason: modelUnavailableMessage(model.unavailableReason) }
|
|
6790
7149
|
}));
|
|
6791
|
-
const draft = requestedModel === void 0 ? void 0 : snapshot.draft;
|
|
6792
7150
|
return {
|
|
6793
7151
|
version: 1,
|
|
6794
|
-
service: "
|
|
6795
|
-
operation: "
|
|
7152
|
+
service: "media",
|
|
7153
|
+
operation: "list",
|
|
6796
7154
|
models,
|
|
6797
|
-
truncated: matches.length > models.length
|
|
6798
|
-
|
|
6799
|
-
|
|
7155
|
+
truncated: matches.length > models.length
|
|
7156
|
+
};
|
|
7157
|
+
}
|
|
7158
|
+
function projectSchema(draft) {
|
|
7159
|
+
return {
|
|
7160
|
+
version: 1,
|
|
7161
|
+
service: "media",
|
|
7162
|
+
operation: "schema",
|
|
7163
|
+
modelId: draft.modelId,
|
|
7164
|
+
available: true,
|
|
7165
|
+
schema: {
|
|
6800
7166
|
modelId: draft.modelId,
|
|
6801
7167
|
irContractHash: draft.irContractHash,
|
|
6802
7168
|
primaryInputPath: draft.primaryInputPath,
|
|
@@ -6809,7 +7175,17 @@ function projectModels(snapshot, query, limit, requestedModel) {
|
|
|
6809
7175
|
...field.description === null ? {} : { description: field.description }
|
|
6810
7176
|
})),
|
|
6811
7177
|
truncated: draft.fields.length > MAX_SCHEMA_FIELDS
|
|
6812
|
-
}
|
|
7178
|
+
}
|
|
7179
|
+
};
|
|
7180
|
+
}
|
|
7181
|
+
function projectUnsupportedSchema(modelId) {
|
|
7182
|
+
return {
|
|
7183
|
+
version: 1,
|
|
7184
|
+
service: "media",
|
|
7185
|
+
operation: "schema",
|
|
7186
|
+
modelId,
|
|
7187
|
+
available: false,
|
|
7188
|
+
unavailableReason: "This model's API schema is not compatible with Modellix Design. Choose another model from the live catalog."
|
|
6813
7189
|
};
|
|
6814
7190
|
}
|
|
6815
7191
|
function normalizeGenerationInput(draft, prompt, input) {
|
|
@@ -6837,7 +7213,7 @@ function normalizeGenerationInput(draft, prompt, input) {
|
|
|
6837
7213
|
function projectGeneration(modelId, job) {
|
|
6838
7214
|
if (job === void 0) return {
|
|
6839
7215
|
version: 1,
|
|
6840
|
-
service: "
|
|
7216
|
+
service: "media",
|
|
6841
7217
|
operation: "generate",
|
|
6842
7218
|
modelId,
|
|
6843
7219
|
submitted: true,
|
|
@@ -6846,12 +7222,12 @@ function projectGeneration(modelId, job) {
|
|
|
6846
7222
|
resources: [],
|
|
6847
7223
|
diagnostic: {
|
|
6848
7224
|
code: "job-record-unavailable",
|
|
6849
|
-
message: "The
|
|
7225
|
+
message: "The generation returned without a readable local job record. Do not retry automatically."
|
|
6850
7226
|
}
|
|
6851
7227
|
};
|
|
6852
7228
|
return {
|
|
6853
7229
|
version: 1,
|
|
6854
|
-
service: "
|
|
7230
|
+
service: "media",
|
|
6855
7231
|
operation: "generate",
|
|
6856
7232
|
modelId,
|
|
6857
7233
|
submitted: true,
|
|
@@ -6926,7 +7302,7 @@ function requiredBoundedText(value, field, maximum) {
|
|
|
6926
7302
|
if (value.trim() === "" || value.length > maximum) throw new DesignError("INVALID_ARGUMENT", `${field} must be non-empty and at most ${String(maximum)} characters`);
|
|
6927
7303
|
return value;
|
|
6928
7304
|
}
|
|
6929
|
-
function assertOnlyKeys(value, allowed) {
|
|
7305
|
+
function assertOnlyKeys$1(value, allowed) {
|
|
6930
7306
|
const extra = Object.keys(value).find((key) => !allowed.includes(key));
|
|
6931
7307
|
if (extra !== void 0) throw new DesignError("INVALID_ARGUMENT", `Unknown tool argument: ${extra}`);
|
|
6932
7308
|
}
|
|
@@ -6945,9 +7321,27 @@ function toToolJson(value) {
|
|
|
6945
7321
|
return value;
|
|
6946
7322
|
}
|
|
6947
7323
|
function formatModels(value) {
|
|
6948
|
-
const lines = value.models.map((model) => `- ${model.modelId} (${model.kind})${model.available ? "" : " — unavailable"}`);
|
|
6949
|
-
|
|
6950
|
-
|
|
7324
|
+
const lines = value.models.map((model) => `- ${model.modelId} (${model.taskType ?? model.kind})${model.available ? "" : " — unavailable"}${model.description === void 0 ? "" : `: ${model.description}`}`);
|
|
7325
|
+
return `${lines.length === 0 ? "No matching Modellix media models." : lines.join("\n")}${value.truncated ? "\n(Results truncated; refine the query.)" : ""}`;
|
|
7326
|
+
}
|
|
7327
|
+
function formatSchema(value) {
|
|
7328
|
+
if (!value.available || value.schema === void 0) return `Schema for ${value.modelId} is unavailable to Modellix Design. ${value.unavailableReason ?? "Choose another model from the live catalog."}`;
|
|
7329
|
+
const fields = value.schema.fields.map((field) => {
|
|
7330
|
+
const qualifiers = [
|
|
7331
|
+
field.kind,
|
|
7332
|
+
field.required ? "required" : "optional",
|
|
7333
|
+
field.options.length === 0 ? null : `allowed values: ${field.options.map((option) => JSON.stringify(option)).join(", ")}`
|
|
7334
|
+
].filter((qualifier) => qualifier !== null);
|
|
7335
|
+
return `- ${field.path} (${qualifiers.join("; ")})${field.description === void 0 ? "" : ` — ${field.description}`}`;
|
|
7336
|
+
});
|
|
7337
|
+
return [
|
|
7338
|
+
`Schema for ${value.schema.modelId}. Primary input: ${value.schema.primaryInputPath}. Use the exact field descriptions and allowed values below; do not infer a different format.`,
|
|
7339
|
+
fields.length === 0 ? "No input fields." : fields.join("\n"),
|
|
7340
|
+
value.schema.truncated ? "The field list is truncated." : null
|
|
7341
|
+
].filter((line) => line !== null).join("\n");
|
|
7342
|
+
}
|
|
7343
|
+
function isUnsupportedSchema(error) {
|
|
7344
|
+
return error instanceof DesignError && (error.code === "SCHEMA_INVALID" || error.code === "ENDPOINT_NOT_ALLOWED");
|
|
6951
7345
|
}
|
|
6952
7346
|
function formatPreparation(value) {
|
|
6953
7347
|
const changes = value.changes.map((change) => `- ${change.path} (${change.label})`).join("\n");
|
|
@@ -6975,12 +7369,399 @@ function diagnosticMessage(code) {
|
|
|
6975
7369
|
function formatGeneration(value) {
|
|
6976
7370
|
const resources = value.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
|
|
6977
7371
|
const diagnostic = value.diagnostic === void 0 ? "" : `\n${value.diagnostic.message}`;
|
|
6978
|
-
return `Modellix
|
|
7372
|
+
if (value.status === "running") return `Modellix media submission accepted${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}\nThe result card and Modellix Design now own the current status. Do not inspect it repeatedly or start a replacement in this turn. In ordinary assistant text, say only that submission was accepted and point to those live surfaces; do not use queued, running, generating, processing, pending, or equivalent current-state wording.`;
|
|
7373
|
+
return `Modellix media status: ${value.status}${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}${resources === "" ? "" : `\n${resources}`}\nDo not automatically repeat this submission.`;
|
|
6979
7374
|
}
|
|
6980
7375
|
function formatTask(value) {
|
|
6981
|
-
if (!value.found || value.job === void 0) return "No persisted Modellix
|
|
7376
|
+
if (!value.found || value.job === void 0) return "No persisted Modellix media task matched that identifier.";
|
|
6982
7377
|
const resources = value.job.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
|
|
6983
|
-
return `Modellix
|
|
7378
|
+
if (value.job.status === "running") return `Modellix media submission ${value.job.jobId} remains assigned to the live result card and Modellix Design. Do not inspect it again or start a replacement in this turn. In ordinary assistant text, say only that submission was accepted and point to those live surfaces; do not use queued, running, generating, processing, pending, or equivalent current-state wording.`;
|
|
7379
|
+
return `Modellix media task ${value.job.jobId}: ${value.job.status}.${resources === "" ? "" : `\n${resources}`}`;
|
|
7380
|
+
}
|
|
7381
|
+
function disposeAll$1(disposers) {
|
|
7382
|
+
for (const dispose of [...disposers].reverse()) try {
|
|
7383
|
+
dispose();
|
|
7384
|
+
} catch {}
|
|
7385
|
+
}
|
|
7386
|
+
//#endregion
|
|
7387
|
+
//#region src/host/web-tool.ts
|
|
7388
|
+
const MODELLIX_WEB_SEARCH_TOOL = "modellix_web_search";
|
|
7389
|
+
const MODELLIX_WEB_FETCH_TOOL = "modellix_web_fetch";
|
|
7390
|
+
const WEB_TOOL_TIMEOUT_MS = 3e4;
|
|
7391
|
+
const MAX_FETCH_OUTPUT_CHARS = 2e5;
|
|
7392
|
+
/** Build the two explicit Modellix Web tools over the concrete Modellix providers. */
|
|
7393
|
+
function createModellixWebToolDefinitions(providers) {
|
|
7394
|
+
return [createSearchTool(providers), createFetchTool(providers)];
|
|
7395
|
+
}
|
|
7396
|
+
/** Register the explicit tools and roll back atomically if either name conflicts. */
|
|
7397
|
+
function registerModellixWebTools(ctx, providers) {
|
|
7398
|
+
const disposers = [];
|
|
7399
|
+
try {
|
|
7400
|
+
for (const definition of createModellixWebToolDefinitions(providers)) disposers.push(ctx.tools.register(definition));
|
|
7401
|
+
} catch (error) {
|
|
7402
|
+
disposeAll(disposers);
|
|
7403
|
+
throw error;
|
|
7404
|
+
}
|
|
7405
|
+
return () => disposeAll(disposers);
|
|
7406
|
+
}
|
|
7407
|
+
function createSearchTool(providers) {
|
|
7408
|
+
return defineTool({
|
|
7409
|
+
name: MODELLIX_WEB_SEARCH_TOOL,
|
|
7410
|
+
description: "Search current public web information through the Modellix Web Search API. Use automatically when current or externally verified information is needed, even when the user did not name a tool. One invocation sends exactly one Modellix search request and never retries it automatically.",
|
|
7411
|
+
parameters: {
|
|
7412
|
+
query: {
|
|
7413
|
+
type: "string",
|
|
7414
|
+
required: true,
|
|
7415
|
+
description: "One non-empty web search query."
|
|
7416
|
+
},
|
|
7417
|
+
max_results: {
|
|
7418
|
+
type: "integer",
|
|
7419
|
+
description: `Maximum sources to return (default ${String(5)}, maximum ${String(20)}).`
|
|
7420
|
+
}
|
|
7421
|
+
},
|
|
7422
|
+
output: {
|
|
7423
|
+
schema: {
|
|
7424
|
+
type: "object",
|
|
7425
|
+
additionalProperties: false,
|
|
7426
|
+
properties: {
|
|
7427
|
+
version: {
|
|
7428
|
+
type: "integer",
|
|
7429
|
+
const: 1,
|
|
7430
|
+
required: true
|
|
7431
|
+
},
|
|
7432
|
+
service: {
|
|
7433
|
+
type: "string",
|
|
7434
|
+
const: "web",
|
|
7435
|
+
required: true
|
|
7436
|
+
},
|
|
7437
|
+
provider: {
|
|
7438
|
+
type: "string",
|
|
7439
|
+
const: "modellix",
|
|
7440
|
+
required: true
|
|
7441
|
+
},
|
|
7442
|
+
operation: {
|
|
7443
|
+
type: "string",
|
|
7444
|
+
const: "search",
|
|
7445
|
+
required: true
|
|
7446
|
+
},
|
|
7447
|
+
endpoint: {
|
|
7448
|
+
type: "string",
|
|
7449
|
+
const: MODELLIX_WEB_SEARCH_ENDPOINT,
|
|
7450
|
+
required: true
|
|
7451
|
+
},
|
|
7452
|
+
query: {
|
|
7453
|
+
type: "string",
|
|
7454
|
+
required: true
|
|
7455
|
+
},
|
|
7456
|
+
content: { type: "string" },
|
|
7457
|
+
sources: {
|
|
7458
|
+
type: "array",
|
|
7459
|
+
required: true,
|
|
7460
|
+
items: {
|
|
7461
|
+
type: "object",
|
|
7462
|
+
additionalProperties: false,
|
|
7463
|
+
properties: {
|
|
7464
|
+
url: {
|
|
7465
|
+
type: "string",
|
|
7466
|
+
required: true
|
|
7467
|
+
},
|
|
7468
|
+
title: { type: "string" },
|
|
7469
|
+
snippet: { type: "string" },
|
|
7470
|
+
publishedAt: { type: "string" }
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
},
|
|
7474
|
+
truncated: {
|
|
7475
|
+
type: "boolean",
|
|
7476
|
+
required: true
|
|
7477
|
+
},
|
|
7478
|
+
noAutomaticRetry: {
|
|
7479
|
+
type: "boolean",
|
|
7480
|
+
const: true,
|
|
7481
|
+
required: true
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
},
|
|
7485
|
+
render: (_args, value) => [{
|
|
7486
|
+
type: "text",
|
|
7487
|
+
text: formatSearchOutput(value)
|
|
7488
|
+
}],
|
|
7489
|
+
presentationMeta: (_args, value) => searchPresentationMeta(value)
|
|
7490
|
+
},
|
|
7491
|
+
timeoutMs: WEB_TOOL_TIMEOUT_MS,
|
|
7492
|
+
async execute(args, exec) {
|
|
7493
|
+
assertOnlyKeys(args, ["query", "max_results"]);
|
|
7494
|
+
const query = requireNonBlank(args.query, "query");
|
|
7495
|
+
const maxResults = boundedResultCount(args.max_results);
|
|
7496
|
+
return projectSearchResult(query, await providers.search.search({
|
|
7497
|
+
query,
|
|
7498
|
+
maxResults
|
|
7499
|
+
}, exec.signal));
|
|
7500
|
+
},
|
|
7501
|
+
presentCall: (args) => ({
|
|
7502
|
+
card: "generic",
|
|
7503
|
+
title: `Modellix Web Search · ${args.query}`,
|
|
7504
|
+
kind: "search",
|
|
7505
|
+
rawInput: args.query
|
|
7506
|
+
}),
|
|
7507
|
+
presentResult: (args, result) => presentSearchResult(args.query, result)
|
|
7508
|
+
});
|
|
7509
|
+
}
|
|
7510
|
+
function createFetchTool(providers) {
|
|
7511
|
+
return defineTool({
|
|
7512
|
+
name: MODELLIX_WEB_FETCH_TOOL,
|
|
7513
|
+
description: "Fetch one specific public HTTP(S) page through the Modellix Web Fetch API. Use automatically for a user-supplied URL or when a Modellix search source needs full-page reading. One invocation sends exactly one Modellix fetch request and never retries it automatically.",
|
|
7514
|
+
parameters: { url: {
|
|
7515
|
+
type: "string",
|
|
7516
|
+
required: true,
|
|
7517
|
+
description: "The public HTTP(S) URL to retrieve."
|
|
7518
|
+
} },
|
|
7519
|
+
output: {
|
|
7520
|
+
schema: {
|
|
7521
|
+
type: "object",
|
|
7522
|
+
additionalProperties: false,
|
|
7523
|
+
properties: {
|
|
7524
|
+
version: {
|
|
7525
|
+
type: "integer",
|
|
7526
|
+
const: 1,
|
|
7527
|
+
required: true
|
|
7528
|
+
},
|
|
7529
|
+
service: {
|
|
7530
|
+
type: "string",
|
|
7531
|
+
const: "web",
|
|
7532
|
+
required: true
|
|
7533
|
+
},
|
|
7534
|
+
provider: {
|
|
7535
|
+
type: "string",
|
|
7536
|
+
const: "modellix",
|
|
7537
|
+
required: true
|
|
7538
|
+
},
|
|
7539
|
+
operation: {
|
|
7540
|
+
type: "string",
|
|
7541
|
+
const: "fetch",
|
|
7542
|
+
required: true
|
|
7543
|
+
},
|
|
7544
|
+
endpoint: {
|
|
7545
|
+
type: "string",
|
|
7546
|
+
const: MODELLIX_WEB_FETCH_ENDPOINT,
|
|
7547
|
+
required: true
|
|
7548
|
+
},
|
|
7549
|
+
requestedUrl: {
|
|
7550
|
+
type: "string",
|
|
7551
|
+
required: true
|
|
7552
|
+
},
|
|
7553
|
+
url: {
|
|
7554
|
+
type: "string",
|
|
7555
|
+
required: true
|
|
7556
|
+
},
|
|
7557
|
+
statusCode: {
|
|
7558
|
+
type: "integer",
|
|
7559
|
+
required: true
|
|
7560
|
+
},
|
|
7561
|
+
body: {
|
|
7562
|
+
required: true,
|
|
7563
|
+
oneOf: [{
|
|
7564
|
+
type: "object",
|
|
7565
|
+
additionalProperties: false,
|
|
7566
|
+
properties: {
|
|
7567
|
+
kind: {
|
|
7568
|
+
type: "string",
|
|
7569
|
+
const: "html",
|
|
7570
|
+
required: true
|
|
7571
|
+
},
|
|
7572
|
+
content: {
|
|
7573
|
+
type: "string",
|
|
7574
|
+
required: true
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
}, {
|
|
7578
|
+
type: "object",
|
|
7579
|
+
additionalProperties: false,
|
|
7580
|
+
properties: {
|
|
7581
|
+
kind: {
|
|
7582
|
+
type: "string",
|
|
7583
|
+
const: "text",
|
|
7584
|
+
required: true
|
|
7585
|
+
},
|
|
7586
|
+
content: {
|
|
7587
|
+
type: "string",
|
|
7588
|
+
required: true
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
}]
|
|
7592
|
+
},
|
|
7593
|
+
truncated: {
|
|
7594
|
+
type: "boolean",
|
|
7595
|
+
required: true
|
|
7596
|
+
},
|
|
7597
|
+
noAutomaticRetry: {
|
|
7598
|
+
type: "boolean",
|
|
7599
|
+
const: true,
|
|
7600
|
+
required: true
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
},
|
|
7604
|
+
render: (_args, value) => [{
|
|
7605
|
+
type: "text",
|
|
7606
|
+
text: formatFetchOutput(value)
|
|
7607
|
+
}],
|
|
7608
|
+
presentationMeta: (_args, value) => fetchPresentationMeta(value)
|
|
7609
|
+
},
|
|
7610
|
+
timeoutMs: WEB_TOOL_TIMEOUT_MS,
|
|
7611
|
+
async execute(args, exec) {
|
|
7612
|
+
assertOnlyKeys(args, ["url"]);
|
|
7613
|
+
const requestedUrl = requireNonBlank(args.url, "url");
|
|
7614
|
+
return projectFetchResult(requestedUrl, await providers.fetch.fetch({ url: requestedUrl }, exec.signal));
|
|
7615
|
+
},
|
|
7616
|
+
presentCall: (args) => ({
|
|
7617
|
+
card: "generic",
|
|
7618
|
+
title: `Modellix Web Fetch · ${args.url}`,
|
|
7619
|
+
kind: "fetch",
|
|
7620
|
+
rawInput: args.url
|
|
7621
|
+
}),
|
|
7622
|
+
presentResult: (args, result) => presentFetchResult(args.url, result)
|
|
7623
|
+
});
|
|
7624
|
+
}
|
|
7625
|
+
function projectSearchResult(query, result) {
|
|
7626
|
+
return {
|
|
7627
|
+
version: 1,
|
|
7628
|
+
service: "web",
|
|
7629
|
+
provider: "modellix",
|
|
7630
|
+
operation: "search",
|
|
7631
|
+
endpoint: MODELLIX_WEB_SEARCH_ENDPOINT,
|
|
7632
|
+
query,
|
|
7633
|
+
...result.content === void 0 ? {} : { content: result.content },
|
|
7634
|
+
sources: result.sources.map(projectSource),
|
|
7635
|
+
truncated: result.truncated,
|
|
7636
|
+
noAutomaticRetry: true
|
|
7637
|
+
};
|
|
7638
|
+
}
|
|
7639
|
+
function projectFetchResult(requestedUrl, result) {
|
|
7640
|
+
const content = result.body.content.slice(0, MAX_FETCH_OUTPUT_CHARS);
|
|
7641
|
+
return {
|
|
7642
|
+
version: 1,
|
|
7643
|
+
service: "web",
|
|
7644
|
+
provider: "modellix",
|
|
7645
|
+
operation: "fetch",
|
|
7646
|
+
endpoint: MODELLIX_WEB_FETCH_ENDPOINT,
|
|
7647
|
+
requestedUrl,
|
|
7648
|
+
url: result.url,
|
|
7649
|
+
statusCode: result.statusCode,
|
|
7650
|
+
body: {
|
|
7651
|
+
kind: result.body.kind,
|
|
7652
|
+
content
|
|
7653
|
+
},
|
|
7654
|
+
truncated: result.truncated || content.length !== result.body.content.length,
|
|
7655
|
+
noAutomaticRetry: true
|
|
7656
|
+
};
|
|
7657
|
+
}
|
|
7658
|
+
function projectSource(source) {
|
|
7659
|
+
return {
|
|
7660
|
+
url: source.url,
|
|
7661
|
+
...source.title === void 0 ? {} : { title: source.title },
|
|
7662
|
+
...source.snippet === void 0 ? {} : { snippet: source.snippet },
|
|
7663
|
+
...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
|
|
7664
|
+
};
|
|
7665
|
+
}
|
|
7666
|
+
function formatSearchOutput(value) {
|
|
7667
|
+
const parts = [`Modellix Web Search completed for: ${value.query}`, `Provider endpoint: ${value.endpoint}`];
|
|
7668
|
+
if (value.content !== void 0 && value.content.length > 0) parts.push(value.content);
|
|
7669
|
+
if (value.sources.length === 0) parts.push("No sources found.");
|
|
7670
|
+
else parts.push(`Sources:\n${value.sources.map(formatSource).join("\n")}`);
|
|
7671
|
+
if (value.truncated) parts.push("The source list was truncated; refine the query if needed.");
|
|
7672
|
+
parts.push("Cite the relevant source URLs as markdown links in the answer.");
|
|
7673
|
+
return parts.join("\n\n");
|
|
7674
|
+
}
|
|
7675
|
+
function formatSource(source) {
|
|
7676
|
+
const label = source.title === void 0 || source.title.length === 0 ? source.url : source.title;
|
|
7677
|
+
const details = [source.snippet, source.publishedAt].filter((value) => value !== void 0 && value.length > 0);
|
|
7678
|
+
return `- ${label}\n URL: ${source.url}${details.length === 0 ? "" : `\n ${details.join(" · ")}`}`;
|
|
7679
|
+
}
|
|
7680
|
+
function formatFetchOutput(value) {
|
|
7681
|
+
const header = [`Modellix Web Fetch completed: ${value.url} (HTTP ${String(value.statusCode)})`, `Provider endpoint: ${value.endpoint}`].join("\n");
|
|
7682
|
+
const footer = value.truncated ? "\n\n(Content truncated. Fetch a more specific public URL if more detail is required.)" : "";
|
|
7683
|
+
return `${header}\n\n${value.body.content}${footer}`;
|
|
7684
|
+
}
|
|
7685
|
+
function searchPresentationMeta(value) {
|
|
7686
|
+
return {
|
|
7687
|
+
sources: value.sources.map((source) => ({
|
|
7688
|
+
url: source.url,
|
|
7689
|
+
...source.title === void 0 ? {} : { title: source.title },
|
|
7690
|
+
...source.snippet === void 0 ? {} : { snippet: source.snippet },
|
|
7691
|
+
...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
|
|
7692
|
+
})),
|
|
7693
|
+
truncated: value.truncated,
|
|
7694
|
+
...value.content === void 0 ? {} : { answer: value.content }
|
|
7695
|
+
};
|
|
7696
|
+
}
|
|
7697
|
+
function fetchPresentationMeta(value) {
|
|
7698
|
+
return {
|
|
7699
|
+
url: value.url,
|
|
7700
|
+
statusCode: value.statusCode,
|
|
7701
|
+
truncated: value.truncated
|
|
7702
|
+
};
|
|
7703
|
+
}
|
|
7704
|
+
function presentSearchResult(query, result) {
|
|
7705
|
+
if (result.isError) return void 0;
|
|
7706
|
+
const meta = asRecord(result.meta);
|
|
7707
|
+
if (meta === void 0 || !Array.isArray(meta.sources) || typeof meta.truncated !== "boolean") return void 0;
|
|
7708
|
+
const sources = [];
|
|
7709
|
+
for (const candidate of meta.sources) {
|
|
7710
|
+
const source = asRecord(candidate);
|
|
7711
|
+
if (source === void 0 || typeof source.url !== "string" || !optionalStrings(source, [
|
|
7712
|
+
"title",
|
|
7713
|
+
"snippet",
|
|
7714
|
+
"publishedAt"
|
|
7715
|
+
])) return void 0;
|
|
7716
|
+
sources.push({
|
|
7717
|
+
url: source.url,
|
|
7718
|
+
...typeof source.title === "string" ? { title: source.title } : {},
|
|
7719
|
+
...typeof source.snippet === "string" ? { snippet: source.snippet } : {},
|
|
7720
|
+
...typeof source.publishedAt === "string" ? { publishedAt: source.publishedAt } : {}
|
|
7721
|
+
});
|
|
7722
|
+
}
|
|
7723
|
+
if (meta.answer !== void 0 && typeof meta.answer !== "string") return void 0;
|
|
7724
|
+
return {
|
|
7725
|
+
card: "web",
|
|
7726
|
+
kind: "search",
|
|
7727
|
+
title: `Modellix Web Search · ${query}`,
|
|
7728
|
+
sources,
|
|
7729
|
+
truncated: meta.truncated,
|
|
7730
|
+
...typeof meta.answer === "string" ? { answer: meta.answer } : {}
|
|
7731
|
+
};
|
|
7732
|
+
}
|
|
7733
|
+
function presentFetchResult(requestedUrl, result) {
|
|
7734
|
+
if (result.isError) return void 0;
|
|
7735
|
+
const meta = asRecord(result.meta);
|
|
7736
|
+
if (meta === void 0 || typeof meta.url !== "string" || typeof meta.statusCode !== "number" || !Number.isInteger(meta.statusCode) || typeof meta.truncated !== "boolean") return void 0;
|
|
7737
|
+
return {
|
|
7738
|
+
card: "web",
|
|
7739
|
+
kind: "fetch",
|
|
7740
|
+
title: `Modellix Web Fetch · ${requestedUrl}`,
|
|
7741
|
+
url: meta.url,
|
|
7742
|
+
statusCode: meta.statusCode,
|
|
7743
|
+
truncated: meta.truncated
|
|
7744
|
+
};
|
|
7745
|
+
}
|
|
7746
|
+
function boundedResultCount(value) {
|
|
7747
|
+
const resolved = value ?? 5;
|
|
7748
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > 20) throw new TypeError(`max_results must be an integer from 1 through ${String(20)}`);
|
|
7749
|
+
return resolved;
|
|
7750
|
+
}
|
|
7751
|
+
function requireNonBlank(value, name) {
|
|
7752
|
+
if (value.trim().length === 0) throw new TypeError(`${name} must contain non-whitespace text`);
|
|
7753
|
+
return value;
|
|
7754
|
+
}
|
|
7755
|
+
function assertOnlyKeys(value, allowed) {
|
|
7756
|
+
const accepted = new Set(allowed);
|
|
7757
|
+
const unexpected = Object.keys(value).find((key) => !accepted.has(key));
|
|
7758
|
+
if (unexpected !== void 0) throw new TypeError(`Unexpected tool argument: ${unexpected}`);
|
|
7759
|
+
}
|
|
7760
|
+
function optionalStrings(value, keys) {
|
|
7761
|
+
return keys.every((key) => value[key] === void 0 || typeof value[key] === "string");
|
|
7762
|
+
}
|
|
7763
|
+
function asRecord(value) {
|
|
7764
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6984
7765
|
}
|
|
6985
7766
|
function disposeAll(disposers) {
|
|
6986
7767
|
for (const dispose of [...disposers].reverse()) try {
|
|
@@ -7079,7 +7860,7 @@ var PluginSettingsController = class {
|
|
|
7079
7860
|
//#endregion
|
|
7080
7861
|
//#region src/host/runtime.ts
|
|
7081
7862
|
const RPC_CHANNEL = "/modellix";
|
|
7082
|
-
const LLM_SETTINGS_NAMESPACE =
|
|
7863
|
+
const LLM_SETTINGS_NAMESPACE = "llm-pi-ai";
|
|
7083
7864
|
const LLM_OWNERSHIP_ROLLBACK_FAILED = "MODELLIX_LLM_OWNERSHIP_ROLLBACK_FAILED";
|
|
7084
7865
|
const LLM_MATERIALIZATION_RECOVERED = "MODELLIX_LLM_MATERIALIZATION_RECOVERED";
|
|
7085
7866
|
const LLM_MATERIALIZATION_RECOVERY_FAILED = "MODELLIX_LLM_MATERIALIZATION_RECOVERY_FAILED";
|
|
@@ -7119,6 +7900,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7119
7900
|
#credentialRecoveryRequestId = null;
|
|
7120
7901
|
#designPollTimer;
|
|
7121
7902
|
#disposeDesignTools;
|
|
7903
|
+
#disposeWebTools;
|
|
7122
7904
|
#closing = false;
|
|
7123
7905
|
#lifecycleAbort = new AbortController();
|
|
7124
7906
|
static async create(ctx) {
|
|
@@ -7197,6 +7979,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7197
7979
|
this.syncDesignTools(next.services.design.enabled);
|
|
7198
7980
|
if (next.services.design.enabled) this.scheduleDesignPoll(0);
|
|
7199
7981
|
}
|
|
7982
|
+
if (next.services.web.enabled !== previous.services.web.enabled) this.syncWebTools(next.services.web.enabled);
|
|
7200
7983
|
if (next.services.llm.enabled !== previous.services.llm.enabled || next.credentialEpoch !== previous.credentialEpoch) return this.enqueueWrite(() => this.reconcileLiveSettings());
|
|
7201
7984
|
}), "dsh-modellix: live settings snapshot");
|
|
7202
7985
|
this.#ctx.on("credentials/reference-updated", (updated) => {
|
|
@@ -7227,25 +8010,22 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7227
8010
|
this.scheduleDesignPoll(0);
|
|
7228
8011
|
});
|
|
7229
8012
|
});
|
|
7230
|
-
this.#ctx.effect(() =>
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
}
|
|
7247
|
-
}), "dsh-modellix: native Web providers");
|
|
7248
|
-
this.#ctx.effect(() => this.#ctx.connection.rpc.handle(RPC_CHANNEL, (endpoint, payload, signal) => this.handleRpc(endpoint, payload, signal), { authority: "loopback" }), "dsh-modellix: loopback configuration and Design RPC");
|
|
8013
|
+
this.#ctx.effect(() => {
|
|
8014
|
+
this.syncWebTools(this.#config.services.web.enabled);
|
|
8015
|
+
return () => {
|
|
8016
|
+
this.#disposeWebTools?.();
|
|
8017
|
+
this.#disposeWebTools = void 0;
|
|
8018
|
+
};
|
|
8019
|
+
}, "dsh-modellix: explicit Modellix Web tools");
|
|
8020
|
+
this.#ctx.on("agent/session-start", ({ agent }) => {
|
|
8021
|
+
const message = createModellixRoutingMessage({
|
|
8022
|
+
mediaEnabled: this.#config.services.design.enabled && this.credentialIsUsable(),
|
|
8023
|
+
webEnabled: this.#config.services.web.enabled && this.credentialIsUsable()
|
|
8024
|
+
});
|
|
8025
|
+
if (message !== null) agent.inject(message);
|
|
8026
|
+
});
|
|
8027
|
+
const connectionRpc = this.#ctx.connection.rpc;
|
|
8028
|
+
this.#ctx.effect(() => connectionRpc.handle(RPC_CHANNEL, (endpoint, payload, signal) => this.handleRpc(endpoint, payload, signal), { authority: "loopback" }), "dsh-modellix: loopback configuration and Design RPC");
|
|
7249
8029
|
this.#ctx.effect(() => {
|
|
7250
8030
|
this.scheduleDesignPoll(2e3);
|
|
7251
8031
|
return async () => {
|
|
@@ -7904,11 +8684,14 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7904
8684
|
}
|
|
7905
8685
|
syncDesignTools(enabled) {
|
|
7906
8686
|
if (enabled && this.#disposeDesignTools === void 0) {
|
|
7907
|
-
this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, {
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8687
|
+
this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, {
|
|
8688
|
+
resolveCredential: async () => (await this.resolveUsableCredential())?.value,
|
|
8689
|
+
handle: (endpoint, payload, signal) => {
|
|
8690
|
+
const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
|
|
8691
|
+
if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
|
|
8692
|
+
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal));
|
|
8693
|
+
}
|
|
8694
|
+
});
|
|
7912
8695
|
return;
|
|
7913
8696
|
}
|
|
7914
8697
|
if (!enabled && this.#disposeDesignTools !== void 0) {
|
|
@@ -7916,6 +8699,34 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7916
8699
|
this.#disposeDesignTools = void 0;
|
|
7917
8700
|
}
|
|
7918
8701
|
}
|
|
8702
|
+
syncWebTools(enabled) {
|
|
8703
|
+
if (enabled && this.#disposeWebTools === void 0) {
|
|
8704
|
+
const providers = createModellixWebProviders({
|
|
8705
|
+
isEnabled: () => this.#config.services.web.enabled,
|
|
8706
|
+
hasCredential: () => this.credentialIsUsable(),
|
|
8707
|
+
resolveCredential: async () => {
|
|
8708
|
+
const hit = await this.resolveUsableCredential();
|
|
8709
|
+
if (hit === void 0) this.requestCredentialRecovery();
|
|
8710
|
+
return hit === void 0 ? null : {
|
|
8711
|
+
apiKey: hit.value,
|
|
8712
|
+
credentialEpoch: hit.credentialEpoch
|
|
8713
|
+
};
|
|
8714
|
+
},
|
|
8715
|
+
getUserId: () => this.#userId,
|
|
8716
|
+
isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
|
|
8717
|
+
onCredentialRejected: async (epoch) => {
|
|
8718
|
+
if (epoch !== this.#config.credentialEpoch) return;
|
|
8719
|
+
this.markCredentialRejected(epoch);
|
|
8720
|
+
}
|
|
8721
|
+
});
|
|
8722
|
+
this.#disposeWebTools = registerModellixWebTools(this.#ctx, providers);
|
|
8723
|
+
return;
|
|
8724
|
+
}
|
|
8725
|
+
if (!enabled && this.#disposeWebTools !== void 0) {
|
|
8726
|
+
this.#disposeWebTools();
|
|
8727
|
+
this.#disposeWebTools = void 0;
|
|
8728
|
+
}
|
|
8729
|
+
}
|
|
7919
8730
|
scheduleDesignPoll(delayMs) {
|
|
7920
8731
|
if (this.#closing || this.#designPollTimer !== void 0) return;
|
|
7921
8732
|
this.#designPollTimer = setTimeout(() => {
|
|
@@ -8075,6 +8886,6 @@ async function apply(ctx) {
|
|
|
8075
8886
|
await ModellixRuntime.create(ctx);
|
|
8076
8887
|
}
|
|
8077
8888
|
//#endregion
|
|
8078
|
-
export { AUTHENTICATED_CATALOG_URL, CURRENT_CONFIG_SCHEMA_VERSION, Config, CredentialBroker, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialValidationError, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignError, DesignHostController, DesignPlannerClient, DesignTaskRepository, EMPTY_LLM_ROUTE_LEDGER, HttpPolicyError, HttpResponseBoundaryError, LlmCatalogCache, LlmCatalogClient, LlmCatalogRequestError, LlmMaterializationConflictError, LlmMaterializationRollbackError, LlmRegistryBackreadError, LlmRouteConflictError, LlmSettingsMaterializer, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF,
|
|
8889
|
+
export { AUTHENTICATED_CATALOG_URL, CURRENT_CONFIG_SCHEMA_VERSION, Config, CredentialBroker, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialValidationError, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignError, DesignHostController, DesignPlannerClient, DesignTaskRepository, EMPTY_LLM_ROUTE_LEDGER, HttpPolicyError, HttpResponseBoundaryError, LlmCatalogCache, LlmCatalogClient, LlmCatalogRequestError, LlmMaterializationConflictError, LlmMaterializationRollbackError, LlmRegistryBackreadError, LlmRouteConflictError, LlmSettingsMaterializer, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF, MODELLIX_LLM_BASE_URL, MODELLIX_LLM_MODELS_URL, MODELLIX_LLM_PROVENANCE_FIELD, MODELLIX_LLM_PROVIDER_ID, MODELLIX_MEDIA_GENERATE_TOOL, MODELLIX_MEDIA_GET_RESULT_TOOL, MODELLIX_MEDIA_LIST_TOOL, MODELLIX_MEDIA_PREPARE_TOOL, MODELLIX_MEDIA_SCHEMA_TOOL, MODELLIX_MEDIA_UPLOAD_FILE_TOOL, MODELLIX_ORIGINS, MODELLIX_SETTINGS_NAMESPACE, MODELLIX_WEB_FETCH_ENDPOINT, MODELLIX_WEB_FETCH_TOOL, MODELLIX_WEB_PROVIDER_ID, MODELLIX_WEB_SEARCH_ENDPOINT, MODELLIX_WEB_SEARCH_TOOL, MediaUploadClient, ModelCatalogClient, ModelSchemaClient, ModellixRuntime, ModellixWebContractError, ModellixWebFetchFailedError, ModellixWebFetchProvider, ModellixWebProviderError, ModellixWebSearchProvider, OnboardingSaveConflictError, PUBLIC_PORTAL_CATALOG_URL, PluginSettingsController, PluginSettingsSchema, PredictionClient, REDACTED, StaleLlmCatalogError, UnsupportedConfigVersionError, abandonLlmMaterialization, advanceCredentialEpoch, apply, applyCredentialDescriptor, applyExactPatch, applyNaturalLanguage, applyRuntimeUnauthorized, applyVerificationResult, approveHttpRequest, approveRedirect, beginLlmMaterialization, beginOnboardingSave, buildFetchRequest, buildInvocationBody, buildSearchRequest, completeLlmMaterialization, completeOnboardingSave, computeRetryDelay, createCredentialState, createDefaultConfig, createModellixDesignToolDefinitions, createModellixWebProviders, createModellixWebToolDefinitions, deferOnboarding, deriveModellixSessionId, deriveModellixUserId, executeWithRetry, extractAllowedSubmitUrl, getServiceToggles, inject, isAllowedModellixOrigin, isCredentialInvalidError, isPublicHostname, isRetryableError, isRetryableReadFailure, isValidModellixIdentity, markOnboardingCredentialSaved, materializeDefaults, migrateConfig, missingCredentialDescriptor, modellixDesignDomainSpec, name, normalizeCredentialDescriptor, openDesignStorage, parseCatalogPage, parseDesignSchema, parseFetchResponse, parsePredictionTask, parseResources, parseRetryAfter, parseSearchResponse, planLlmRouteMaterialization, planLlmRouteRemoval, prepareMediaUploadFromPath, prepareMediaUploadFromSession, preserveStoredCredentialAfterCandidateFailure, preserveVerificationAfterTransientFailure, readBoundedResponseJson, readBoundedResponseText, reconcileLlmRouteLedgerAfterInterruption, reconcileOnboardingSave, redactForLog, redactHeaders, redactUrl, registerModellixDesignTools, registerModellixWebProviderInstances, registerModellixWebProviders, registerModellixWebTools, replayDesignWal, requestDeadline, retryAfterFromFailure, selectAvailableResults, setServiceToggles, systemClock, systemSleep, toModellixError, validatePublicHttpUrl, validateSubmitEndpoint, verifyLlmRegistryBackread };
|
|
8079
8890
|
|
|
8080
8891
|
//# sourceMappingURL=index.js.map
|