dsh-modellix 0.1.0 → 0.2.0

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