dsh-modellix 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +101 -169
- package/README.zh-CN.md +99 -167
- 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 +17 -15
- package/docs/en-US/RELEASE_CHECKLIST.md +270 -0
- package/docs/en-US/USER_GUIDE.md +183 -401
- package/docs/zh-CN/LOCAL_USAGE.md +17 -15
- package/docs/zh-CN/RELEASE_CHECKLIST.md +270 -0
- package/docs/zh-CN/USER_GUIDE.md +182 -400
- package/lib/client.d.ts +16 -2
- package/lib/client.js +1308 -466
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +159 -109
- package/lib/index.js +995 -192
- package/lib/index.js.map +1 -1
- package/package.json +7 -2
- package/docs/assets/credential-recovery.webp +0 -0
- package/docs/assets/design-desktop.webp +0 -0
- package/docs/assets/design-mobile-en.webp +0 -0
- package/docs/assets/design-proposal.webp +0 -0
- package/docs/assets/design-results-media.webp +0 -0
- package/docs/assets/llm-model-selector.webp +0 -0
- package/docs/assets/onboarding-defaults.webp +0 -0
- package/docs/assets/settings-ready.webp +0 -0
- package/docs/assets/web-tools.webp +0 -0
package/lib/index.js
CHANGED
|
@@ -3,7 +3,10 @@ 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
5
|
import { SettingsConflictError, settingsNamespace } 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,32 @@ 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
|
+
if (state.webEnabled) sections.push({
|
|
6219
|
+
name: "Web routing",
|
|
6220
|
+
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."
|
|
6221
|
+
});
|
|
6222
|
+
if (state.mediaEnabled) sections.push({
|
|
6223
|
+
name: "Media routing",
|
|
6224
|
+
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; use the Modellix Web tools for those questions 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."
|
|
6225
|
+
});
|
|
6226
|
+
if (sections.length === 0) return null;
|
|
6227
|
+
return createUserMessage({
|
|
6228
|
+
content: [{
|
|
6229
|
+
type: "text",
|
|
6230
|
+
text: sections.map((section) => `${section.name}: ${section.text}`).join("\n")
|
|
6231
|
+
}],
|
|
6232
|
+
source: {
|
|
6233
|
+
kind: "plugin",
|
|
6234
|
+
plugin: "dsh-modellix",
|
|
6235
|
+
form: "snapshot",
|
|
6236
|
+
sections
|
|
6237
|
+
}
|
|
6238
|
+
});
|
|
6239
|
+
}
|
|
6041
6240
|
/** One atomic singleton keeps the Design WAL independent of Settings. */
|
|
6042
6241
|
const modellixDesignDomainSpec = defineDomain({
|
|
6043
6242
|
name: "modellix_design",
|
|
@@ -6083,10 +6282,12 @@ function assertStorageKey(key) {
|
|
|
6083
6282
|
}
|
|
6084
6283
|
//#endregion
|
|
6085
6284
|
//#region src/host/design-tool.ts
|
|
6086
|
-
const
|
|
6087
|
-
const
|
|
6088
|
-
const
|
|
6089
|
-
const
|
|
6285
|
+
const MODELLIX_MEDIA_LIST_TOOL = "modellix_media_list";
|
|
6286
|
+
const MODELLIX_MEDIA_SCHEMA_TOOL = "modellix_media_schema";
|
|
6287
|
+
const MODELLIX_MEDIA_PREPARE_TOOL = "modellix_media_prepare";
|
|
6288
|
+
const MODELLIX_MEDIA_UPLOAD_FILE_TOOL = "modellix_media_upload_file";
|
|
6289
|
+
const MODELLIX_MEDIA_GENERATE_TOOL = "modellix_media_generate";
|
|
6290
|
+
const MODELLIX_MEDIA_GET_RESULT_TOOL = "modellix_media_get_result";
|
|
6090
6291
|
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
6292
|
const SAFE_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
6092
6293
|
const MAX_QUERY_LENGTH = 512;
|
|
@@ -6096,54 +6297,43 @@ const DEFAULT_MODEL_LIMIT = 20;
|
|
|
6096
6297
|
const MAX_MODEL_LIMIT = 50;
|
|
6097
6298
|
const MAX_SCHEMA_FIELDS = 128;
|
|
6098
6299
|
/**
|
|
6099
|
-
* Build the
|
|
6300
|
+
* Build the six stable, namespaced Modellix media tools. The caller owns
|
|
6100
6301
|
* visibility and must only register these definitions while Design is enabled.
|
|
6101
6302
|
*/
|
|
6102
|
-
function createModellixDesignToolDefinitions(controller) {
|
|
6303
|
+
function createModellixDesignToolDefinitions(controller, uploadClient) {
|
|
6304
|
+
const uploads = uploadClient ?? new MediaUploadClient();
|
|
6103
6305
|
return [
|
|
6104
|
-
|
|
6306
|
+
createListTool(controller),
|
|
6307
|
+
createSchemaTool(controller),
|
|
6105
6308
|
createPrepareTool(controller),
|
|
6309
|
+
createUploadFileTool(uploads, controller.resolveCredential),
|
|
6106
6310
|
createGenerateTool(controller),
|
|
6107
|
-
|
|
6311
|
+
createGetResultTool(controller)
|
|
6108
6312
|
];
|
|
6109
6313
|
}
|
|
6110
6314
|
/**
|
|
6111
|
-
* Register
|
|
6112
|
-
* disposer removes both definitions and the gate, allowing the runtime to
|
|
6315
|
+
* Register media tools. The returned disposer removes every definition, allowing the runtime to
|
|
6113
6316
|
* mirror the live Design toggle without leaving model-visible stale tools.
|
|
6114
6317
|
*/
|
|
6115
6318
|
function registerModellixDesignTools(ctx, controller) {
|
|
6116
6319
|
const disposers = [];
|
|
6117
6320
|
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
6321
|
for (const definition of createModellixDesignToolDefinitions(controller)) disposers.push(ctx.tools.register(definition));
|
|
6128
6322
|
} catch (error) {
|
|
6129
|
-
disposeAll(disposers);
|
|
6323
|
+
disposeAll$1(disposers);
|
|
6130
6324
|
throw error;
|
|
6131
6325
|
}
|
|
6132
|
-
return () => disposeAll(disposers);
|
|
6326
|
+
return () => disposeAll$1(disposers);
|
|
6133
6327
|
}
|
|
6134
|
-
function
|
|
6328
|
+
function createListTool(controller) {
|
|
6135
6329
|
return defineTool({
|
|
6136
|
-
name:
|
|
6137
|
-
description: "
|
|
6330
|
+
name: MODELLIX_MEDIA_LIST_TOOL,
|
|
6331
|
+
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
6332
|
parameters: {
|
|
6139
6333
|
query: {
|
|
6140
6334
|
type: "string",
|
|
6141
6335
|
description: "Optional case-insensitive model, provider, or media-kind search."
|
|
6142
6336
|
},
|
|
6143
|
-
model: {
|
|
6144
|
-
type: "string",
|
|
6145
|
-
description: "Optional exact provider/model slug whose current schema should be summarized."
|
|
6146
|
-
},
|
|
6147
6337
|
limit: {
|
|
6148
6338
|
type: "integer",
|
|
6149
6339
|
description: `Maximum models to return (default ${String(DEFAULT_MODEL_LIMIT)}, maximum ${String(MAX_MODEL_LIMIT)}).`
|
|
@@ -6161,12 +6351,12 @@ function createModelsTool(controller) {
|
|
|
6161
6351
|
},
|
|
6162
6352
|
service: {
|
|
6163
6353
|
type: "string",
|
|
6164
|
-
const: "
|
|
6354
|
+
const: "media",
|
|
6165
6355
|
required: true
|
|
6166
6356
|
},
|
|
6167
6357
|
operation: {
|
|
6168
6358
|
type: "string",
|
|
6169
|
-
const: "
|
|
6359
|
+
const: "list",
|
|
6170
6360
|
required: true
|
|
6171
6361
|
},
|
|
6172
6362
|
models: {
|
|
@@ -6194,6 +6384,8 @@ function createModelsTool(controller) {
|
|
|
6194
6384
|
],
|
|
6195
6385
|
required: true
|
|
6196
6386
|
},
|
|
6387
|
+
taskType: { type: "string" },
|
|
6388
|
+
description: { type: "string" },
|
|
6197
6389
|
featured: {
|
|
6198
6390
|
type: "boolean",
|
|
6199
6391
|
required: true
|
|
@@ -6209,8 +6401,74 @@ function createModelsTool(controller) {
|
|
|
6209
6401
|
truncated: {
|
|
6210
6402
|
type: "boolean",
|
|
6211
6403
|
required: true
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
},
|
|
6407
|
+
render: (_args, value) => [{
|
|
6408
|
+
type: "text",
|
|
6409
|
+
text: formatModels(value)
|
|
6410
|
+
}]
|
|
6411
|
+
},
|
|
6412
|
+
async execute(args, exec) {
|
|
6413
|
+
assertOnlyKeys$1(args, ["query", "limit"]);
|
|
6414
|
+
throwIfAborted(exec.signal);
|
|
6415
|
+
const sessionId = sessionIdFrom(exec);
|
|
6416
|
+
const query = optionalBoundedText(args.query, "query", MAX_QUERY_LENGTH)?.toLowerCase() ?? "";
|
|
6417
|
+
const limit = boundedLimit(args.limit);
|
|
6418
|
+
const snapshot = await controller.handle("design/read", {
|
|
6419
|
+
version: 1,
|
|
6420
|
+
sessionId
|
|
6421
|
+
}, exec.signal);
|
|
6422
|
+
requireReady(snapshot);
|
|
6423
|
+
throwIfAborted(exec.signal);
|
|
6424
|
+
return projectModels(snapshot, query, limit);
|
|
6425
|
+
},
|
|
6426
|
+
presentCall: (args) => ({
|
|
6427
|
+
card: "generic",
|
|
6428
|
+
title: "Browse Modellix media models",
|
|
6429
|
+
kind: "search",
|
|
6430
|
+
...args.query === void 0 ? {} : { rawInput: args.query }
|
|
6431
|
+
})
|
|
6432
|
+
});
|
|
6433
|
+
}
|
|
6434
|
+
function createSchemaTool(controller) {
|
|
6435
|
+
return defineTool({
|
|
6436
|
+
name: MODELLIX_MEDIA_SCHEMA_TOOL,
|
|
6437
|
+
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.",
|
|
6438
|
+
parameters: { model: {
|
|
6439
|
+
type: "string",
|
|
6440
|
+
required: true,
|
|
6441
|
+
description: "Exact provider/model slug returned by modellix_media_list."
|
|
6442
|
+
} },
|
|
6443
|
+
output: {
|
|
6444
|
+
schema: {
|
|
6445
|
+
type: "object",
|
|
6446
|
+
additionalProperties: false,
|
|
6447
|
+
properties: {
|
|
6448
|
+
version: {
|
|
6449
|
+
type: "integer",
|
|
6450
|
+
const: 1,
|
|
6451
|
+
required: true
|
|
6452
|
+
},
|
|
6453
|
+
service: {
|
|
6454
|
+
type: "string",
|
|
6455
|
+
const: "media",
|
|
6456
|
+
required: true
|
|
6212
6457
|
},
|
|
6213
|
-
|
|
6458
|
+
operation: {
|
|
6459
|
+
type: "string",
|
|
6460
|
+
const: "schema",
|
|
6461
|
+
required: true
|
|
6462
|
+
},
|
|
6463
|
+
modelId: {
|
|
6464
|
+
type: "string",
|
|
6465
|
+
required: true
|
|
6466
|
+
},
|
|
6467
|
+
available: {
|
|
6468
|
+
type: "boolean",
|
|
6469
|
+
required: true
|
|
6470
|
+
},
|
|
6471
|
+
unavailableReason: { type: "string" },
|
|
6214
6472
|
schema: {
|
|
6215
6473
|
type: "object",
|
|
6216
6474
|
additionalProperties: false,
|
|
@@ -6269,54 +6527,148 @@ function createModelsTool(controller) {
|
|
|
6269
6527
|
},
|
|
6270
6528
|
render: (_args, value) => [{
|
|
6271
6529
|
type: "text",
|
|
6272
|
-
text:
|
|
6530
|
+
text: formatSchema(value)
|
|
6273
6531
|
}]
|
|
6274
6532
|
},
|
|
6275
6533
|
async execute(args, exec) {
|
|
6276
|
-
assertOnlyKeys(args, [
|
|
6277
|
-
|
|
6278
|
-
"model",
|
|
6279
|
-
"limit"
|
|
6280
|
-
]);
|
|
6281
|
-
throwIfAborted(exec.signal);
|
|
6534
|
+
assertOnlyKeys$1(args, ["model"]);
|
|
6535
|
+
const modelId = requireModel(args.model);
|
|
6282
6536
|
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", {
|
|
6537
|
+
const read = await controller.handle("design/read", {
|
|
6287
6538
|
version: 1,
|
|
6288
6539
|
sessionId
|
|
6289
6540
|
}, exec.signal);
|
|
6290
|
-
requireReady(
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6541
|
+
requireReady(read);
|
|
6542
|
+
requireCatalogModel(read, modelId);
|
|
6543
|
+
throwIfAborted(exec.signal);
|
|
6544
|
+
try {
|
|
6545
|
+
return projectSchema(requireDraft(await controller.handle("design/select-model", {
|
|
6295
6546
|
version: 1,
|
|
6296
6547
|
sessionId,
|
|
6297
|
-
modelId
|
|
6298
|
-
}, exec.signal);
|
|
6548
|
+
modelId
|
|
6549
|
+
}, exec.signal), modelId));
|
|
6550
|
+
} catch (error) {
|
|
6551
|
+
if (!isUnsupportedSchema(error)) throw error;
|
|
6552
|
+
return projectUnsupportedSchema(modelId);
|
|
6299
6553
|
}
|
|
6300
|
-
throwIfAborted(exec.signal);
|
|
6301
|
-
return projectModels(snapshot, query, limit, requestedModel);
|
|
6302
6554
|
},
|
|
6303
6555
|
presentCall: (args) => ({
|
|
6304
6556
|
card: "generic",
|
|
6305
|
-
title:
|
|
6306
|
-
kind: "
|
|
6307
|
-
|
|
6557
|
+
title: `Inspect ${args.model} schema`,
|
|
6558
|
+
kind: "read"
|
|
6559
|
+
})
|
|
6560
|
+
});
|
|
6561
|
+
}
|
|
6562
|
+
function createUploadFileTool(client, resolveCredential) {
|
|
6563
|
+
return defineTool({
|
|
6564
|
+
name: MODELLIX_MEDIA_UPLOAD_FILE_TOOL,
|
|
6565
|
+
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.",
|
|
6566
|
+
parameters: {
|
|
6567
|
+
attachment_id: {
|
|
6568
|
+
type: "string",
|
|
6569
|
+
description: "Attachment ID from an image in this conversation."
|
|
6570
|
+
},
|
|
6571
|
+
path: {
|
|
6572
|
+
type: "string",
|
|
6573
|
+
description: "Image path inside the current session workspace."
|
|
6574
|
+
}
|
|
6575
|
+
},
|
|
6576
|
+
output: {
|
|
6577
|
+
schema: {
|
|
6578
|
+
type: "object",
|
|
6579
|
+
additionalProperties: false,
|
|
6580
|
+
properties: {
|
|
6581
|
+
version: {
|
|
6582
|
+
type: "integer",
|
|
6583
|
+
const: 1,
|
|
6584
|
+
required: true
|
|
6585
|
+
},
|
|
6586
|
+
service: {
|
|
6587
|
+
type: "string",
|
|
6588
|
+
const: "media",
|
|
6589
|
+
required: true
|
|
6590
|
+
},
|
|
6591
|
+
operation: {
|
|
6592
|
+
type: "string",
|
|
6593
|
+
const: "upload_file",
|
|
6594
|
+
required: true
|
|
6595
|
+
},
|
|
6596
|
+
fileId: {
|
|
6597
|
+
type: "string",
|
|
6598
|
+
required: true
|
|
6599
|
+
},
|
|
6600
|
+
type: {
|
|
6601
|
+
type: "string",
|
|
6602
|
+
required: true
|
|
6603
|
+
},
|
|
6604
|
+
url: {
|
|
6605
|
+
type: "string",
|
|
6606
|
+
required: true
|
|
6607
|
+
},
|
|
6608
|
+
filename: {
|
|
6609
|
+
type: "string",
|
|
6610
|
+
required: true
|
|
6611
|
+
},
|
|
6612
|
+
size: {
|
|
6613
|
+
type: "integer",
|
|
6614
|
+
required: true
|
|
6615
|
+
},
|
|
6616
|
+
createdAt: { type: "string" },
|
|
6617
|
+
noAutomaticRetry: {
|
|
6618
|
+
type: "boolean",
|
|
6619
|
+
const: true,
|
|
6620
|
+
required: true
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
},
|
|
6624
|
+
render: (_args, value) => [{
|
|
6625
|
+
type: "text",
|
|
6626
|
+
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.`
|
|
6627
|
+
}]
|
|
6628
|
+
},
|
|
6629
|
+
async execute(args, exec) {
|
|
6630
|
+
assertOnlyKeys$1(args, ["attachment_id", "path"]);
|
|
6631
|
+
const attachmentId = optionalBoundedText(args.attachment_id, "attachment_id", 512);
|
|
6632
|
+
const path = optionalBoundedText(args.path, "path", 32767);
|
|
6633
|
+
if (attachmentId === void 0 === (path === void 0)) throw new DesignError("INVALID_ARGUMENT", "Provide exactly one of attachment_id or path");
|
|
6634
|
+
const prepared = attachmentId === void 0 ? await prepareMediaUploadFromPath(exec.agent?.session.header.cwd, path) : await prepareMediaUploadFromSession(exec.agent, attachmentId, exec.signal);
|
|
6635
|
+
throwIfAborted(exec.signal);
|
|
6636
|
+
const apiKey = await resolveCredential?.();
|
|
6637
|
+
if (apiKey === void 0) throw new DesignError("MISSING_API_KEY", "A Modellix API key is required");
|
|
6638
|
+
const uploaded = await client.upload({
|
|
6639
|
+
...prepared,
|
|
6640
|
+
apiKey,
|
|
6641
|
+
signal: exec.signal
|
|
6642
|
+
});
|
|
6643
|
+
return {
|
|
6644
|
+
version: 1,
|
|
6645
|
+
service: "media",
|
|
6646
|
+
operation: "upload_file",
|
|
6647
|
+
fileId: uploaded.fileId,
|
|
6648
|
+
type: uploaded.type,
|
|
6649
|
+
url: uploaded.url,
|
|
6650
|
+
filename: uploaded.filename,
|
|
6651
|
+
size: uploaded.size,
|
|
6652
|
+
...uploaded.createdAt === null ? {} : { createdAt: uploaded.createdAt },
|
|
6653
|
+
noAutomaticRetry: true
|
|
6654
|
+
};
|
|
6655
|
+
},
|
|
6656
|
+
presentCall: () => ({
|
|
6657
|
+
card: "generic",
|
|
6658
|
+
title: "Upload media to Modellix",
|
|
6659
|
+
kind: "execute"
|
|
6308
6660
|
})
|
|
6309
6661
|
});
|
|
6310
6662
|
}
|
|
6311
6663
|
function createPrepareTool(controller) {
|
|
6312
6664
|
return defineTool({
|
|
6313
|
-
name:
|
|
6314
|
-
description: "
|
|
6665
|
+
name: MODELLIX_MEDIA_PREPARE_TOOL,
|
|
6666
|
+
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
6667
|
parameters: {
|
|
6316
6668
|
model: {
|
|
6317
6669
|
type: "string",
|
|
6318
6670
|
required: true,
|
|
6319
|
-
description: "Exact provider/model slug from
|
|
6671
|
+
description: "Exact provider/model slug from modellix_media_list."
|
|
6320
6672
|
},
|
|
6321
6673
|
instruction: {
|
|
6322
6674
|
type: "string",
|
|
@@ -6336,7 +6688,7 @@ function createPrepareTool(controller) {
|
|
|
6336
6688
|
},
|
|
6337
6689
|
service: {
|
|
6338
6690
|
type: "string",
|
|
6339
|
-
const: "
|
|
6691
|
+
const: "media",
|
|
6340
6692
|
required: true
|
|
6341
6693
|
},
|
|
6342
6694
|
operation: {
|
|
@@ -6402,7 +6754,7 @@ function createPrepareTool(controller) {
|
|
|
6402
6754
|
}]
|
|
6403
6755
|
},
|
|
6404
6756
|
async execute(args, exec) {
|
|
6405
|
-
assertOnlyKeys(args, ["model", "instruction"]);
|
|
6757
|
+
assertOnlyKeys$1(args, ["model", "instruction"]);
|
|
6406
6758
|
throwIfAborted(exec.signal);
|
|
6407
6759
|
const modelId = requireModel(args.model);
|
|
6408
6760
|
const instruction = requiredBoundedText(args.instruction, "instruction", MAX_INSTRUCTION_LENGTH);
|
|
@@ -6434,7 +6786,7 @@ function createPrepareTool(controller) {
|
|
|
6434
6786
|
if (proposal === null) throw new DesignError("UNEXPECTED_RESPONSE", "Design did not return a parameter proposal");
|
|
6435
6787
|
return {
|
|
6436
6788
|
version: 1,
|
|
6437
|
-
service: "
|
|
6789
|
+
service: "media",
|
|
6438
6790
|
operation: "prepare",
|
|
6439
6791
|
modelId,
|
|
6440
6792
|
irContractHash: draft.irContractHash,
|
|
@@ -6460,8 +6812,8 @@ function createPrepareTool(controller) {
|
|
|
6460
6812
|
}
|
|
6461
6813
|
function createGenerateTool(controller) {
|
|
6462
6814
|
return defineTool({
|
|
6463
|
-
name:
|
|
6464
|
-
description: "
|
|
6815
|
+
name: MODELLIX_MEDIA_GENERATE_TOOL,
|
|
6816
|
+
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
6817
|
parameters: {
|
|
6466
6818
|
model: {
|
|
6467
6819
|
type: "string",
|
|
@@ -6490,7 +6842,7 @@ function createGenerateTool(controller) {
|
|
|
6490
6842
|
},
|
|
6491
6843
|
service: {
|
|
6492
6844
|
type: "string",
|
|
6493
|
-
const: "
|
|
6845
|
+
const: "media",
|
|
6494
6846
|
required: true
|
|
6495
6847
|
},
|
|
6496
6848
|
operation: {
|
|
@@ -6568,10 +6920,11 @@ function createGenerateTool(controller) {
|
|
|
6568
6920
|
render: (_args, value) => [{
|
|
6569
6921
|
type: "text",
|
|
6570
6922
|
text: formatGeneration(value)
|
|
6571
|
-
}]
|
|
6923
|
+
}],
|
|
6924
|
+
presentationMeta: (_args, value) => value
|
|
6572
6925
|
},
|
|
6573
6926
|
async execute(args, exec) {
|
|
6574
|
-
assertOnlyKeys(args, [
|
|
6927
|
+
assertOnlyKeys$1(args, [
|
|
6575
6928
|
"model",
|
|
6576
6929
|
"prompt",
|
|
6577
6930
|
"input"
|
|
@@ -6609,7 +6962,7 @@ function createGenerateTool(controller) {
|
|
|
6609
6962
|
if (!(error instanceof DesignError) || error.code !== "SUBMIT_UNKNOWN") throw error;
|
|
6610
6963
|
return {
|
|
6611
6964
|
version: 1,
|
|
6612
|
-
service: "
|
|
6965
|
+
service: "media",
|
|
6613
6966
|
operation: "generate",
|
|
6614
6967
|
modelId,
|
|
6615
6968
|
submitted: true,
|
|
@@ -6618,7 +6971,7 @@ function createGenerateTool(controller) {
|
|
|
6618
6971
|
resources: [],
|
|
6619
6972
|
diagnostic: {
|
|
6620
6973
|
code: "submit-unknown",
|
|
6621
|
-
message: "The
|
|
6974
|
+
message: "The generation outcome is unknown. Do not retry automatically; inspect Modellix Design results or the Modellix console."
|
|
6622
6975
|
}
|
|
6623
6976
|
};
|
|
6624
6977
|
}
|
|
@@ -6630,10 +6983,10 @@ function createGenerateTool(controller) {
|
|
|
6630
6983
|
})
|
|
6631
6984
|
});
|
|
6632
6985
|
}
|
|
6633
|
-
function
|
|
6986
|
+
function createGetResultTool(controller) {
|
|
6634
6987
|
return defineTool({
|
|
6635
|
-
name:
|
|
6636
|
-
description: "Refresh and inspect a Design
|
|
6988
|
+
name: MODELLIX_MEDIA_GET_RESULT_TOOL,
|
|
6989
|
+
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
6990
|
parameters: { task_id: {
|
|
6638
6991
|
type: "string",
|
|
6639
6992
|
required: true,
|
|
@@ -6651,12 +7004,12 @@ function createTaskTool(controller) {
|
|
|
6651
7004
|
},
|
|
6652
7005
|
service: {
|
|
6653
7006
|
type: "string",
|
|
6654
|
-
const: "
|
|
7007
|
+
const: "media",
|
|
6655
7008
|
required: true
|
|
6656
7009
|
},
|
|
6657
7010
|
operation: {
|
|
6658
7011
|
type: "string",
|
|
6659
|
-
const: "
|
|
7012
|
+
const: "get_result",
|
|
6660
7013
|
required: true
|
|
6661
7014
|
},
|
|
6662
7015
|
found: {
|
|
@@ -6740,10 +7093,11 @@ function createTaskTool(controller) {
|
|
|
6740
7093
|
render: (_args, value) => [{
|
|
6741
7094
|
type: "text",
|
|
6742
7095
|
text: formatTask(value)
|
|
6743
|
-
}]
|
|
7096
|
+
}],
|
|
7097
|
+
presentationMeta: (_args, value) => value
|
|
6744
7098
|
},
|
|
6745
7099
|
async execute(args, exec) {
|
|
6746
|
-
assertOnlyKeys(args, ["task_id"]);
|
|
7100
|
+
assertOnlyKeys$1(args, ["task_id"]);
|
|
6747
7101
|
throwIfAborted(exec.signal);
|
|
6748
7102
|
const taskId = requiredBoundedText(args.task_id, "task_id", 256);
|
|
6749
7103
|
if (!/^[A-Za-z0-9._:-]{1,256}$/u.test(taskId)) throw new DesignError("INVALID_ARGUMENT", "task_id is malformed");
|
|
@@ -6756,13 +7110,13 @@ function createTaskTool(controller) {
|
|
|
6756
7110
|
const job = snapshot.jobs.find((candidate) => candidate.jobId === taskId);
|
|
6757
7111
|
return job === void 0 ? {
|
|
6758
7112
|
version: 1,
|
|
6759
|
-
service: "
|
|
6760
|
-
operation: "
|
|
7113
|
+
service: "media",
|
|
7114
|
+
operation: "get_result",
|
|
6761
7115
|
found: false
|
|
6762
7116
|
} : {
|
|
6763
7117
|
version: 1,
|
|
6764
|
-
service: "
|
|
6765
|
-
operation: "
|
|
7118
|
+
service: "media",
|
|
7119
|
+
operation: "get_result",
|
|
6766
7120
|
found: true,
|
|
6767
7121
|
job: projectJob(job)
|
|
6768
7122
|
};
|
|
@@ -6774,29 +7128,40 @@ function createTaskTool(controller) {
|
|
|
6774
7128
|
})
|
|
6775
7129
|
});
|
|
6776
7130
|
}
|
|
6777
|
-
function projectModels(snapshot, query, limit
|
|
7131
|
+
function projectModels(snapshot, query, limit) {
|
|
6778
7132
|
const matches = snapshot.models.filter((model) => query === "" || [
|
|
6779
7133
|
model.id,
|
|
6780
7134
|
model.label,
|
|
6781
|
-
model.kind
|
|
6782
|
-
|
|
7135
|
+
model.kind,
|
|
7136
|
+
model.taskType,
|
|
7137
|
+
model.description
|
|
7138
|
+
].some((value) => value?.toLowerCase().includes(query) === true));
|
|
6783
7139
|
const models = matches.slice(0, limit).map((model) => ({
|
|
6784
7140
|
modelId: model.id,
|
|
6785
7141
|
label: model.label,
|
|
6786
7142
|
kind: model.kind,
|
|
7143
|
+
...model.taskType === void 0 ? {} : { taskType: model.taskType },
|
|
7144
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
6787
7145
|
featured: model.featured,
|
|
6788
7146
|
available: model.available,
|
|
6789
7147
|
...model.unavailableReason === null ? {} : { unavailableReason: modelUnavailableMessage(model.unavailableReason) }
|
|
6790
7148
|
}));
|
|
6791
|
-
const draft = requestedModel === void 0 ? void 0 : snapshot.draft;
|
|
6792
7149
|
return {
|
|
6793
7150
|
version: 1,
|
|
6794
|
-
service: "
|
|
6795
|
-
operation: "
|
|
7151
|
+
service: "media",
|
|
7152
|
+
operation: "list",
|
|
6796
7153
|
models,
|
|
6797
|
-
truncated: matches.length > models.length
|
|
6798
|
-
|
|
6799
|
-
|
|
7154
|
+
truncated: matches.length > models.length
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
function projectSchema(draft) {
|
|
7158
|
+
return {
|
|
7159
|
+
version: 1,
|
|
7160
|
+
service: "media",
|
|
7161
|
+
operation: "schema",
|
|
7162
|
+
modelId: draft.modelId,
|
|
7163
|
+
available: true,
|
|
7164
|
+
schema: {
|
|
6800
7165
|
modelId: draft.modelId,
|
|
6801
7166
|
irContractHash: draft.irContractHash,
|
|
6802
7167
|
primaryInputPath: draft.primaryInputPath,
|
|
@@ -6809,7 +7174,17 @@ function projectModels(snapshot, query, limit, requestedModel) {
|
|
|
6809
7174
|
...field.description === null ? {} : { description: field.description }
|
|
6810
7175
|
})),
|
|
6811
7176
|
truncated: draft.fields.length > MAX_SCHEMA_FIELDS
|
|
6812
|
-
}
|
|
7177
|
+
}
|
|
7178
|
+
};
|
|
7179
|
+
}
|
|
7180
|
+
function projectUnsupportedSchema(modelId) {
|
|
7181
|
+
return {
|
|
7182
|
+
version: 1,
|
|
7183
|
+
service: "media",
|
|
7184
|
+
operation: "schema",
|
|
7185
|
+
modelId,
|
|
7186
|
+
available: false,
|
|
7187
|
+
unavailableReason: "This model's API schema is not compatible with Modellix Design. Choose another model from the live catalog."
|
|
6813
7188
|
};
|
|
6814
7189
|
}
|
|
6815
7190
|
function normalizeGenerationInput(draft, prompt, input) {
|
|
@@ -6837,7 +7212,7 @@ function normalizeGenerationInput(draft, prompt, input) {
|
|
|
6837
7212
|
function projectGeneration(modelId, job) {
|
|
6838
7213
|
if (job === void 0) return {
|
|
6839
7214
|
version: 1,
|
|
6840
|
-
service: "
|
|
7215
|
+
service: "media",
|
|
6841
7216
|
operation: "generate",
|
|
6842
7217
|
modelId,
|
|
6843
7218
|
submitted: true,
|
|
@@ -6846,12 +7221,12 @@ function projectGeneration(modelId, job) {
|
|
|
6846
7221
|
resources: [],
|
|
6847
7222
|
diagnostic: {
|
|
6848
7223
|
code: "job-record-unavailable",
|
|
6849
|
-
message: "The
|
|
7224
|
+
message: "The generation returned without a readable local job record. Do not retry automatically."
|
|
6850
7225
|
}
|
|
6851
7226
|
};
|
|
6852
7227
|
return {
|
|
6853
7228
|
version: 1,
|
|
6854
|
-
service: "
|
|
7229
|
+
service: "media",
|
|
6855
7230
|
operation: "generate",
|
|
6856
7231
|
modelId,
|
|
6857
7232
|
submitted: true,
|
|
@@ -6926,7 +7301,7 @@ function requiredBoundedText(value, field, maximum) {
|
|
|
6926
7301
|
if (value.trim() === "" || value.length > maximum) throw new DesignError("INVALID_ARGUMENT", `${field} must be non-empty and at most ${String(maximum)} characters`);
|
|
6927
7302
|
return value;
|
|
6928
7303
|
}
|
|
6929
|
-
function assertOnlyKeys(value, allowed) {
|
|
7304
|
+
function assertOnlyKeys$1(value, allowed) {
|
|
6930
7305
|
const extra = Object.keys(value).find((key) => !allowed.includes(key));
|
|
6931
7306
|
if (extra !== void 0) throw new DesignError("INVALID_ARGUMENT", `Unknown tool argument: ${extra}`);
|
|
6932
7307
|
}
|
|
@@ -6945,9 +7320,27 @@ function toToolJson(value) {
|
|
|
6945
7320
|
return value;
|
|
6946
7321
|
}
|
|
6947
7322
|
function formatModels(value) {
|
|
6948
|
-
const lines = value.models.map((model) => `- ${model.modelId} (${model.kind})${model.available ? "" : " — unavailable"}`);
|
|
6949
|
-
|
|
6950
|
-
|
|
7323
|
+
const lines = value.models.map((model) => `- ${model.modelId} (${model.taskType ?? model.kind})${model.available ? "" : " — unavailable"}${model.description === void 0 ? "" : `: ${model.description}`}`);
|
|
7324
|
+
return `${lines.length === 0 ? "No matching Modellix media models." : lines.join("\n")}${value.truncated ? "\n(Results truncated; refine the query.)" : ""}`;
|
|
7325
|
+
}
|
|
7326
|
+
function formatSchema(value) {
|
|
7327
|
+
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."}`;
|
|
7328
|
+
const fields = value.schema.fields.map((field) => {
|
|
7329
|
+
const qualifiers = [
|
|
7330
|
+
field.kind,
|
|
7331
|
+
field.required ? "required" : "optional",
|
|
7332
|
+
field.options.length === 0 ? null : `allowed values: ${field.options.map((option) => JSON.stringify(option)).join(", ")}`
|
|
7333
|
+
].filter((qualifier) => qualifier !== null);
|
|
7334
|
+
return `- ${field.path} (${qualifiers.join("; ")})${field.description === void 0 ? "" : ` — ${field.description}`}`;
|
|
7335
|
+
});
|
|
7336
|
+
return [
|
|
7337
|
+
`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.`,
|
|
7338
|
+
fields.length === 0 ? "No input fields." : fields.join("\n"),
|
|
7339
|
+
value.schema.truncated ? "The field list is truncated." : null
|
|
7340
|
+
].filter((line) => line !== null).join("\n");
|
|
7341
|
+
}
|
|
7342
|
+
function isUnsupportedSchema(error) {
|
|
7343
|
+
return error instanceof DesignError && (error.code === "SCHEMA_INVALID" || error.code === "ENDPOINT_NOT_ALLOWED");
|
|
6951
7344
|
}
|
|
6952
7345
|
function formatPreparation(value) {
|
|
6953
7346
|
const changes = value.changes.map((change) => `- ${change.path} (${change.label})`).join("\n");
|
|
@@ -6975,12 +7368,399 @@ function diagnosticMessage(code) {
|
|
|
6975
7368
|
function formatGeneration(value) {
|
|
6976
7369
|
const resources = value.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
|
|
6977
7370
|
const diagnostic = value.diagnostic === void 0 ? "" : `\n${value.diagnostic.message}`;
|
|
6978
|
-
return `Modellix
|
|
7371
|
+
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.`;
|
|
7372
|
+
return `Modellix media status: ${value.status}${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}${resources === "" ? "" : `\n${resources}`}\nDo not automatically repeat this submission.`;
|
|
6979
7373
|
}
|
|
6980
7374
|
function formatTask(value) {
|
|
6981
|
-
if (!value.found || value.job === void 0) return "No persisted Modellix
|
|
7375
|
+
if (!value.found || value.job === void 0) return "No persisted Modellix media task matched that identifier.";
|
|
6982
7376
|
const resources = value.job.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
|
|
6983
|
-
return `Modellix
|
|
7377
|
+
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.`;
|
|
7378
|
+
return `Modellix media task ${value.job.jobId}: ${value.job.status}.${resources === "" ? "" : `\n${resources}`}`;
|
|
7379
|
+
}
|
|
7380
|
+
function disposeAll$1(disposers) {
|
|
7381
|
+
for (const dispose of [...disposers].reverse()) try {
|
|
7382
|
+
dispose();
|
|
7383
|
+
} catch {}
|
|
7384
|
+
}
|
|
7385
|
+
//#endregion
|
|
7386
|
+
//#region src/host/web-tool.ts
|
|
7387
|
+
const MODELLIX_WEB_SEARCH_TOOL = "modellix_web_search";
|
|
7388
|
+
const MODELLIX_WEB_FETCH_TOOL = "modellix_web_fetch";
|
|
7389
|
+
const WEB_TOOL_TIMEOUT_MS = 3e4;
|
|
7390
|
+
const MAX_FETCH_OUTPUT_CHARS = 2e5;
|
|
7391
|
+
/** Build the two explicit Modellix Web tools over the concrete Modellix providers. */
|
|
7392
|
+
function createModellixWebToolDefinitions(providers) {
|
|
7393
|
+
return [createSearchTool(providers), createFetchTool(providers)];
|
|
7394
|
+
}
|
|
7395
|
+
/** Register the explicit tools and roll back atomically if either name conflicts. */
|
|
7396
|
+
function registerModellixWebTools(ctx, providers) {
|
|
7397
|
+
const disposers = [];
|
|
7398
|
+
try {
|
|
7399
|
+
for (const definition of createModellixWebToolDefinitions(providers)) disposers.push(ctx.tools.register(definition));
|
|
7400
|
+
} catch (error) {
|
|
7401
|
+
disposeAll(disposers);
|
|
7402
|
+
throw error;
|
|
7403
|
+
}
|
|
7404
|
+
return () => disposeAll(disposers);
|
|
7405
|
+
}
|
|
7406
|
+
function createSearchTool(providers) {
|
|
7407
|
+
return defineTool({
|
|
7408
|
+
name: MODELLIX_WEB_SEARCH_TOOL,
|
|
7409
|
+
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.",
|
|
7410
|
+
parameters: {
|
|
7411
|
+
query: {
|
|
7412
|
+
type: "string",
|
|
7413
|
+
required: true,
|
|
7414
|
+
description: "One non-empty web search query."
|
|
7415
|
+
},
|
|
7416
|
+
max_results: {
|
|
7417
|
+
type: "integer",
|
|
7418
|
+
description: `Maximum sources to return (default ${String(5)}, maximum ${String(20)}).`
|
|
7419
|
+
}
|
|
7420
|
+
},
|
|
7421
|
+
output: {
|
|
7422
|
+
schema: {
|
|
7423
|
+
type: "object",
|
|
7424
|
+
additionalProperties: false,
|
|
7425
|
+
properties: {
|
|
7426
|
+
version: {
|
|
7427
|
+
type: "integer",
|
|
7428
|
+
const: 1,
|
|
7429
|
+
required: true
|
|
7430
|
+
},
|
|
7431
|
+
service: {
|
|
7432
|
+
type: "string",
|
|
7433
|
+
const: "web",
|
|
7434
|
+
required: true
|
|
7435
|
+
},
|
|
7436
|
+
provider: {
|
|
7437
|
+
type: "string",
|
|
7438
|
+
const: "modellix",
|
|
7439
|
+
required: true
|
|
7440
|
+
},
|
|
7441
|
+
operation: {
|
|
7442
|
+
type: "string",
|
|
7443
|
+
const: "search",
|
|
7444
|
+
required: true
|
|
7445
|
+
},
|
|
7446
|
+
endpoint: {
|
|
7447
|
+
type: "string",
|
|
7448
|
+
const: MODELLIX_WEB_SEARCH_ENDPOINT,
|
|
7449
|
+
required: true
|
|
7450
|
+
},
|
|
7451
|
+
query: {
|
|
7452
|
+
type: "string",
|
|
7453
|
+
required: true
|
|
7454
|
+
},
|
|
7455
|
+
content: { type: "string" },
|
|
7456
|
+
sources: {
|
|
7457
|
+
type: "array",
|
|
7458
|
+
required: true,
|
|
7459
|
+
items: {
|
|
7460
|
+
type: "object",
|
|
7461
|
+
additionalProperties: false,
|
|
7462
|
+
properties: {
|
|
7463
|
+
url: {
|
|
7464
|
+
type: "string",
|
|
7465
|
+
required: true
|
|
7466
|
+
},
|
|
7467
|
+
title: { type: "string" },
|
|
7468
|
+
snippet: { type: "string" },
|
|
7469
|
+
publishedAt: { type: "string" }
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
7472
|
+
},
|
|
7473
|
+
truncated: {
|
|
7474
|
+
type: "boolean",
|
|
7475
|
+
required: true
|
|
7476
|
+
},
|
|
7477
|
+
noAutomaticRetry: {
|
|
7478
|
+
type: "boolean",
|
|
7479
|
+
const: true,
|
|
7480
|
+
required: true
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
},
|
|
7484
|
+
render: (_args, value) => [{
|
|
7485
|
+
type: "text",
|
|
7486
|
+
text: formatSearchOutput(value)
|
|
7487
|
+
}],
|
|
7488
|
+
presentationMeta: (_args, value) => searchPresentationMeta(value)
|
|
7489
|
+
},
|
|
7490
|
+
timeoutMs: WEB_TOOL_TIMEOUT_MS,
|
|
7491
|
+
async execute(args, exec) {
|
|
7492
|
+
assertOnlyKeys(args, ["query", "max_results"]);
|
|
7493
|
+
const query = requireNonBlank(args.query, "query");
|
|
7494
|
+
const maxResults = boundedResultCount(args.max_results);
|
|
7495
|
+
return projectSearchResult(query, await providers.search.search({
|
|
7496
|
+
query,
|
|
7497
|
+
maxResults
|
|
7498
|
+
}, exec.signal));
|
|
7499
|
+
},
|
|
7500
|
+
presentCall: (args) => ({
|
|
7501
|
+
card: "generic",
|
|
7502
|
+
title: `Modellix Web Search · ${args.query}`,
|
|
7503
|
+
kind: "search",
|
|
7504
|
+
rawInput: args.query
|
|
7505
|
+
}),
|
|
7506
|
+
presentResult: (args, result) => presentSearchResult(args.query, result)
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
function createFetchTool(providers) {
|
|
7510
|
+
return defineTool({
|
|
7511
|
+
name: MODELLIX_WEB_FETCH_TOOL,
|
|
7512
|
+
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.",
|
|
7513
|
+
parameters: { url: {
|
|
7514
|
+
type: "string",
|
|
7515
|
+
required: true,
|
|
7516
|
+
description: "The public HTTP(S) URL to retrieve."
|
|
7517
|
+
} },
|
|
7518
|
+
output: {
|
|
7519
|
+
schema: {
|
|
7520
|
+
type: "object",
|
|
7521
|
+
additionalProperties: false,
|
|
7522
|
+
properties: {
|
|
7523
|
+
version: {
|
|
7524
|
+
type: "integer",
|
|
7525
|
+
const: 1,
|
|
7526
|
+
required: true
|
|
7527
|
+
},
|
|
7528
|
+
service: {
|
|
7529
|
+
type: "string",
|
|
7530
|
+
const: "web",
|
|
7531
|
+
required: true
|
|
7532
|
+
},
|
|
7533
|
+
provider: {
|
|
7534
|
+
type: "string",
|
|
7535
|
+
const: "modellix",
|
|
7536
|
+
required: true
|
|
7537
|
+
},
|
|
7538
|
+
operation: {
|
|
7539
|
+
type: "string",
|
|
7540
|
+
const: "fetch",
|
|
7541
|
+
required: true
|
|
7542
|
+
},
|
|
7543
|
+
endpoint: {
|
|
7544
|
+
type: "string",
|
|
7545
|
+
const: MODELLIX_WEB_FETCH_ENDPOINT,
|
|
7546
|
+
required: true
|
|
7547
|
+
},
|
|
7548
|
+
requestedUrl: {
|
|
7549
|
+
type: "string",
|
|
7550
|
+
required: true
|
|
7551
|
+
},
|
|
7552
|
+
url: {
|
|
7553
|
+
type: "string",
|
|
7554
|
+
required: true
|
|
7555
|
+
},
|
|
7556
|
+
statusCode: {
|
|
7557
|
+
type: "integer",
|
|
7558
|
+
required: true
|
|
7559
|
+
},
|
|
7560
|
+
body: {
|
|
7561
|
+
required: true,
|
|
7562
|
+
oneOf: [{
|
|
7563
|
+
type: "object",
|
|
7564
|
+
additionalProperties: false,
|
|
7565
|
+
properties: {
|
|
7566
|
+
kind: {
|
|
7567
|
+
type: "string",
|
|
7568
|
+
const: "html",
|
|
7569
|
+
required: true
|
|
7570
|
+
},
|
|
7571
|
+
content: {
|
|
7572
|
+
type: "string",
|
|
7573
|
+
required: true
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
}, {
|
|
7577
|
+
type: "object",
|
|
7578
|
+
additionalProperties: false,
|
|
7579
|
+
properties: {
|
|
7580
|
+
kind: {
|
|
7581
|
+
type: "string",
|
|
7582
|
+
const: "text",
|
|
7583
|
+
required: true
|
|
7584
|
+
},
|
|
7585
|
+
content: {
|
|
7586
|
+
type: "string",
|
|
7587
|
+
required: true
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
}]
|
|
7591
|
+
},
|
|
7592
|
+
truncated: {
|
|
7593
|
+
type: "boolean",
|
|
7594
|
+
required: true
|
|
7595
|
+
},
|
|
7596
|
+
noAutomaticRetry: {
|
|
7597
|
+
type: "boolean",
|
|
7598
|
+
const: true,
|
|
7599
|
+
required: true
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
},
|
|
7603
|
+
render: (_args, value) => [{
|
|
7604
|
+
type: "text",
|
|
7605
|
+
text: formatFetchOutput(value)
|
|
7606
|
+
}],
|
|
7607
|
+
presentationMeta: (_args, value) => fetchPresentationMeta(value)
|
|
7608
|
+
},
|
|
7609
|
+
timeoutMs: WEB_TOOL_TIMEOUT_MS,
|
|
7610
|
+
async execute(args, exec) {
|
|
7611
|
+
assertOnlyKeys(args, ["url"]);
|
|
7612
|
+
const requestedUrl = requireNonBlank(args.url, "url");
|
|
7613
|
+
return projectFetchResult(requestedUrl, await providers.fetch.fetch({ url: requestedUrl }, exec.signal));
|
|
7614
|
+
},
|
|
7615
|
+
presentCall: (args) => ({
|
|
7616
|
+
card: "generic",
|
|
7617
|
+
title: `Modellix Web Fetch · ${args.url}`,
|
|
7618
|
+
kind: "fetch",
|
|
7619
|
+
rawInput: args.url
|
|
7620
|
+
}),
|
|
7621
|
+
presentResult: (args, result) => presentFetchResult(args.url, result)
|
|
7622
|
+
});
|
|
7623
|
+
}
|
|
7624
|
+
function projectSearchResult(query, result) {
|
|
7625
|
+
return {
|
|
7626
|
+
version: 1,
|
|
7627
|
+
service: "web",
|
|
7628
|
+
provider: "modellix",
|
|
7629
|
+
operation: "search",
|
|
7630
|
+
endpoint: MODELLIX_WEB_SEARCH_ENDPOINT,
|
|
7631
|
+
query,
|
|
7632
|
+
...result.content === void 0 ? {} : { content: result.content },
|
|
7633
|
+
sources: result.sources.map(projectSource),
|
|
7634
|
+
truncated: result.truncated,
|
|
7635
|
+
noAutomaticRetry: true
|
|
7636
|
+
};
|
|
7637
|
+
}
|
|
7638
|
+
function projectFetchResult(requestedUrl, result) {
|
|
7639
|
+
const content = result.body.content.slice(0, MAX_FETCH_OUTPUT_CHARS);
|
|
7640
|
+
return {
|
|
7641
|
+
version: 1,
|
|
7642
|
+
service: "web",
|
|
7643
|
+
provider: "modellix",
|
|
7644
|
+
operation: "fetch",
|
|
7645
|
+
endpoint: MODELLIX_WEB_FETCH_ENDPOINT,
|
|
7646
|
+
requestedUrl,
|
|
7647
|
+
url: result.url,
|
|
7648
|
+
statusCode: result.statusCode,
|
|
7649
|
+
body: {
|
|
7650
|
+
kind: result.body.kind,
|
|
7651
|
+
content
|
|
7652
|
+
},
|
|
7653
|
+
truncated: result.truncated || content.length !== result.body.content.length,
|
|
7654
|
+
noAutomaticRetry: true
|
|
7655
|
+
};
|
|
7656
|
+
}
|
|
7657
|
+
function projectSource(source) {
|
|
7658
|
+
return {
|
|
7659
|
+
url: source.url,
|
|
7660
|
+
...source.title === void 0 ? {} : { title: source.title },
|
|
7661
|
+
...source.snippet === void 0 ? {} : { snippet: source.snippet },
|
|
7662
|
+
...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
|
|
7663
|
+
};
|
|
7664
|
+
}
|
|
7665
|
+
function formatSearchOutput(value) {
|
|
7666
|
+
const parts = [`Modellix Web Search completed for: ${value.query}`, `Provider endpoint: ${value.endpoint}`];
|
|
7667
|
+
if (value.content !== void 0 && value.content.length > 0) parts.push(value.content);
|
|
7668
|
+
if (value.sources.length === 0) parts.push("No sources found.");
|
|
7669
|
+
else parts.push(`Sources:\n${value.sources.map(formatSource).join("\n")}`);
|
|
7670
|
+
if (value.truncated) parts.push("The source list was truncated; refine the query if needed.");
|
|
7671
|
+
parts.push("Cite the relevant source URLs as markdown links in the answer.");
|
|
7672
|
+
return parts.join("\n\n");
|
|
7673
|
+
}
|
|
7674
|
+
function formatSource(source) {
|
|
7675
|
+
const label = source.title === void 0 || source.title.length === 0 ? source.url : source.title;
|
|
7676
|
+
const details = [source.snippet, source.publishedAt].filter((value) => value !== void 0 && value.length > 0);
|
|
7677
|
+
return `- ${label}\n URL: ${source.url}${details.length === 0 ? "" : `\n ${details.join(" · ")}`}`;
|
|
7678
|
+
}
|
|
7679
|
+
function formatFetchOutput(value) {
|
|
7680
|
+
const header = [`Modellix Web Fetch completed: ${value.url} (HTTP ${String(value.statusCode)})`, `Provider endpoint: ${value.endpoint}`].join("\n");
|
|
7681
|
+
const footer = value.truncated ? "\n\n(Content truncated. Fetch a more specific public URL if more detail is required.)" : "";
|
|
7682
|
+
return `${header}\n\n${value.body.content}${footer}`;
|
|
7683
|
+
}
|
|
7684
|
+
function searchPresentationMeta(value) {
|
|
7685
|
+
return {
|
|
7686
|
+
sources: value.sources.map((source) => ({
|
|
7687
|
+
url: source.url,
|
|
7688
|
+
...source.title === void 0 ? {} : { title: source.title },
|
|
7689
|
+
...source.snippet === void 0 ? {} : { snippet: source.snippet },
|
|
7690
|
+
...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
|
|
7691
|
+
})),
|
|
7692
|
+
truncated: value.truncated,
|
|
7693
|
+
...value.content === void 0 ? {} : { answer: value.content }
|
|
7694
|
+
};
|
|
7695
|
+
}
|
|
7696
|
+
function fetchPresentationMeta(value) {
|
|
7697
|
+
return {
|
|
7698
|
+
url: value.url,
|
|
7699
|
+
statusCode: value.statusCode,
|
|
7700
|
+
truncated: value.truncated
|
|
7701
|
+
};
|
|
7702
|
+
}
|
|
7703
|
+
function presentSearchResult(query, result) {
|
|
7704
|
+
if (result.isError) return void 0;
|
|
7705
|
+
const meta = asRecord(result.meta);
|
|
7706
|
+
if (meta === void 0 || !Array.isArray(meta.sources) || typeof meta.truncated !== "boolean") return void 0;
|
|
7707
|
+
const sources = [];
|
|
7708
|
+
for (const candidate of meta.sources) {
|
|
7709
|
+
const source = asRecord(candidate);
|
|
7710
|
+
if (source === void 0 || typeof source.url !== "string" || !optionalStrings(source, [
|
|
7711
|
+
"title",
|
|
7712
|
+
"snippet",
|
|
7713
|
+
"publishedAt"
|
|
7714
|
+
])) return void 0;
|
|
7715
|
+
sources.push({
|
|
7716
|
+
url: source.url,
|
|
7717
|
+
...typeof source.title === "string" ? { title: source.title } : {},
|
|
7718
|
+
...typeof source.snippet === "string" ? { snippet: source.snippet } : {},
|
|
7719
|
+
...typeof source.publishedAt === "string" ? { publishedAt: source.publishedAt } : {}
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
if (meta.answer !== void 0 && typeof meta.answer !== "string") return void 0;
|
|
7723
|
+
return {
|
|
7724
|
+
card: "web",
|
|
7725
|
+
kind: "search",
|
|
7726
|
+
title: `Modellix Web Search · ${query}`,
|
|
7727
|
+
sources,
|
|
7728
|
+
truncated: meta.truncated,
|
|
7729
|
+
...typeof meta.answer === "string" ? { answer: meta.answer } : {}
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7732
|
+
function presentFetchResult(requestedUrl, result) {
|
|
7733
|
+
if (result.isError) return void 0;
|
|
7734
|
+
const meta = asRecord(result.meta);
|
|
7735
|
+
if (meta === void 0 || typeof meta.url !== "string" || typeof meta.statusCode !== "number" || !Number.isInteger(meta.statusCode) || typeof meta.truncated !== "boolean") return void 0;
|
|
7736
|
+
return {
|
|
7737
|
+
card: "web",
|
|
7738
|
+
kind: "fetch",
|
|
7739
|
+
title: `Modellix Web Fetch · ${requestedUrl}`,
|
|
7740
|
+
url: meta.url,
|
|
7741
|
+
statusCode: meta.statusCode,
|
|
7742
|
+
truncated: meta.truncated
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
function boundedResultCount(value) {
|
|
7746
|
+
const resolved = value ?? 5;
|
|
7747
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > 20) throw new TypeError(`max_results must be an integer from 1 through ${String(20)}`);
|
|
7748
|
+
return resolved;
|
|
7749
|
+
}
|
|
7750
|
+
function requireNonBlank(value, name) {
|
|
7751
|
+
if (value.trim().length === 0) throw new TypeError(`${name} must contain non-whitespace text`);
|
|
7752
|
+
return value;
|
|
7753
|
+
}
|
|
7754
|
+
function assertOnlyKeys(value, allowed) {
|
|
7755
|
+
const accepted = new Set(allowed);
|
|
7756
|
+
const unexpected = Object.keys(value).find((key) => !accepted.has(key));
|
|
7757
|
+
if (unexpected !== void 0) throw new TypeError(`Unexpected tool argument: ${unexpected}`);
|
|
7758
|
+
}
|
|
7759
|
+
function optionalStrings(value, keys) {
|
|
7760
|
+
return keys.every((key) => value[key] === void 0 || typeof value[key] === "string");
|
|
7761
|
+
}
|
|
7762
|
+
function asRecord(value) {
|
|
7763
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6984
7764
|
}
|
|
6985
7765
|
function disposeAll(disposers) {
|
|
6986
7766
|
for (const dispose of [...disposers].reverse()) try {
|
|
@@ -7227,24 +8007,44 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7227
8007
|
this.scheduleDesignPoll(0);
|
|
7228
8008
|
});
|
|
7229
8009
|
});
|
|
7230
|
-
this.#ctx.effect(() =>
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
8010
|
+
this.#ctx.effect(() => {
|
|
8011
|
+
const providers = createModellixWebProviders({
|
|
8012
|
+
isEnabled: () => this.#config.services.web.enabled,
|
|
8013
|
+
hasCredential: () => this.credentialIsUsable(),
|
|
8014
|
+
resolveCredential: async () => {
|
|
8015
|
+
const hit = await this.resolveUsableCredential();
|
|
8016
|
+
if (hit === void 0) this.requestCredentialRecovery();
|
|
8017
|
+
return hit === void 0 ? null : {
|
|
8018
|
+
apiKey: hit.value,
|
|
8019
|
+
credentialEpoch: hit.credentialEpoch
|
|
8020
|
+
};
|
|
8021
|
+
},
|
|
8022
|
+
getUserId: () => this.#userId,
|
|
8023
|
+
isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
|
|
8024
|
+
onCredentialRejected: async (epoch) => {
|
|
8025
|
+
if (epoch !== this.#config.credentialEpoch) return;
|
|
8026
|
+
this.markCredentialRejected(epoch);
|
|
8027
|
+
}
|
|
8028
|
+
});
|
|
8029
|
+
const disposeProviders = registerModellixWebProviderInstances(this.#ctx.web, providers);
|
|
8030
|
+
try {
|
|
8031
|
+
const disposeTools = registerModellixWebTools(this.#ctx, providers);
|
|
8032
|
+
return () => {
|
|
8033
|
+
disposeTools();
|
|
8034
|
+
disposeProviders();
|
|
7239
8035
|
};
|
|
7240
|
-
}
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
onCredentialRejected: async (epoch) => {
|
|
7244
|
-
if (epoch !== this.#config.credentialEpoch) return;
|
|
7245
|
-
this.markCredentialRejected(epoch);
|
|
8036
|
+
} catch (error) {
|
|
8037
|
+
disposeProviders();
|
|
8038
|
+
throw error;
|
|
7246
8039
|
}
|
|
7247
|
-
}
|
|
8040
|
+
}, "dsh-modellix: Web providers and explicit Modellix tools");
|
|
8041
|
+
this.#ctx.on("agent/session-start", ({ agent }) => {
|
|
8042
|
+
const message = createModellixRoutingMessage({
|
|
8043
|
+
mediaEnabled: this.#config.services.design.enabled && this.credentialIsUsable(),
|
|
8044
|
+
webEnabled: this.#config.services.web.enabled && this.credentialIsUsable()
|
|
8045
|
+
});
|
|
8046
|
+
if (message !== null) agent.inject(message);
|
|
8047
|
+
});
|
|
7248
8048
|
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");
|
|
7249
8049
|
this.#ctx.effect(() => {
|
|
7250
8050
|
this.scheduleDesignPoll(2e3);
|
|
@@ -7904,11 +8704,14 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7904
8704
|
}
|
|
7905
8705
|
syncDesignTools(enabled) {
|
|
7906
8706
|
if (enabled && this.#disposeDesignTools === void 0) {
|
|
7907
|
-
this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, {
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8707
|
+
this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, {
|
|
8708
|
+
resolveCredential: async () => (await this.resolveUsableCredential())?.value,
|
|
8709
|
+
handle: (endpoint, payload, signal) => {
|
|
8710
|
+
const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
|
|
8711
|
+
if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
|
|
8712
|
+
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal));
|
|
8713
|
+
}
|
|
8714
|
+
});
|
|
7912
8715
|
return;
|
|
7913
8716
|
}
|
|
7914
8717
|
if (!enabled && this.#disposeDesignTools !== void 0) {
|
|
@@ -8075,6 +8878,6 @@ async function apply(ctx) {
|
|
|
8075
8878
|
await ModellixRuntime.create(ctx);
|
|
8076
8879
|
}
|
|
8077
8880
|
//#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,
|
|
8881
|
+
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
8882
|
|
|
8080
8883
|
//# sourceMappingURL=index.js.map
|