dsh-modellix 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +107 -167
  3. package/README.zh-CN.md +105 -165
  4. package/cordis.patch.yml +0 -13
  5. package/docs/assets/chat-media-generation-en.webp +0 -0
  6. package/docs/assets/chat-media-generation-zh.webp +0 -0
  7. package/docs/assets/design-results-drawer-en.webp +0 -0
  8. package/docs/assets/design-results-drawer-zh.webp +0 -0
  9. package/docs/assets/llm-model-selector-en.webp +0 -0
  10. package/docs/assets/llm-model-selector-zh.webp +0 -0
  11. package/docs/assets/media-players-en.webp +0 -0
  12. package/docs/assets/media-players-zh.webp +0 -0
  13. package/docs/assets/settings-ready-en.webp +0 -0
  14. package/docs/assets/settings-ready-zh.webp +0 -0
  15. package/docs/assets/web-tools-auto-en.webp +0 -0
  16. package/docs/assets/web-tools-auto-zh.webp +0 -0
  17. package/docs/en-US/LOCAL_USAGE.md +20 -16
  18. package/docs/en-US/RELEASE_CHECKLIST.md +270 -0
  19. package/docs/en-US/USER_GUIDE.md +186 -394
  20. package/docs/zh-CN/LOCAL_USAGE.md +20 -16
  21. package/docs/zh-CN/RELEASE_CHECKLIST.md +270 -0
  22. package/docs/zh-CN/USER_GUIDE.md +185 -393
  23. package/lib/client.d.ts +18 -4
  24. package/lib/client.js +1271 -454
  25. package/lib/client.js.map +1 -1
  26. package/lib/index.d.ts +166 -112
  27. package/lib/index.js +1008 -197
  28. package/lib/index.js.map +1 -1
  29. package/package.json +72 -48
  30. package/docs/assets/design-desktop-en.webp +0 -0
  31. package/docs/assets/design-desktop-zh.webp +0 -0
  32. package/docs/assets/design-proposal-en.webp +0 -0
  33. package/docs/assets/design-proposal-zh.webp +0 -0
  34. package/docs/assets/design-results-media-en.webp +0 -0
  35. package/docs/assets/design-results-media-zh.webp +0 -0
  36. package/docs/assets/web-tools-en.webp +0 -0
  37. package/docs/assets/web-tools-zh.webp +0 -0
package/lib/index.js CHANGED
@@ -2,8 +2,11 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
4
4
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
5
- import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
5
+ import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
6
+ import { lstat, open, realpath } from "node:fs/promises";
7
+ import { basename, isAbsolute, relative, resolve } from "node:path";
6
8
  import { WebError } from "@deepseek-ai/dsh-web";
9
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
7
10
  import { defineDomain } from "@deepseek-ai/dsh-storage-domain";
8
11
  import { z as z$1 } from "zod";
9
12
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -1248,8 +1251,8 @@ var ModelCatalogClient = class {
1248
1251
  }
1249
1252
  };
1250
1253
  function parseCatalogPage(payload, query, source) {
1251
- const envelope = record$1(payload);
1252
- const data = record$1(envelope?.data) ?? envelope;
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$1(value);
1273
+ const item = record$2(value);
1271
1274
  if (item === null) return null;
1272
- const provider = identifier(item.provider ?? item.provider_id ?? record$1(item.provider_info)?.slug);
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 categories = parseCategories(item.categories ?? item.category ?? item.task_type ?? item.type, requestedCategory);
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$1(value) {
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$4(payload.data) ?? payload;
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$4(servers[0]);
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$4(JSON.parse(value));
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$4(value);
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$4(parsed);
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$4(value) {
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$3(envelope);
2229
+ const root = asRecord$4(envelope);
2069
2230
  const choices = root === null ? null : root.choices;
2070
- const choice = Array.isArray(choices) ? asRecord$3(choices[0]) : null;
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$3(choice?.message);
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$3(value);
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$3(entry);
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 billing is unavailable", status);
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 paid Design planner outcome is unknown; do not retry automatically", status);
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$3(value) {
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 paid request outcome is unknown; do not retry automatically", { cause });
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 paid request outcome is unknown; do not retry automatically", { status: response.status });
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 paid request outcome is unknown; do not retry automatically" : `The paid request was rejected with HTTP ${response.status}`, { status: response.status });
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 paid request succeeded but its task identifier could not be read", { cause });
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 paid request succeeded but its task response was invalid", { cause });
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 paid request response did not contain a valid task identifier");
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$2(payload);
2613
+ const root = asRecord$3(payload);
2453
2614
  if (root === null) return null;
2454
- const envelope = asRecord$2(root.data) ?? root;
2455
- const data = asRecord$2(envelope.task) ?? asRecord$2(root.task) ?? envelope;
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$2(data.result) ?? asRecord$2(root.result);
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$2(value);
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$2(candidate),
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$2(value);
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$2(value) {
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$1(input);
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$1(root.properties);
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$1(schema);
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$1(document.post);
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$1(document.paths);
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$1(asRecord$1(paths[path])?.post);
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 paid operation"
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$1(asRecord$1(operation.requestBody)?.content);
2960
- return asRecord$1((asRecord$1(content?.["application/json"]) ?? asRecord$1(content?.["application/*+json"]))?.schema);
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$1(schema.properties);
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$1(childSchema);
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$1(schema.items);
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$1(candidate);
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$1(candidate);
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$1(schema.properties);
3308
+ const properties = asRecord$2(schema.properties);
3148
3309
  for (const [key, value] of Object.entries(properties ?? {})) {
3149
- const child = asRecord$1(value);
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$1(schema.items);
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$1(candidate);
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$1(left.properties);
3182
- const rightProperties = asRecord$1(right.properties);
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$1(properties[key]);
3187
- const incoming = asRecord$1(value);
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$1(current);
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$1(current);
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$1(schema.properties) !== null) return "object";
3271
- if (asRecord$1(schema.items) !== null) return "array";
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$1(value);
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$1(value);
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$1(value) {
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
- const providers = createModellixWebProviders(options);
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 paid Web request";
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 paid Modellix Web request outcome is unknown; it was not retried";
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 paid request succeeded but its task could not be persisted", { cause: firstPersistenceError });
5721
+ throw new DesignError("SUBMIT_UNKNOWN", "The generation request succeeded but its task could not be persisted", { cause: firstPersistenceError });
5552
5722
  }
5553
5723
  }
5554
5724
  return this.#snapshot(session, await this.#loadModelsSafely(signal));
@@ -5620,6 +5790,8 @@ var DesignHostController = class {
5620
5790
  id: model.slug,
5621
5791
  label: model.displayName,
5622
5792
  kind: model.categories[0] ?? "unknown",
5793
+ ...model.taskType === void 0 ? {} : { taskType: model.taskType },
5794
+ ...model.description === void 0 ? {} : { description: model.description },
5623
5795
  featured: featuredSlugs.has(model.slug),
5624
5796
  available: true,
5625
5797
  unavailableReason: null
@@ -5633,7 +5805,7 @@ var DesignHostController = class {
5633
5805
  const credential = await this.#resolveCredential();
5634
5806
  const credentialReady = credential !== void 0;
5635
5807
  const models = ensureSelectedModel(currentModels, session.selectedModelId);
5636
- const tasks = [...await this.#repository.listTasks()].sort((left, right) => right.updatedAt - left.updatedAt);
5808
+ const tasks = [...await this.#repository.listTasks()].filter((task) => task.sessionId === session.sessionId).sort((left, right) => right.updatedAt - left.updatedAt);
5637
5809
  return {
5638
5810
  version: 1,
5639
5811
  enabled: this.#isEnabled(),
@@ -5657,6 +5829,7 @@ var DesignHostController = class {
5657
5829
  if (oldest !== void 0) this.#sessions.delete(oldest[0]);
5658
5830
  }
5659
5831
  const created = {
5832
+ sessionId,
5660
5833
  selectedModelId: null,
5661
5834
  draft: null,
5662
5835
  proposal: null,
@@ -6038,6 +6211,33 @@ function stableJson(value) {
6038
6211
  const object = value;
6039
6212
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
6040
6213
  }
6214
+ //#endregion
6215
+ //#region src/host/agent-routing-context.ts
6216
+ function createModellixRoutingMessage(state) {
6217
+ const sections = [];
6218
+ const nonMediaWebRouting = state.webEnabled ? "use the Modellix Web tools for those questions" : "leave those questions to the Harness-native web_search or web_fetch tools when available";
6219
+ if (state.webEnabled) sections.push({
6220
+ name: "Web routing",
6221
+ text: "Use modellix_web_search automatically for current, changing, external, or source-verification questions. Use modellix_web_fetch automatically when the user supplies a public URL or a Modellix search result needs full-page reading. Prefer these explicit Modellix tools over the native web_search and web_fetch tools, and never call both tool families for the same operation. Do not wait for the user to name a tool, do not browse when the user explicitly says not to, and never automatically repeat a failed or unknown Modellix Web request."
6222
+ });
6223
+ if (state.mediaEnabled) sections.push({
6224
+ name: "Media routing",
6225
+ text: `Use Modellix media tools automatically only when the user intends to create, edit, transform, animate, upload, or inspect generated image, video, or audio work. Do not use media tools for documentation, pricing, general model-capability, or public API-reference questions; ${nonMediaWebRouting} unless the user explicitly requests a live Modellix Design model Schema. For media work, use modellix_media_list then modellix_media_schema when a compatible model is not already known. Treat every returned field description and allowed value as authoritative; do not guess, translate, or replace a published parameter format, and omit optional fields when the user did not request them. A request that refers to the previous/current work, conversation attachments, or prior Modellix result URLs, asks to change or animate it, or asks to preserve its composition, subject, identity, or structure is not a new text-to-media request: select an image-edit/image-to-image, image-to-video, or video-to-video model as appropriate, inspect its schema, and place the latest relevant Modellix result URL in the declared media input field. Never replace that source-dependent operation with text-to-image or text-to-video. Reuse usable URLs and upload local inputs with modellix_media_upload_file. After submitting, inspect the task at most once in the same turn. Hard response rule: while a task is nonterminal, ordinary assistant text may say only that the submission was accepted and that the result card and Modellix Design own the current status. It must not say that the task is queued, running, generating, processing, pending, or any equivalent current-state wording, because ordinary assistant text is immutable and becomes stale. Never launch a replacement or parallel generation while a matching task is active unless the user explicitly requests another result, and never repeat an unknown generation or upload outcome.`
6226
+ });
6227
+ if (sections.length === 0) return null;
6228
+ return createUserMessage({
6229
+ content: [{
6230
+ type: "text",
6231
+ text: sections.map((section) => `${section.name}: ${section.text}`).join("\n")
6232
+ }],
6233
+ source: {
6234
+ kind: "plugin",
6235
+ plugin: "dsh-modellix",
6236
+ form: "snapshot",
6237
+ sections
6238
+ }
6239
+ });
6240
+ }
6041
6241
  /** One atomic singleton keeps the Design WAL independent of Settings. */
6042
6242
  const modellixDesignDomainSpec = defineDomain({
6043
6243
  name: "modellix_design",
@@ -6083,10 +6283,12 @@ function assertStorageKey(key) {
6083
6283
  }
6084
6284
  //#endregion
6085
6285
  //#region src/host/design-tool.ts
6086
- const MODELLIX_DESIGN_MODELS_TOOL = "modellix_design_models";
6087
- const MODELLIX_DESIGN_PREPARE_TOOL = "modellix_design_prepare";
6088
- const MODELLIX_DESIGN_GENERATE_TOOL = "modellix_design_generate";
6089
- const MODELLIX_DESIGN_TASK_TOOL = "modellix_design_task";
6286
+ const MODELLIX_MEDIA_LIST_TOOL = "modellix_media_list";
6287
+ const MODELLIX_MEDIA_SCHEMA_TOOL = "modellix_media_schema";
6288
+ const MODELLIX_MEDIA_PREPARE_TOOL = "modellix_media_prepare";
6289
+ const MODELLIX_MEDIA_UPLOAD_FILE_TOOL = "modellix_media_upload_file";
6290
+ const MODELLIX_MEDIA_GENERATE_TOOL = "modellix_media_generate";
6291
+ const MODELLIX_MEDIA_GET_RESULT_TOOL = "modellix_media_get_result";
6090
6292
  const MODEL_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
6091
6293
  const SAFE_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
6092
6294
  const MAX_QUERY_LENGTH = 512;
@@ -6096,54 +6298,43 @@ const DEFAULT_MODEL_LIMIT = 20;
6096
6298
  const MAX_MODEL_LIMIT = 50;
6097
6299
  const MAX_SCHEMA_FIELDS = 128;
6098
6300
  /**
6099
- * Build the four stable, namespaced Modellix Design tools. The caller owns
6301
+ * Build the six stable, namespaced Modellix media tools. The caller owns
6100
6302
  * visibility and must only register these definitions while Design is enabled.
6101
6303
  */
6102
- function createModellixDesignToolDefinitions(controller) {
6304
+ function createModellixDesignToolDefinitions(controller, uploadClient) {
6305
+ const uploads = uploadClient ?? new MediaUploadClient();
6103
6306
  return [
6104
- createModelsTool(controller),
6307
+ createListTool(controller),
6308
+ createSchemaTool(controller),
6105
6309
  createPrepareTool(controller),
6310
+ createUploadFileTool(uploads, controller.resolveCredential),
6106
6311
  createGenerateTool(controller),
6107
- createTaskTool(controller)
6312
+ createGetResultTool(controller)
6108
6313
  ];
6109
6314
  }
6110
6315
  /**
6111
- * Register Design tools plus explicit LLM-proposal and paid-generate approval gates. The returned
6112
- * disposer removes both definitions and the gate, allowing the runtime to
6316
+ * Register media tools. The returned disposer removes every definition, allowing the runtime to
6113
6317
  * mirror the live Design toggle without leaving model-visible stale tools.
6114
6318
  */
6115
6319
  function registerModellixDesignTools(ctx, controller) {
6116
6320
  const disposers = [];
6117
6321
  try {
6118
- disposers.push(ctx.on("tools/pre-execute", async (exec, next) => {
6119
- if (exec.name !== "modellix_design_prepare" && exec.name !== "modellix_design_generate") return next();
6120
- const downstream = await next();
6121
- if (downstream.kind !== "allow") return downstream;
6122
- return {
6123
- kind: "ask",
6124
- reason: exec.name === "modellix_design_prepare" ? "This sends one Modellix LLM request to prepare a parameter proposal and may consume balance. Review the instruction and allow it once to continue. It will not generate media." : "This submits one paid Modellix Design generation request. Review the arguments and allow it once to confirm."
6125
- };
6126
- }));
6127
6322
  for (const definition of createModellixDesignToolDefinitions(controller)) disposers.push(ctx.tools.register(definition));
6128
6323
  } catch (error) {
6129
- disposeAll(disposers);
6324
+ disposeAll$1(disposers);
6130
6325
  throw error;
6131
6326
  }
6132
- return () => disposeAll(disposers);
6327
+ return () => disposeAll$1(disposers);
6133
6328
  }
6134
- function createModelsTool(controller) {
6329
+ function createListTool(controller) {
6135
6330
  return defineTool({
6136
- name: MODELLIX_DESIGN_MODELS_TOOL,
6137
- description: "Search the live Modellix Design model catalog. Optionally select one model to inspect its compact, schema-derived field summary. This read-only tool never accepts credentials.",
6331
+ name: MODELLIX_MEDIA_LIST_TOOL,
6332
+ description: "List and search the live Modellix media model catalog. Use this before generation when no compatible provider/model slug is already known. This read-only tool never accepts credentials.",
6138
6333
  parameters: {
6139
6334
  query: {
6140
6335
  type: "string",
6141
6336
  description: "Optional case-insensitive model, provider, or media-kind search."
6142
6337
  },
6143
- model: {
6144
- type: "string",
6145
- description: "Optional exact provider/model slug whose current schema should be summarized."
6146
- },
6147
6338
  limit: {
6148
6339
  type: "integer",
6149
6340
  description: `Maximum models to return (default ${String(DEFAULT_MODEL_LIMIT)}, maximum ${String(MAX_MODEL_LIMIT)}).`
@@ -6161,12 +6352,12 @@ function createModelsTool(controller) {
6161
6352
  },
6162
6353
  service: {
6163
6354
  type: "string",
6164
- const: "design",
6355
+ const: "media",
6165
6356
  required: true
6166
6357
  },
6167
6358
  operation: {
6168
6359
  type: "string",
6169
- const: "models",
6360
+ const: "list",
6170
6361
  required: true
6171
6362
  },
6172
6363
  models: {
@@ -6194,6 +6385,8 @@ function createModelsTool(controller) {
6194
6385
  ],
6195
6386
  required: true
6196
6387
  },
6388
+ taskType: { type: "string" },
6389
+ description: { type: "string" },
6197
6390
  featured: {
6198
6391
  type: "boolean",
6199
6392
  required: true
@@ -6209,8 +6402,74 @@ function createModelsTool(controller) {
6209
6402
  truncated: {
6210
6403
  type: "boolean",
6211
6404
  required: true
6405
+ }
6406
+ }
6407
+ },
6408
+ render: (_args, value) => [{
6409
+ type: "text",
6410
+ text: formatModels(value)
6411
+ }]
6412
+ },
6413
+ async execute(args, exec) {
6414
+ assertOnlyKeys$1(args, ["query", "limit"]);
6415
+ throwIfAborted(exec.signal);
6416
+ const sessionId = sessionIdFrom(exec);
6417
+ const query = optionalBoundedText(args.query, "query", MAX_QUERY_LENGTH)?.toLowerCase() ?? "";
6418
+ const limit = boundedLimit(args.limit);
6419
+ const snapshot = await controller.handle("design/read", {
6420
+ version: 1,
6421
+ sessionId
6422
+ }, exec.signal);
6423
+ requireReady(snapshot);
6424
+ throwIfAborted(exec.signal);
6425
+ return projectModels(snapshot, query, limit);
6426
+ },
6427
+ presentCall: (args) => ({
6428
+ card: "generic",
6429
+ title: "Browse Modellix media models",
6430
+ kind: "search",
6431
+ ...args.query === void 0 ? {} : { rawInput: args.query }
6432
+ })
6433
+ });
6434
+ }
6435
+ function createSchemaTool(controller) {
6436
+ return defineTool({
6437
+ name: MODELLIX_MEDIA_SCHEMA_TOOL,
6438
+ description: "Get the current Modellix API schema for one exact media model. Use the returned field paths and contract hash before prepare or generate; do not guess model parameters.",
6439
+ parameters: { model: {
6440
+ type: "string",
6441
+ required: true,
6442
+ description: "Exact provider/model slug returned by modellix_media_list."
6443
+ } },
6444
+ output: {
6445
+ schema: {
6446
+ type: "object",
6447
+ additionalProperties: false,
6448
+ properties: {
6449
+ version: {
6450
+ type: "integer",
6451
+ const: 1,
6452
+ required: true
6453
+ },
6454
+ service: {
6455
+ type: "string",
6456
+ const: "media",
6457
+ required: true
6212
6458
  },
6213
- selectedModelId: { type: "string" },
6459
+ operation: {
6460
+ type: "string",
6461
+ const: "schema",
6462
+ required: true
6463
+ },
6464
+ modelId: {
6465
+ type: "string",
6466
+ required: true
6467
+ },
6468
+ available: {
6469
+ type: "boolean",
6470
+ required: true
6471
+ },
6472
+ unavailableReason: { type: "string" },
6214
6473
  schema: {
6215
6474
  type: "object",
6216
6475
  additionalProperties: false,
@@ -6269,54 +6528,148 @@ function createModelsTool(controller) {
6269
6528
  },
6270
6529
  render: (_args, value) => [{
6271
6530
  type: "text",
6272
- text: formatModels(value)
6531
+ text: formatSchema(value)
6273
6532
  }]
6274
6533
  },
6275
6534
  async execute(args, exec) {
6276
- assertOnlyKeys(args, [
6277
- "query",
6278
- "model",
6279
- "limit"
6280
- ]);
6281
- throwIfAborted(exec.signal);
6535
+ assertOnlyKeys$1(args, ["model"]);
6536
+ const modelId = requireModel(args.model);
6282
6537
  const sessionId = sessionIdFrom(exec);
6283
- const query = optionalBoundedText(args.query, "query", MAX_QUERY_LENGTH)?.toLowerCase() ?? "";
6284
- const limit = boundedLimit(args.limit);
6285
- const requestedModel = args.model === void 0 ? void 0 : requireModel(args.model);
6286
- let snapshot = await controller.handle("design/read", {
6538
+ const read = await controller.handle("design/read", {
6287
6539
  version: 1,
6288
6540
  sessionId
6289
6541
  }, exec.signal);
6290
- requireReady(snapshot);
6291
- if (requestedModel !== void 0) {
6292
- requireCatalogModel(snapshot, requestedModel);
6293
- throwIfAborted(exec.signal);
6294
- snapshot = await controller.handle("design/select-model", {
6542
+ requireReady(read);
6543
+ requireCatalogModel(read, modelId);
6544
+ throwIfAborted(exec.signal);
6545
+ try {
6546
+ return projectSchema(requireDraft(await controller.handle("design/select-model", {
6295
6547
  version: 1,
6296
6548
  sessionId,
6297
- modelId: requestedModel
6298
- }, exec.signal);
6549
+ modelId
6550
+ }, exec.signal), modelId));
6551
+ } catch (error) {
6552
+ if (!isUnsupportedSchema(error)) throw error;
6553
+ return projectUnsupportedSchema(modelId);
6299
6554
  }
6300
- throwIfAborted(exec.signal);
6301
- return projectModels(snapshot, query, limit, requestedModel);
6302
6555
  },
6303
6556
  presentCall: (args) => ({
6304
6557
  card: "generic",
6305
- title: args.model === void 0 ? "Browse Modellix Design models" : `Inspect ${args.model}`,
6306
- kind: "search",
6307
- ...args.query === void 0 ? {} : { rawInput: args.query }
6558
+ title: `Inspect ${args.model} schema`,
6559
+ kind: "read"
6560
+ })
6561
+ });
6562
+ }
6563
+ function createUploadFileTool(client, resolveCredential) {
6564
+ return defineTool({
6565
+ name: MODELLIX_MEDIA_UPLOAD_FILE_TOOL,
6566
+ description: "Upload one image from the current conversation or session workspace to Modellix and return a temporary URL for media model inputs. Supply exactly one of attachment_id or path. Reuse returned URLs and never retry an unknown upload outcome automatically.",
6567
+ parameters: {
6568
+ attachment_id: {
6569
+ type: "string",
6570
+ description: "Attachment ID from an image in this conversation."
6571
+ },
6572
+ path: {
6573
+ type: "string",
6574
+ description: "Image path inside the current session workspace."
6575
+ }
6576
+ },
6577
+ output: {
6578
+ schema: {
6579
+ type: "object",
6580
+ additionalProperties: false,
6581
+ properties: {
6582
+ version: {
6583
+ type: "integer",
6584
+ const: 1,
6585
+ required: true
6586
+ },
6587
+ service: {
6588
+ type: "string",
6589
+ const: "media",
6590
+ required: true
6591
+ },
6592
+ operation: {
6593
+ type: "string",
6594
+ const: "upload_file",
6595
+ required: true
6596
+ },
6597
+ fileId: {
6598
+ type: "string",
6599
+ required: true
6600
+ },
6601
+ type: {
6602
+ type: "string",
6603
+ required: true
6604
+ },
6605
+ url: {
6606
+ type: "string",
6607
+ required: true
6608
+ },
6609
+ filename: {
6610
+ type: "string",
6611
+ required: true
6612
+ },
6613
+ size: {
6614
+ type: "integer",
6615
+ required: true
6616
+ },
6617
+ createdAt: { type: "string" },
6618
+ noAutomaticRetry: {
6619
+ type: "boolean",
6620
+ const: true,
6621
+ required: true
6622
+ }
6623
+ }
6624
+ },
6625
+ render: (_args, value) => [{
6626
+ type: "text",
6627
+ text: `Uploaded ${value.filename} for Modellix media input: ${value.url}\nReuse this URL; do not upload the same file again unless the URL is no longer usable.`
6628
+ }]
6629
+ },
6630
+ async execute(args, exec) {
6631
+ assertOnlyKeys$1(args, ["attachment_id", "path"]);
6632
+ const attachmentId = optionalBoundedText(args.attachment_id, "attachment_id", 512);
6633
+ const path = optionalBoundedText(args.path, "path", 32767);
6634
+ if (attachmentId === void 0 === (path === void 0)) throw new DesignError("INVALID_ARGUMENT", "Provide exactly one of attachment_id or path");
6635
+ const prepared = attachmentId === void 0 ? await prepareMediaUploadFromPath(exec.agent?.session.header.cwd, path) : await prepareMediaUploadFromSession(exec.agent, attachmentId, exec.signal);
6636
+ throwIfAborted(exec.signal);
6637
+ const apiKey = await resolveCredential?.();
6638
+ if (apiKey === void 0) throw new DesignError("MISSING_API_KEY", "A Modellix API key is required");
6639
+ const uploaded = await client.upload({
6640
+ ...prepared,
6641
+ apiKey,
6642
+ signal: exec.signal
6643
+ });
6644
+ return {
6645
+ version: 1,
6646
+ service: "media",
6647
+ operation: "upload_file",
6648
+ fileId: uploaded.fileId,
6649
+ type: uploaded.type,
6650
+ url: uploaded.url,
6651
+ filename: uploaded.filename,
6652
+ size: uploaded.size,
6653
+ ...uploaded.createdAt === null ? {} : { createdAt: uploaded.createdAt },
6654
+ noAutomaticRetry: true
6655
+ };
6656
+ },
6657
+ presentCall: () => ({
6658
+ card: "generic",
6659
+ title: "Upload media to Modellix",
6660
+ kind: "execute"
6308
6661
  })
6309
6662
  });
6310
6663
  }
6311
6664
  function createPrepareTool(controller) {
6312
6665
  return defineTool({
6313
- name: MODELLIX_DESIGN_PREPARE_TOOL,
6314
- description: "Use one Modellix LLM request to prepare a schema-constrained Design parameter proposal for one provider/model slug. This may consume balance but never submits a media generation. The returned diff still requires explicit acceptance or a separately approved generate call.",
6666
+ name: MODELLIX_MEDIA_PREPARE_TOOL,
6667
+ description: "Convert a natural-language media request into a schema-constrained parameter proposal for one exact provider/model slug. Use this for Modellix Design parameter editing; it never submits media generation.",
6315
6668
  parameters: {
6316
6669
  model: {
6317
6670
  type: "string",
6318
6671
  required: true,
6319
- description: "Exact provider/model slug from modellix_design_models."
6672
+ description: "Exact provider/model slug from modellix_media_list."
6320
6673
  },
6321
6674
  instruction: {
6322
6675
  type: "string",
@@ -6336,7 +6689,7 @@ function createPrepareTool(controller) {
6336
6689
  },
6337
6690
  service: {
6338
6691
  type: "string",
6339
- const: "design",
6692
+ const: "media",
6340
6693
  required: true
6341
6694
  },
6342
6695
  operation: {
@@ -6402,7 +6755,7 @@ function createPrepareTool(controller) {
6402
6755
  }]
6403
6756
  },
6404
6757
  async execute(args, exec) {
6405
- assertOnlyKeys(args, ["model", "instruction"]);
6758
+ assertOnlyKeys$1(args, ["model", "instruction"]);
6406
6759
  throwIfAborted(exec.signal);
6407
6760
  const modelId = requireModel(args.model);
6408
6761
  const instruction = requiredBoundedText(args.instruction, "instruction", MAX_INSTRUCTION_LENGTH);
@@ -6434,7 +6787,7 @@ function createPrepareTool(controller) {
6434
6787
  if (proposal === null) throw new DesignError("UNEXPECTED_RESPONSE", "Design did not return a parameter proposal");
6435
6788
  return {
6436
6789
  version: 1,
6437
- service: "design",
6790
+ service: "media",
6438
6791
  operation: "prepare",
6439
6792
  modelId,
6440
6793
  irContractHash: draft.irContractHash,
@@ -6460,8 +6813,8 @@ function createPrepareTool(controller) {
6460
6813
  }
6461
6814
  function createGenerateTool(controller) {
6462
6815
  return defineTool({
6463
- name: MODELLIX_DESIGN_GENERATE_TOOL,
6464
- description: "Submit exactly one paid Modellix Design generation after the Harness asks the user to allow it once. Use an exact provider/model slug and schema field names or RFC 6901 paths from modellix_design_models. Never include an API key. Unknown outcomes must not be retried automatically.",
6816
+ name: MODELLIX_MEDIA_GENERATE_TOOL,
6817
+ description: "Generate or edit image, video, or audio with one exact Modellix media model. Choose text-to-media for a new work; reuse the latest relevant media URL or uploaded file for edit/image-to-video/video-to-video requests. Use schema fields from modellix_media_schema. Never include an API key, never retry an unknown submission outcome, and never start a replacement or parallel generation while a matching task is queued or running unless the user explicitly asks for another result.",
6465
6818
  parameters: {
6466
6819
  model: {
6467
6820
  type: "string",
@@ -6490,7 +6843,7 @@ function createGenerateTool(controller) {
6490
6843
  },
6491
6844
  service: {
6492
6845
  type: "string",
6493
- const: "design",
6846
+ const: "media",
6494
6847
  required: true
6495
6848
  },
6496
6849
  operation: {
@@ -6568,10 +6921,11 @@ function createGenerateTool(controller) {
6568
6921
  render: (_args, value) => [{
6569
6922
  type: "text",
6570
6923
  text: formatGeneration(value)
6571
- }]
6924
+ }],
6925
+ presentationMeta: (_args, value) => value
6572
6926
  },
6573
6927
  async execute(args, exec) {
6574
- assertOnlyKeys(args, [
6928
+ assertOnlyKeys$1(args, [
6575
6929
  "model",
6576
6930
  "prompt",
6577
6931
  "input"
@@ -6609,7 +6963,7 @@ function createGenerateTool(controller) {
6609
6963
  if (!(error instanceof DesignError) || error.code !== "SUBMIT_UNKNOWN") throw error;
6610
6964
  return {
6611
6965
  version: 1,
6612
- service: "design",
6966
+ service: "media",
6613
6967
  operation: "generate",
6614
6968
  modelId,
6615
6969
  submitted: true,
@@ -6618,7 +6972,7 @@ function createGenerateTool(controller) {
6618
6972
  resources: [],
6619
6973
  diagnostic: {
6620
6974
  code: "submit-unknown",
6621
- message: "The paid generation outcome is unknown. Do not retry automatically; inspect Design results or the Modellix console."
6975
+ message: "The generation outcome is unknown. Do not retry automatically; inspect Modellix Design results or the Modellix console."
6622
6976
  }
6623
6977
  };
6624
6978
  }
@@ -6630,10 +6984,10 @@ function createGenerateTool(controller) {
6630
6984
  })
6631
6985
  });
6632
6986
  }
6633
- function createTaskTool(controller) {
6987
+ function createGetResultTool(controller) {
6634
6988
  return defineTool({
6635
- name: MODELLIX_DESIGN_TASK_TOOL,
6636
- description: "Refresh and inspect a Design job already present in this plugin's persistent repository. Accepts a remote task ID or local submit-unknown request ID. This read-only tool never submits or retries a generation.",
6989
+ name: MODELLIX_MEDIA_GET_RESULT_TOOL,
6990
+ description: "Refresh and inspect an existing Modellix media generation at most once per Agent turn. For a nonterminal result, the assistant may only confirm submission and direct the user to the live result card or Modellix Design; it must not write queued, running, generating, processing, pending, or equivalent status wording in ordinary assistant text. Accepts a remote task ID or local submit-unknown request ID and never resubmits or replaces generation.",
6637
6991
  parameters: { task_id: {
6638
6992
  type: "string",
6639
6993
  required: true,
@@ -6651,12 +7005,12 @@ function createTaskTool(controller) {
6651
7005
  },
6652
7006
  service: {
6653
7007
  type: "string",
6654
- const: "design",
7008
+ const: "media",
6655
7009
  required: true
6656
7010
  },
6657
7011
  operation: {
6658
7012
  type: "string",
6659
- const: "task",
7013
+ const: "get_result",
6660
7014
  required: true
6661
7015
  },
6662
7016
  found: {
@@ -6740,10 +7094,11 @@ function createTaskTool(controller) {
6740
7094
  render: (_args, value) => [{
6741
7095
  type: "text",
6742
7096
  text: formatTask(value)
6743
- }]
7097
+ }],
7098
+ presentationMeta: (_args, value) => value
6744
7099
  },
6745
7100
  async execute(args, exec) {
6746
- assertOnlyKeys(args, ["task_id"]);
7101
+ assertOnlyKeys$1(args, ["task_id"]);
6747
7102
  throwIfAborted(exec.signal);
6748
7103
  const taskId = requiredBoundedText(args.task_id, "task_id", 256);
6749
7104
  if (!/^[A-Za-z0-9._:-]{1,256}$/u.test(taskId)) throw new DesignError("INVALID_ARGUMENT", "task_id is malformed");
@@ -6756,13 +7111,13 @@ function createTaskTool(controller) {
6756
7111
  const job = snapshot.jobs.find((candidate) => candidate.jobId === taskId);
6757
7112
  return job === void 0 ? {
6758
7113
  version: 1,
6759
- service: "design",
6760
- operation: "task",
7114
+ service: "media",
7115
+ operation: "get_result",
6761
7116
  found: false
6762
7117
  } : {
6763
7118
  version: 1,
6764
- service: "design",
6765
- operation: "task",
7119
+ service: "media",
7120
+ operation: "get_result",
6766
7121
  found: true,
6767
7122
  job: projectJob(job)
6768
7123
  };
@@ -6774,29 +7129,40 @@ function createTaskTool(controller) {
6774
7129
  })
6775
7130
  });
6776
7131
  }
6777
- function projectModels(snapshot, query, limit, requestedModel) {
7132
+ function projectModels(snapshot, query, limit) {
6778
7133
  const matches = snapshot.models.filter((model) => query === "" || [
6779
7134
  model.id,
6780
7135
  model.label,
6781
- model.kind
6782
- ].some((value) => value.toLowerCase().includes(query)));
7136
+ model.kind,
7137
+ model.taskType,
7138
+ model.description
7139
+ ].some((value) => value?.toLowerCase().includes(query) === true));
6783
7140
  const models = matches.slice(0, limit).map((model) => ({
6784
7141
  modelId: model.id,
6785
7142
  label: model.label,
6786
7143
  kind: model.kind,
7144
+ ...model.taskType === void 0 ? {} : { taskType: model.taskType },
7145
+ ...model.description === void 0 ? {} : { description: model.description },
6787
7146
  featured: model.featured,
6788
7147
  available: model.available,
6789
7148
  ...model.unavailableReason === null ? {} : { unavailableReason: modelUnavailableMessage(model.unavailableReason) }
6790
7149
  }));
6791
- const draft = requestedModel === void 0 ? void 0 : snapshot.draft;
6792
7150
  return {
6793
7151
  version: 1,
6794
- service: "design",
6795
- operation: "models",
7152
+ service: "media",
7153
+ operation: "list",
6796
7154
  models,
6797
- truncated: matches.length > models.length,
6798
- ...snapshot.selectedModelId === null ? {} : { selectedModelId: snapshot.selectedModelId },
6799
- ...draft === null || draft === void 0 ? {} : { schema: {
7155
+ truncated: matches.length > models.length
7156
+ };
7157
+ }
7158
+ function projectSchema(draft) {
7159
+ return {
7160
+ version: 1,
7161
+ service: "media",
7162
+ operation: "schema",
7163
+ modelId: draft.modelId,
7164
+ available: true,
7165
+ schema: {
6800
7166
  modelId: draft.modelId,
6801
7167
  irContractHash: draft.irContractHash,
6802
7168
  primaryInputPath: draft.primaryInputPath,
@@ -6809,7 +7175,17 @@ function projectModels(snapshot, query, limit, requestedModel) {
6809
7175
  ...field.description === null ? {} : { description: field.description }
6810
7176
  })),
6811
7177
  truncated: draft.fields.length > MAX_SCHEMA_FIELDS
6812
- } }
7178
+ }
7179
+ };
7180
+ }
7181
+ function projectUnsupportedSchema(modelId) {
7182
+ return {
7183
+ version: 1,
7184
+ service: "media",
7185
+ operation: "schema",
7186
+ modelId,
7187
+ available: false,
7188
+ unavailableReason: "This model's API schema is not compatible with Modellix Design. Choose another model from the live catalog."
6813
7189
  };
6814
7190
  }
6815
7191
  function normalizeGenerationInput(draft, prompt, input) {
@@ -6837,7 +7213,7 @@ function normalizeGenerationInput(draft, prompt, input) {
6837
7213
  function projectGeneration(modelId, job) {
6838
7214
  if (job === void 0) return {
6839
7215
  version: 1,
6840
- service: "design",
7216
+ service: "media",
6841
7217
  operation: "generate",
6842
7218
  modelId,
6843
7219
  submitted: true,
@@ -6846,12 +7222,12 @@ function projectGeneration(modelId, job) {
6846
7222
  resources: [],
6847
7223
  diagnostic: {
6848
7224
  code: "job-record-unavailable",
6849
- message: "The paid request returned without a readable local job record. Do not retry automatically."
7225
+ message: "The generation returned without a readable local job record. Do not retry automatically."
6850
7226
  }
6851
7227
  };
6852
7228
  return {
6853
7229
  version: 1,
6854
- service: "design",
7230
+ service: "media",
6855
7231
  operation: "generate",
6856
7232
  modelId,
6857
7233
  submitted: true,
@@ -6926,7 +7302,7 @@ function requiredBoundedText(value, field, maximum) {
6926
7302
  if (value.trim() === "" || value.length > maximum) throw new DesignError("INVALID_ARGUMENT", `${field} must be non-empty and at most ${String(maximum)} characters`);
6927
7303
  return value;
6928
7304
  }
6929
- function assertOnlyKeys(value, allowed) {
7305
+ function assertOnlyKeys$1(value, allowed) {
6930
7306
  const extra = Object.keys(value).find((key) => !allowed.includes(key));
6931
7307
  if (extra !== void 0) throw new DesignError("INVALID_ARGUMENT", `Unknown tool argument: ${extra}`);
6932
7308
  }
@@ -6945,9 +7321,27 @@ function toToolJson(value) {
6945
7321
  return value;
6946
7322
  }
6947
7323
  function formatModels(value) {
6948
- const lines = value.models.map((model) => `- ${model.modelId} (${model.kind})${model.available ? "" : " — unavailable"}`);
6949
- const schema = value.schema === void 0 ? "" : `\n\nSchema for ${value.schema.modelId}: primary input ${value.schema.primaryInputPath}; fields ${value.schema.fields.map((field) => field.path).join(", ") || "none"}${value.schema.truncated ? " (truncated)" : ""}.`;
6950
- return `${lines.length === 0 ? "No matching Modellix Design models." : lines.join("\n")}${value.truncated ? "\n(Results truncated; refine the query.)" : ""}${schema}`;
7324
+ const lines = value.models.map((model) => `- ${model.modelId} (${model.taskType ?? model.kind})${model.available ? "" : " — unavailable"}${model.description === void 0 ? "" : `: ${model.description}`}`);
7325
+ return `${lines.length === 0 ? "No matching Modellix media models." : lines.join("\n")}${value.truncated ? "\n(Results truncated; refine the query.)" : ""}`;
7326
+ }
7327
+ function formatSchema(value) {
7328
+ if (!value.available || value.schema === void 0) return `Schema for ${value.modelId} is unavailable to Modellix Design. ${value.unavailableReason ?? "Choose another model from the live catalog."}`;
7329
+ const fields = value.schema.fields.map((field) => {
7330
+ const qualifiers = [
7331
+ field.kind,
7332
+ field.required ? "required" : "optional",
7333
+ field.options.length === 0 ? null : `allowed values: ${field.options.map((option) => JSON.stringify(option)).join(", ")}`
7334
+ ].filter((qualifier) => qualifier !== null);
7335
+ return `- ${field.path} (${qualifiers.join("; ")})${field.description === void 0 ? "" : ` — ${field.description}`}`;
7336
+ });
7337
+ return [
7338
+ `Schema for ${value.schema.modelId}. Primary input: ${value.schema.primaryInputPath}. Use the exact field descriptions and allowed values below; do not infer a different format.`,
7339
+ fields.length === 0 ? "No input fields." : fields.join("\n"),
7340
+ value.schema.truncated ? "The field list is truncated." : null
7341
+ ].filter((line) => line !== null).join("\n");
7342
+ }
7343
+ function isUnsupportedSchema(error) {
7344
+ return error instanceof DesignError && (error.code === "SCHEMA_INVALID" || error.code === "ENDPOINT_NOT_ALLOWED");
6951
7345
  }
6952
7346
  function formatPreparation(value) {
6953
7347
  const changes = value.changes.map((change) => `- ${change.path} (${change.label})`).join("\n");
@@ -6975,12 +7369,399 @@ function diagnosticMessage(code) {
6975
7369
  function formatGeneration(value) {
6976
7370
  const resources = value.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
6977
7371
  const diagnostic = value.diagnostic === void 0 ? "" : `\n${value.diagnostic.message}`;
6978
- return `Modellix Design status: ${value.status}${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}${resources === "" ? "" : `\n${resources}`}\nDo not automatically repeat this paid submission.`;
7372
+ if (value.status === "running") return `Modellix media submission accepted${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}\nThe result card and Modellix Design now own the current status. Do not inspect it repeatedly or start a replacement in this turn. In ordinary assistant text, say only that submission was accepted and point to those live surfaces; do not use queued, running, generating, processing, pending, or equivalent current-state wording.`;
7373
+ return `Modellix media status: ${value.status}${value.jobId === void 0 ? "" : ` (${value.jobId})`}.${diagnostic}${resources === "" ? "" : `\n${resources}`}\nDo not automatically repeat this submission.`;
6979
7374
  }
6980
7375
  function formatTask(value) {
6981
- if (!value.found || value.job === void 0) return "No persisted Modellix Design task matched that identifier.";
7376
+ if (!value.found || value.job === void 0) return "No persisted Modellix media task matched that identifier.";
6982
7377
  const resources = value.job.resources.map((resource) => `- ${resource.kind}: ${resource.url}`).join("\n");
6983
- return `Modellix Design task ${value.job.jobId}: ${value.job.status}.${resources === "" ? "" : `\n${resources}`}`;
7378
+ if (value.job.status === "running") return `Modellix media submission ${value.job.jobId} remains assigned to the live result card and Modellix Design. Do not inspect it again or start a replacement in this turn. In ordinary assistant text, say only that submission was accepted and point to those live surfaces; do not use queued, running, generating, processing, pending, or equivalent current-state wording.`;
7379
+ return `Modellix media task ${value.job.jobId}: ${value.job.status}.${resources === "" ? "" : `\n${resources}`}`;
7380
+ }
7381
+ function disposeAll$1(disposers) {
7382
+ for (const dispose of [...disposers].reverse()) try {
7383
+ dispose();
7384
+ } catch {}
7385
+ }
7386
+ //#endregion
7387
+ //#region src/host/web-tool.ts
7388
+ const MODELLIX_WEB_SEARCH_TOOL = "modellix_web_search";
7389
+ const MODELLIX_WEB_FETCH_TOOL = "modellix_web_fetch";
7390
+ const WEB_TOOL_TIMEOUT_MS = 3e4;
7391
+ const MAX_FETCH_OUTPUT_CHARS = 2e5;
7392
+ /** Build the two explicit Modellix Web tools over the concrete Modellix providers. */
7393
+ function createModellixWebToolDefinitions(providers) {
7394
+ return [createSearchTool(providers), createFetchTool(providers)];
7395
+ }
7396
+ /** Register the explicit tools and roll back atomically if either name conflicts. */
7397
+ function registerModellixWebTools(ctx, providers) {
7398
+ const disposers = [];
7399
+ try {
7400
+ for (const definition of createModellixWebToolDefinitions(providers)) disposers.push(ctx.tools.register(definition));
7401
+ } catch (error) {
7402
+ disposeAll(disposers);
7403
+ throw error;
7404
+ }
7405
+ return () => disposeAll(disposers);
7406
+ }
7407
+ function createSearchTool(providers) {
7408
+ return defineTool({
7409
+ name: MODELLIX_WEB_SEARCH_TOOL,
7410
+ description: "Search current public web information through the Modellix Web Search API. Use automatically when current or externally verified information is needed, even when the user did not name a tool. One invocation sends exactly one Modellix search request and never retries it automatically.",
7411
+ parameters: {
7412
+ query: {
7413
+ type: "string",
7414
+ required: true,
7415
+ description: "One non-empty web search query."
7416
+ },
7417
+ max_results: {
7418
+ type: "integer",
7419
+ description: `Maximum sources to return (default ${String(5)}, maximum ${String(20)}).`
7420
+ }
7421
+ },
7422
+ output: {
7423
+ schema: {
7424
+ type: "object",
7425
+ additionalProperties: false,
7426
+ properties: {
7427
+ version: {
7428
+ type: "integer",
7429
+ const: 1,
7430
+ required: true
7431
+ },
7432
+ service: {
7433
+ type: "string",
7434
+ const: "web",
7435
+ required: true
7436
+ },
7437
+ provider: {
7438
+ type: "string",
7439
+ const: "modellix",
7440
+ required: true
7441
+ },
7442
+ operation: {
7443
+ type: "string",
7444
+ const: "search",
7445
+ required: true
7446
+ },
7447
+ endpoint: {
7448
+ type: "string",
7449
+ const: MODELLIX_WEB_SEARCH_ENDPOINT,
7450
+ required: true
7451
+ },
7452
+ query: {
7453
+ type: "string",
7454
+ required: true
7455
+ },
7456
+ content: { type: "string" },
7457
+ sources: {
7458
+ type: "array",
7459
+ required: true,
7460
+ items: {
7461
+ type: "object",
7462
+ additionalProperties: false,
7463
+ properties: {
7464
+ url: {
7465
+ type: "string",
7466
+ required: true
7467
+ },
7468
+ title: { type: "string" },
7469
+ snippet: { type: "string" },
7470
+ publishedAt: { type: "string" }
7471
+ }
7472
+ }
7473
+ },
7474
+ truncated: {
7475
+ type: "boolean",
7476
+ required: true
7477
+ },
7478
+ noAutomaticRetry: {
7479
+ type: "boolean",
7480
+ const: true,
7481
+ required: true
7482
+ }
7483
+ }
7484
+ },
7485
+ render: (_args, value) => [{
7486
+ type: "text",
7487
+ text: formatSearchOutput(value)
7488
+ }],
7489
+ presentationMeta: (_args, value) => searchPresentationMeta(value)
7490
+ },
7491
+ timeoutMs: WEB_TOOL_TIMEOUT_MS,
7492
+ async execute(args, exec) {
7493
+ assertOnlyKeys(args, ["query", "max_results"]);
7494
+ const query = requireNonBlank(args.query, "query");
7495
+ const maxResults = boundedResultCount(args.max_results);
7496
+ return projectSearchResult(query, await providers.search.search({
7497
+ query,
7498
+ maxResults
7499
+ }, exec.signal));
7500
+ },
7501
+ presentCall: (args) => ({
7502
+ card: "generic",
7503
+ title: `Modellix Web Search · ${args.query}`,
7504
+ kind: "search",
7505
+ rawInput: args.query
7506
+ }),
7507
+ presentResult: (args, result) => presentSearchResult(args.query, result)
7508
+ });
7509
+ }
7510
+ function createFetchTool(providers) {
7511
+ return defineTool({
7512
+ name: MODELLIX_WEB_FETCH_TOOL,
7513
+ description: "Fetch one specific public HTTP(S) page through the Modellix Web Fetch API. Use automatically for a user-supplied URL or when a Modellix search source needs full-page reading. One invocation sends exactly one Modellix fetch request and never retries it automatically.",
7514
+ parameters: { url: {
7515
+ type: "string",
7516
+ required: true,
7517
+ description: "The public HTTP(S) URL to retrieve."
7518
+ } },
7519
+ output: {
7520
+ schema: {
7521
+ type: "object",
7522
+ additionalProperties: false,
7523
+ properties: {
7524
+ version: {
7525
+ type: "integer",
7526
+ const: 1,
7527
+ required: true
7528
+ },
7529
+ service: {
7530
+ type: "string",
7531
+ const: "web",
7532
+ required: true
7533
+ },
7534
+ provider: {
7535
+ type: "string",
7536
+ const: "modellix",
7537
+ required: true
7538
+ },
7539
+ operation: {
7540
+ type: "string",
7541
+ const: "fetch",
7542
+ required: true
7543
+ },
7544
+ endpoint: {
7545
+ type: "string",
7546
+ const: MODELLIX_WEB_FETCH_ENDPOINT,
7547
+ required: true
7548
+ },
7549
+ requestedUrl: {
7550
+ type: "string",
7551
+ required: true
7552
+ },
7553
+ url: {
7554
+ type: "string",
7555
+ required: true
7556
+ },
7557
+ statusCode: {
7558
+ type: "integer",
7559
+ required: true
7560
+ },
7561
+ body: {
7562
+ required: true,
7563
+ oneOf: [{
7564
+ type: "object",
7565
+ additionalProperties: false,
7566
+ properties: {
7567
+ kind: {
7568
+ type: "string",
7569
+ const: "html",
7570
+ required: true
7571
+ },
7572
+ content: {
7573
+ type: "string",
7574
+ required: true
7575
+ }
7576
+ }
7577
+ }, {
7578
+ type: "object",
7579
+ additionalProperties: false,
7580
+ properties: {
7581
+ kind: {
7582
+ type: "string",
7583
+ const: "text",
7584
+ required: true
7585
+ },
7586
+ content: {
7587
+ type: "string",
7588
+ required: true
7589
+ }
7590
+ }
7591
+ }]
7592
+ },
7593
+ truncated: {
7594
+ type: "boolean",
7595
+ required: true
7596
+ },
7597
+ noAutomaticRetry: {
7598
+ type: "boolean",
7599
+ const: true,
7600
+ required: true
7601
+ }
7602
+ }
7603
+ },
7604
+ render: (_args, value) => [{
7605
+ type: "text",
7606
+ text: formatFetchOutput(value)
7607
+ }],
7608
+ presentationMeta: (_args, value) => fetchPresentationMeta(value)
7609
+ },
7610
+ timeoutMs: WEB_TOOL_TIMEOUT_MS,
7611
+ async execute(args, exec) {
7612
+ assertOnlyKeys(args, ["url"]);
7613
+ const requestedUrl = requireNonBlank(args.url, "url");
7614
+ return projectFetchResult(requestedUrl, await providers.fetch.fetch({ url: requestedUrl }, exec.signal));
7615
+ },
7616
+ presentCall: (args) => ({
7617
+ card: "generic",
7618
+ title: `Modellix Web Fetch · ${args.url}`,
7619
+ kind: "fetch",
7620
+ rawInput: args.url
7621
+ }),
7622
+ presentResult: (args, result) => presentFetchResult(args.url, result)
7623
+ });
7624
+ }
7625
+ function projectSearchResult(query, result) {
7626
+ return {
7627
+ version: 1,
7628
+ service: "web",
7629
+ provider: "modellix",
7630
+ operation: "search",
7631
+ endpoint: MODELLIX_WEB_SEARCH_ENDPOINT,
7632
+ query,
7633
+ ...result.content === void 0 ? {} : { content: result.content },
7634
+ sources: result.sources.map(projectSource),
7635
+ truncated: result.truncated,
7636
+ noAutomaticRetry: true
7637
+ };
7638
+ }
7639
+ function projectFetchResult(requestedUrl, result) {
7640
+ const content = result.body.content.slice(0, MAX_FETCH_OUTPUT_CHARS);
7641
+ return {
7642
+ version: 1,
7643
+ service: "web",
7644
+ provider: "modellix",
7645
+ operation: "fetch",
7646
+ endpoint: MODELLIX_WEB_FETCH_ENDPOINT,
7647
+ requestedUrl,
7648
+ url: result.url,
7649
+ statusCode: result.statusCode,
7650
+ body: {
7651
+ kind: result.body.kind,
7652
+ content
7653
+ },
7654
+ truncated: result.truncated || content.length !== result.body.content.length,
7655
+ noAutomaticRetry: true
7656
+ };
7657
+ }
7658
+ function projectSource(source) {
7659
+ return {
7660
+ url: source.url,
7661
+ ...source.title === void 0 ? {} : { title: source.title },
7662
+ ...source.snippet === void 0 ? {} : { snippet: source.snippet },
7663
+ ...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
7664
+ };
7665
+ }
7666
+ function formatSearchOutput(value) {
7667
+ const parts = [`Modellix Web Search completed for: ${value.query}`, `Provider endpoint: ${value.endpoint}`];
7668
+ if (value.content !== void 0 && value.content.length > 0) parts.push(value.content);
7669
+ if (value.sources.length === 0) parts.push("No sources found.");
7670
+ else parts.push(`Sources:\n${value.sources.map(formatSource).join("\n")}`);
7671
+ if (value.truncated) parts.push("The source list was truncated; refine the query if needed.");
7672
+ parts.push("Cite the relevant source URLs as markdown links in the answer.");
7673
+ return parts.join("\n\n");
7674
+ }
7675
+ function formatSource(source) {
7676
+ const label = source.title === void 0 || source.title.length === 0 ? source.url : source.title;
7677
+ const details = [source.snippet, source.publishedAt].filter((value) => value !== void 0 && value.length > 0);
7678
+ return `- ${label}\n URL: ${source.url}${details.length === 0 ? "" : `\n ${details.join(" · ")}`}`;
7679
+ }
7680
+ function formatFetchOutput(value) {
7681
+ const header = [`Modellix Web Fetch completed: ${value.url} (HTTP ${String(value.statusCode)})`, `Provider endpoint: ${value.endpoint}`].join("\n");
7682
+ const footer = value.truncated ? "\n\n(Content truncated. Fetch a more specific public URL if more detail is required.)" : "";
7683
+ return `${header}\n\n${value.body.content}${footer}`;
7684
+ }
7685
+ function searchPresentationMeta(value) {
7686
+ return {
7687
+ sources: value.sources.map((source) => ({
7688
+ url: source.url,
7689
+ ...source.title === void 0 ? {} : { title: source.title },
7690
+ ...source.snippet === void 0 ? {} : { snippet: source.snippet },
7691
+ ...source.publishedAt === void 0 ? {} : { publishedAt: source.publishedAt }
7692
+ })),
7693
+ truncated: value.truncated,
7694
+ ...value.content === void 0 ? {} : { answer: value.content }
7695
+ };
7696
+ }
7697
+ function fetchPresentationMeta(value) {
7698
+ return {
7699
+ url: value.url,
7700
+ statusCode: value.statusCode,
7701
+ truncated: value.truncated
7702
+ };
7703
+ }
7704
+ function presentSearchResult(query, result) {
7705
+ if (result.isError) return void 0;
7706
+ const meta = asRecord(result.meta);
7707
+ if (meta === void 0 || !Array.isArray(meta.sources) || typeof meta.truncated !== "boolean") return void 0;
7708
+ const sources = [];
7709
+ for (const candidate of meta.sources) {
7710
+ const source = asRecord(candidate);
7711
+ if (source === void 0 || typeof source.url !== "string" || !optionalStrings(source, [
7712
+ "title",
7713
+ "snippet",
7714
+ "publishedAt"
7715
+ ])) return void 0;
7716
+ sources.push({
7717
+ url: source.url,
7718
+ ...typeof source.title === "string" ? { title: source.title } : {},
7719
+ ...typeof source.snippet === "string" ? { snippet: source.snippet } : {},
7720
+ ...typeof source.publishedAt === "string" ? { publishedAt: source.publishedAt } : {}
7721
+ });
7722
+ }
7723
+ if (meta.answer !== void 0 && typeof meta.answer !== "string") return void 0;
7724
+ return {
7725
+ card: "web",
7726
+ kind: "search",
7727
+ title: `Modellix Web Search · ${query}`,
7728
+ sources,
7729
+ truncated: meta.truncated,
7730
+ ...typeof meta.answer === "string" ? { answer: meta.answer } : {}
7731
+ };
7732
+ }
7733
+ function presentFetchResult(requestedUrl, result) {
7734
+ if (result.isError) return void 0;
7735
+ const meta = asRecord(result.meta);
7736
+ if (meta === void 0 || typeof meta.url !== "string" || typeof meta.statusCode !== "number" || !Number.isInteger(meta.statusCode) || typeof meta.truncated !== "boolean") return void 0;
7737
+ return {
7738
+ card: "web",
7739
+ kind: "fetch",
7740
+ title: `Modellix Web Fetch · ${requestedUrl}`,
7741
+ url: meta.url,
7742
+ statusCode: meta.statusCode,
7743
+ truncated: meta.truncated
7744
+ };
7745
+ }
7746
+ function boundedResultCount(value) {
7747
+ const resolved = value ?? 5;
7748
+ if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > 20) throw new TypeError(`max_results must be an integer from 1 through ${String(20)}`);
7749
+ return resolved;
7750
+ }
7751
+ function requireNonBlank(value, name) {
7752
+ if (value.trim().length === 0) throw new TypeError(`${name} must contain non-whitespace text`);
7753
+ return value;
7754
+ }
7755
+ function assertOnlyKeys(value, allowed) {
7756
+ const accepted = new Set(allowed);
7757
+ const unexpected = Object.keys(value).find((key) => !accepted.has(key));
7758
+ if (unexpected !== void 0) throw new TypeError(`Unexpected tool argument: ${unexpected}`);
7759
+ }
7760
+ function optionalStrings(value, keys) {
7761
+ return keys.every((key) => value[key] === void 0 || typeof value[key] === "string");
7762
+ }
7763
+ function asRecord(value) {
7764
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6984
7765
  }
6985
7766
  function disposeAll(disposers) {
6986
7767
  for (const dispose of [...disposers].reverse()) try {
@@ -7079,7 +7860,7 @@ var PluginSettingsController = class {
7079
7860
  //#endregion
7080
7861
  //#region src/host/runtime.ts
7081
7862
  const RPC_CHANNEL = "/modellix";
7082
- const LLM_SETTINGS_NAMESPACE = settingsNamespace("llm-pi-ai");
7863
+ const LLM_SETTINGS_NAMESPACE = "llm-pi-ai";
7083
7864
  const LLM_OWNERSHIP_ROLLBACK_FAILED = "MODELLIX_LLM_OWNERSHIP_ROLLBACK_FAILED";
7084
7865
  const LLM_MATERIALIZATION_RECOVERED = "MODELLIX_LLM_MATERIALIZATION_RECOVERED";
7085
7866
  const LLM_MATERIALIZATION_RECOVERY_FAILED = "MODELLIX_LLM_MATERIALIZATION_RECOVERY_FAILED";
@@ -7119,6 +7900,7 @@ var ModellixRuntime = class ModellixRuntime {
7119
7900
  #credentialRecoveryRequestId = null;
7120
7901
  #designPollTimer;
7121
7902
  #disposeDesignTools;
7903
+ #disposeWebTools;
7122
7904
  #closing = false;
7123
7905
  #lifecycleAbort = new AbortController();
7124
7906
  static async create(ctx) {
@@ -7197,6 +7979,7 @@ var ModellixRuntime = class ModellixRuntime {
7197
7979
  this.syncDesignTools(next.services.design.enabled);
7198
7980
  if (next.services.design.enabled) this.scheduleDesignPoll(0);
7199
7981
  }
7982
+ if (next.services.web.enabled !== previous.services.web.enabled) this.syncWebTools(next.services.web.enabled);
7200
7983
  if (next.services.llm.enabled !== previous.services.llm.enabled || next.credentialEpoch !== previous.credentialEpoch) return this.enqueueWrite(() => this.reconcileLiveSettings());
7201
7984
  }), "dsh-modellix: live settings snapshot");
7202
7985
  this.#ctx.on("credentials/reference-updated", (updated) => {
@@ -7227,25 +8010,22 @@ var ModellixRuntime = class ModellixRuntime {
7227
8010
  this.scheduleDesignPoll(0);
7228
8011
  });
7229
8012
  });
7230
- this.#ctx.effect(() => registerModellixWebProviders(this.#ctx.web, {
7231
- isEnabled: () => this.#config.services.web.enabled,
7232
- hasCredential: () => this.credentialIsUsable(),
7233
- resolveCredential: async () => {
7234
- const hit = await this.resolveUsableCredential();
7235
- if (hit === void 0) this.requestCredentialRecovery();
7236
- return hit === void 0 ? null : {
7237
- apiKey: hit.value,
7238
- credentialEpoch: hit.credentialEpoch
7239
- };
7240
- },
7241
- getUserId: () => this.#userId,
7242
- isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
7243
- onCredentialRejected: async (epoch) => {
7244
- if (epoch !== this.#config.credentialEpoch) return;
7245
- this.markCredentialRejected(epoch);
7246
- }
7247
- }), "dsh-modellix: native Web providers");
7248
- this.#ctx.effect(() => this.#ctx.connection.rpc.handle(RPC_CHANNEL, (endpoint, payload, signal) => this.handleRpc(endpoint, payload, signal), { authority: "loopback" }), "dsh-modellix: loopback configuration and Design RPC");
8013
+ this.#ctx.effect(() => {
8014
+ this.syncWebTools(this.#config.services.web.enabled);
8015
+ return () => {
8016
+ this.#disposeWebTools?.();
8017
+ this.#disposeWebTools = void 0;
8018
+ };
8019
+ }, "dsh-modellix: explicit Modellix Web tools");
8020
+ this.#ctx.on("agent/session-start", ({ agent }) => {
8021
+ const message = createModellixRoutingMessage({
8022
+ mediaEnabled: this.#config.services.design.enabled && this.credentialIsUsable(),
8023
+ webEnabled: this.#config.services.web.enabled && this.credentialIsUsable()
8024
+ });
8025
+ if (message !== null) agent.inject(message);
8026
+ });
8027
+ const connectionRpc = this.#ctx.connection.rpc;
8028
+ this.#ctx.effect(() => connectionRpc.handle(RPC_CHANNEL, (endpoint, payload, signal) => this.handleRpc(endpoint, payload, signal), { authority: "loopback" }), "dsh-modellix: loopback configuration and Design RPC");
7249
8029
  this.#ctx.effect(() => {
7250
8030
  this.scheduleDesignPoll(2e3);
7251
8031
  return async () => {
@@ -7904,11 +8684,14 @@ var ModellixRuntime = class ModellixRuntime {
7904
8684
  }
7905
8685
  syncDesignTools(enabled) {
7906
8686
  if (enabled && this.#disposeDesignTools === void 0) {
7907
- this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, { handle: (endpoint, payload, signal) => {
7908
- const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
7909
- if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
7910
- return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal));
7911
- } });
8687
+ this.#disposeDesignTools = registerModellixDesignTools(this.#ctx, {
8688
+ resolveCredential: async () => (await this.resolveUsableCredential())?.value,
8689
+ handle: (endpoint, payload, signal) => {
8690
+ const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
8691
+ if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
8692
+ return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal));
8693
+ }
8694
+ });
7912
8695
  return;
7913
8696
  }
7914
8697
  if (!enabled && this.#disposeDesignTools !== void 0) {
@@ -7916,6 +8699,34 @@ var ModellixRuntime = class ModellixRuntime {
7916
8699
  this.#disposeDesignTools = void 0;
7917
8700
  }
7918
8701
  }
8702
+ syncWebTools(enabled) {
8703
+ if (enabled && this.#disposeWebTools === void 0) {
8704
+ const providers = createModellixWebProviders({
8705
+ isEnabled: () => this.#config.services.web.enabled,
8706
+ hasCredential: () => this.credentialIsUsable(),
8707
+ resolveCredential: async () => {
8708
+ const hit = await this.resolveUsableCredential();
8709
+ if (hit === void 0) this.requestCredentialRecovery();
8710
+ return hit === void 0 ? null : {
8711
+ apiKey: hit.value,
8712
+ credentialEpoch: hit.credentialEpoch
8713
+ };
8714
+ },
8715
+ getUserId: () => this.#userId,
8716
+ isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
8717
+ onCredentialRejected: async (epoch) => {
8718
+ if (epoch !== this.#config.credentialEpoch) return;
8719
+ this.markCredentialRejected(epoch);
8720
+ }
8721
+ });
8722
+ this.#disposeWebTools = registerModellixWebTools(this.#ctx, providers);
8723
+ return;
8724
+ }
8725
+ if (!enabled && this.#disposeWebTools !== void 0) {
8726
+ this.#disposeWebTools();
8727
+ this.#disposeWebTools = void 0;
8728
+ }
8729
+ }
7919
8730
  scheduleDesignPoll(delayMs) {
7920
8731
  if (this.#closing || this.#designPollTimer !== void 0) return;
7921
8732
  this.#designPollTimer = setTimeout(() => {
@@ -8075,6 +8886,6 @@ async function apply(ctx) {
8075
8886
  await ModellixRuntime.create(ctx);
8076
8887
  }
8077
8888
  //#endregion
8078
- export { AUTHENTICATED_CATALOG_URL, CURRENT_CONFIG_SCHEMA_VERSION, Config, CredentialBroker, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialValidationError, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignError, DesignHostController, DesignPlannerClient, DesignTaskRepository, EMPTY_LLM_ROUTE_LEDGER, HttpPolicyError, HttpResponseBoundaryError, LlmCatalogCache, LlmCatalogClient, LlmCatalogRequestError, LlmMaterializationConflictError, LlmMaterializationRollbackError, LlmRegistryBackreadError, LlmRouteConflictError, LlmSettingsMaterializer, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF, MODELLIX_DESIGN_GENERATE_TOOL, MODELLIX_DESIGN_MODELS_TOOL, MODELLIX_DESIGN_PREPARE_TOOL, MODELLIX_DESIGN_TASK_TOOL, MODELLIX_LLM_BASE_URL, MODELLIX_LLM_MODELS_URL, MODELLIX_LLM_PROVENANCE_FIELD, MODELLIX_LLM_PROVIDER_ID, MODELLIX_ORIGINS, MODELLIX_SETTINGS_NAMESPACE, MODELLIX_WEB_FETCH_ENDPOINT, MODELLIX_WEB_PROVIDER_ID, MODELLIX_WEB_SEARCH_ENDPOINT, 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, 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, preserveStoredCredentialAfterCandidateFailure, preserveVerificationAfterTransientFailure, readBoundedResponseJson, readBoundedResponseText, reconcileLlmRouteLedgerAfterInterruption, reconcileOnboardingSave, redactForLog, redactHeaders, redactUrl, registerModellixDesignTools, registerModellixWebProviders, replayDesignWal, requestDeadline, retryAfterFromFailure, selectAvailableResults, setServiceToggles, systemClock, systemSleep, toModellixError, validatePublicHttpUrl, validateSubmitEndpoint, verifyLlmRegistryBackread };
8889
+ export { AUTHENTICATED_CATALOG_URL, CURRENT_CONFIG_SCHEMA_VERSION, Config, CredentialBroker, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialValidationError, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignError, DesignHostController, DesignPlannerClient, DesignTaskRepository, EMPTY_LLM_ROUTE_LEDGER, HttpPolicyError, HttpResponseBoundaryError, LlmCatalogCache, LlmCatalogClient, LlmCatalogRequestError, LlmMaterializationConflictError, LlmMaterializationRollbackError, LlmRegistryBackreadError, LlmRouteConflictError, LlmSettingsMaterializer, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF, MODELLIX_LLM_BASE_URL, MODELLIX_LLM_MODELS_URL, MODELLIX_LLM_PROVENANCE_FIELD, MODELLIX_LLM_PROVIDER_ID, MODELLIX_MEDIA_GENERATE_TOOL, MODELLIX_MEDIA_GET_RESULT_TOOL, MODELLIX_MEDIA_LIST_TOOL, MODELLIX_MEDIA_PREPARE_TOOL, MODELLIX_MEDIA_SCHEMA_TOOL, MODELLIX_MEDIA_UPLOAD_FILE_TOOL, MODELLIX_ORIGINS, MODELLIX_SETTINGS_NAMESPACE, MODELLIX_WEB_FETCH_ENDPOINT, MODELLIX_WEB_FETCH_TOOL, MODELLIX_WEB_PROVIDER_ID, MODELLIX_WEB_SEARCH_ENDPOINT, MODELLIX_WEB_SEARCH_TOOL, MediaUploadClient, ModelCatalogClient, ModelSchemaClient, ModellixRuntime, ModellixWebContractError, ModellixWebFetchFailedError, ModellixWebFetchProvider, ModellixWebProviderError, ModellixWebSearchProvider, OnboardingSaveConflictError, PUBLIC_PORTAL_CATALOG_URL, PluginSettingsController, PluginSettingsSchema, PredictionClient, REDACTED, StaleLlmCatalogError, UnsupportedConfigVersionError, abandonLlmMaterialization, advanceCredentialEpoch, apply, applyCredentialDescriptor, applyExactPatch, applyNaturalLanguage, applyRuntimeUnauthorized, applyVerificationResult, approveHttpRequest, approveRedirect, beginLlmMaterialization, beginOnboardingSave, buildFetchRequest, buildInvocationBody, buildSearchRequest, completeLlmMaterialization, completeOnboardingSave, computeRetryDelay, createCredentialState, createDefaultConfig, createModellixDesignToolDefinitions, createModellixWebProviders, createModellixWebToolDefinitions, deferOnboarding, deriveModellixSessionId, deriveModellixUserId, executeWithRetry, extractAllowedSubmitUrl, getServiceToggles, inject, isAllowedModellixOrigin, isCredentialInvalidError, isPublicHostname, isRetryableError, isRetryableReadFailure, isValidModellixIdentity, markOnboardingCredentialSaved, materializeDefaults, migrateConfig, missingCredentialDescriptor, modellixDesignDomainSpec, name, normalizeCredentialDescriptor, openDesignStorage, parseCatalogPage, parseDesignSchema, parseFetchResponse, parsePredictionTask, parseResources, parseRetryAfter, parseSearchResponse, planLlmRouteMaterialization, planLlmRouteRemoval, prepareMediaUploadFromPath, prepareMediaUploadFromSession, preserveStoredCredentialAfterCandidateFailure, preserveVerificationAfterTransientFailure, readBoundedResponseJson, readBoundedResponseText, reconcileLlmRouteLedgerAfterInterruption, reconcileOnboardingSave, redactForLog, redactHeaders, redactUrl, registerModellixDesignTools, registerModellixWebProviderInstances, registerModellixWebProviders, registerModellixWebTools, replayDesignWal, requestDeadline, retryAfterFromFailure, selectAvailableResults, setServiceToggles, systemClock, systemSleep, toModellixError, validatePublicHttpUrl, validateSubmitEndpoint, verifyLlmRegistryBackread };
8079
8890
 
8080
8891
  //# sourceMappingURL=index.js.map