@cueai/omni-reader-mcp 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,6 +36,78 @@ function managerError(code, message, facts = {}) {
36
36
  retryable: facts.retryable ?? false,
37
37
  });
38
38
  }
39
+ function journalIntegrityError(message, record) {
40
+ return managerError("JOURNAL_INTEGRITY_FAILED", message, record === undefined
41
+ ? {}
42
+ : {
43
+ operationCreated: record.operationId !== null,
44
+ fileUploaded: record.fileUploaded,
45
+ parserStarted: record.parserStarted,
46
+ billed: record.billed,
47
+ contentReleased: record.contentReleased,
48
+ });
49
+ }
50
+ function isBillingProfile(profile) {
51
+ return profile === "omni.direct_text_billing.v1"
52
+ || profile === "omni.direct_grounding_billing.v1";
53
+ }
54
+ function toJournalBilling(billing) {
55
+ return {
56
+ creditsCharged: billing.credits_charged,
57
+ creditsRemaining: billing.credits_remaining,
58
+ };
59
+ }
60
+ function toWireBilling(billing) {
61
+ return {
62
+ credits_charged: billing.creditsCharged,
63
+ credits_remaining: billing.creditsRemaining,
64
+ };
65
+ }
66
+ function resultBilling(result) {
67
+ if (result.status === "completed"
68
+ || result.status === "cleanup_pending"
69
+ || result.status === "canceled"
70
+ || result.status === "expired") {
71
+ return result.billing ?? null;
72
+ }
73
+ return null;
74
+ }
75
+ function projectBilling(result, billing) {
76
+ if (billing === null
77
+ || (result.status !== "completed"
78
+ && result.status !== "cleanup_pending"
79
+ && result.status !== "canceled"
80
+ && result.status !== "expired")) {
81
+ return result;
82
+ }
83
+ return { ...result, billing };
84
+ }
85
+ function requireWireBillingForSettledProfile(profile, billed, billing) {
86
+ if (!isBillingProfile(profile))
87
+ return billing ?? undefined;
88
+ if (!billed && billing !== null) {
89
+ throw journalIntegrityError("An unbilled direct terminal carries billing facts.");
90
+ }
91
+ if (billed && billing === null) {
92
+ throw journalIntegrityError("New-profile settlement billing is missing.");
93
+ }
94
+ return billing ?? undefined;
95
+ }
96
+ function assertDirectTerminalSnapshot(profile, snapshot) {
97
+ if (!isBillingProfile(profile))
98
+ return;
99
+ if ((snapshot.directProfile ?? null) !== profile) {
100
+ throw journalIntegrityError("IIIS changed the selected direct profile.");
101
+ }
102
+ if (snapshot.status === "CANCELED"
103
+ || snapshot.status === "FAILED"
104
+ || snapshot.status === "UNSUPPORTED"
105
+ || snapshot.status === "SETTLEMENT_DENIED") {
106
+ if (snapshot.billed || snapshot.contentReleased || snapshot.billing !== null) {
107
+ throw journalIntegrityError("A canceled or failed direct-v5 operation is not strictly uncharged.");
108
+ }
109
+ }
110
+ }
39
111
  function canonicalValue(value) {
40
112
  if (value === null || typeof value === "string" || typeof value === "boolean") {
41
113
  return value;
@@ -162,6 +234,7 @@ function resultFromRecord(record) {
162
234
  status: "expired",
163
235
  operation_id: record.operationId,
164
236
  requires_user_confirmation: true,
237
+ ...(record.billing === null ? {} : { billing: toWireBilling(record.billing) }),
165
238
  };
166
239
  }
167
240
  if (record.state === "CANCELED") {
@@ -170,6 +243,7 @@ function resultFromRecord(record) {
170
243
  status: "canceled",
171
244
  operation_id: record.operationId,
172
245
  ...(cleanupDeadline === null ? {} : { cleanup_deadline: cleanupDeadline }),
246
+ ...(record.billing === null ? {} : { billing: toWireBilling(record.billing) }),
173
247
  data_handling: recordDataHandling(record),
174
248
  };
175
249
  }
@@ -188,6 +262,7 @@ function resultFromRecord(record) {
188
262
  return {
189
263
  status: "cleanup_pending",
190
264
  operation_id: record.operationId,
265
+ ...(record.billing === null ? {} : { billing: toWireBilling(record.billing) }),
191
266
  cleanup_deadline: record.resultExpiresAt ?? record.expiresAt ?? new Date(0).toISOString(),
192
267
  data_handling: recordDataHandling(record),
193
268
  };
@@ -364,6 +439,37 @@ export class OperationManager {
364
439
  throw structured;
365
440
  }
366
441
  }
442
+ async #bindResultBilling(record, result) {
443
+ const billing = resultBilling(result);
444
+ if (billing === null)
445
+ return record;
446
+ const latest = await this.#journal.loadByRequestId(record.clientRequestId) ?? record;
447
+ const directProfile = latest.sourceKind === "url" ? null : latest.directProfile;
448
+ if (latest.sourceKind === "local" && directProfile === null) {
449
+ throw journalIntegrityError("A local terminal carries billing without a saved direct profile.", latest);
450
+ }
451
+ return this.#journal.bindSettlementFacts(latest.clientRequestId, {
452
+ directProfile,
453
+ billing: toJournalBilling(billing),
454
+ });
455
+ }
456
+ async #normalizeResultBilling(record, result) {
457
+ const bound = await this.#bindResultBilling(record, result);
458
+ const billing = resultBilling(result)
459
+ ?? (bound.billing === null ? null : toWireBilling(bound.billing));
460
+ if (isBillingProfile(bound.directProfile)) {
461
+ if (result.status === "failed" || result.status === "canceled") {
462
+ if (bound.billed || bound.billing !== null || bound.contentReleased) {
463
+ throw journalIntegrityError("A failed or canceled direct-v5 terminal is not strictly uncharged.", bound);
464
+ }
465
+ }
466
+ else if (result.status === "completed"
467
+ || (result.status === "cleanup_pending" && result.result !== undefined)) {
468
+ requireWireBillingForSettledProfile(bound.directProfile, bound.billed, billing);
469
+ }
470
+ }
471
+ return projectBilling(result, billing);
472
+ }
367
473
  async #present(record, canonicalResult) {
368
474
  if (canonicalResult.status === "processing")
369
475
  return canonicalResult;
@@ -419,8 +525,9 @@ export class OperationManager {
419
525
  if (TERMINAL_STATES.has(record.state)) {
420
526
  const recovered = await this.#driver.result?.(record, signal);
421
527
  if (recovered !== undefined) {
422
- this.#results.set(operationId, recovered);
423
- return this.#present(record, recovered);
528
+ const normalized = await this.#normalizeResultBilling(record, recovered);
529
+ this.#results.set(operationId, normalized);
530
+ return this.#present(record, normalized);
424
531
  }
425
532
  return this.#present(record, resultFromRecord(record));
426
533
  }
@@ -491,7 +598,7 @@ export class OperationManager {
491
598
  return record;
492
599
  const updated = await this.#applyUpdate(record, update);
493
600
  if (update.result !== undefined && updated.operationId !== null) {
494
- this.#results.set(updated.operationId, update.result);
601
+ this.#results.set(updated.operationId, await this.#normalizeResultBilling(updated, update.result));
495
602
  }
496
603
  return updated;
497
604
  }
@@ -505,7 +612,7 @@ export class OperationManager {
505
612
  if (!isExecution(start)) {
506
613
  const updated = await this.#applyUpdate(record, start);
507
614
  if (start.result !== undefined && updated.operationId !== null) {
508
- this.#results.set(updated.operationId, start.result);
615
+ this.#results.set(updated.operationId, await this.#normalizeResultBilling(updated, start.result));
509
616
  }
510
617
  return updated;
511
618
  }
@@ -517,16 +624,17 @@ export class OperationManager {
517
624
  let managed;
518
625
  const settled = start.completed.then(async (completion) => {
519
626
  const current = await this.#journal.loadByOperationId(operationId);
520
- if (current !== null && !TERMINAL_STATES.has(current.state)) {
521
- await this.#applyUpdate(current, completion.update);
522
- }
523
- if (completion.result.status !== "processing") {
524
- this.#results.set(operationId, completion.result);
627
+ const updated = current !== null && !TERMINAL_STATES.has(current.state)
628
+ ? await this.#applyUpdate(current, completion.update)
629
+ : current ?? initial;
630
+ const normalized = await this.#normalizeResultBilling(updated, completion.result);
631
+ if (normalized.status !== "processing") {
632
+ this.#results.set(operationId, normalized);
525
633
  }
526
634
  else {
527
635
  this.#results.delete(operationId);
528
636
  }
529
- return completion.result;
637
+ return normalized;
530
638
  }).catch(async (error) => {
531
639
  const structured = error instanceof OmniBridgeError
532
640
  ? error
@@ -620,7 +728,9 @@ export class OperationManager {
620
728
  return record;
621
729
  }
622
730
  async #applyUpdate(record, update) {
623
- let current = record;
731
+ let current = update.result === undefined
732
+ ? record
733
+ : await this.#bindResultBilling(record, update.result);
624
734
  for (let attempt = 0; attempt < 4; attempt += 1) {
625
735
  try {
626
736
  return await this.#journal.transition(current.clientRequestId, current.state, update.state, update.patch ?? {});
@@ -967,11 +1077,12 @@ function localResultCache(local) {
967
1077
  expiresAt: local.expiresAt,
968
1078
  });
969
1079
  }
970
- function completedLocalParse(local) {
1080
+ function completedLocalParse(local, billing) {
971
1081
  return {
972
1082
  status: "completed",
973
1083
  operation_id: local.operationId,
974
1084
  result: localResultValue(local),
1085
+ ...(billing === undefined ? {} : { billing }),
975
1086
  data_handling: {
976
1087
  processing_copy: "deleted",
977
1088
  temporary_data: "deleted",
@@ -982,11 +1093,12 @@ function completedLocalParse(local) {
982
1093
  local_result_cache: localResultCache(local),
983
1094
  };
984
1095
  }
985
- function cleanupPendingParse(local, cleanupDeadline) {
1096
+ function cleanupPendingParse(local, cleanupDeadline, billing) {
986
1097
  return {
987
1098
  status: "cleanup_pending",
988
1099
  operation_id: local.operationId,
989
1100
  result: localResultValue(local),
1101
+ ...(billing === undefined ? {} : { billing }),
990
1102
  cleanup_deadline: cleanupDeadline,
991
1103
  data_handling: {
992
1104
  processing_copy: "pending",
@@ -1016,6 +1128,7 @@ function recoveredRemoteParse(record, local) {
1016
1128
  status: "cleanup_pending",
1017
1129
  operation_id: record.operationId,
1018
1130
  result: localResultValue(local),
1131
+ ...(record.billing === null ? {} : { billing: toWireBilling(record.billing) }),
1019
1132
  cleanup_deadline: record.resultExpiresAt ?? record.expiresAt ?? new Date(0).toISOString(),
1020
1133
  data_handling: recordDataHandling(record),
1021
1134
  local_result_cache: localResultCache(local),
@@ -1025,6 +1138,7 @@ function recoveredRemoteParse(record, local) {
1025
1138
  status: "completed",
1026
1139
  operation_id: record.operationId,
1027
1140
  result: localResultValue(local),
1141
+ ...(record.billing === null ? {} : { billing: toWireBilling(record.billing) }),
1028
1142
  data_handling: recordDataHandling(record),
1029
1143
  local_result_cache: localResultCache(local),
1030
1144
  };
@@ -1077,7 +1191,9 @@ function safeOperationStage(status, fallback) {
1077
1191
  return "canceled";
1078
1192
  if (status === "EXPIRED" || status === "DELIVERY_EXPIRED")
1079
1193
  return "expired";
1080
- if (status === "FAILED" || status === "SETTLEMENT_DENIED")
1194
+ if (status === "FAILED"
1195
+ || status === "UNSUPPORTED"
1196
+ || status === "SETTLEMENT_DENIED")
1081
1197
  return "failed";
1082
1198
  return safeJournalStage(fallback);
1083
1199
  }
@@ -1116,6 +1232,23 @@ function requireBundleResult(retention) {
1116
1232
  }
1117
1233
  return retention.bundleResult();
1118
1234
  }
1235
+ async function requireExpectedBundleDetail(local, expectedDetail, retention) {
1236
+ if (local.kind === "bundle" && local.detail === expectedDetail)
1237
+ return local;
1238
+ try {
1239
+ await retention.abort();
1240
+ }
1241
+ catch {
1242
+ // Preserve the exact-detail protocol failure over best-effort local cleanup.
1243
+ }
1244
+ throw managerError("IIIS_INVALID_RESPONSE", "IIIS returned a result for a different representation detail.", {
1245
+ operationCreated: true,
1246
+ fileUploaded: true,
1247
+ parserStarted: true,
1248
+ billed: false,
1249
+ contentReleased: false,
1250
+ });
1251
+ }
1119
1252
  export function createLocalParseOperationManager(options) {
1120
1253
  const now = options.now ?? (() => new Date());
1121
1254
  const openFile = options.openFile ?? openAllowedFile;
@@ -1287,6 +1420,9 @@ export function createLocalParseOperationManager(options) {
1287
1420
  output: "markdown",
1288
1421
  ...(representation.detail === "text" ? {} : { detail: representation.detail }),
1289
1422
  }, input.clientRequestId, input.signal, { journal: false });
1423
+ if (isBillingProfile(granted.directProfile)) {
1424
+ await options.journal.bindDirectProfile(input.clientRequestId, granted.directProfile);
1425
+ }
1290
1426
  }
1291
1427
  catch (error) {
1292
1428
  await opened.close().catch(() => undefined);
@@ -1348,9 +1484,17 @@ export function createLocalParseOperationManager(options) {
1348
1484
  await (context?.progress ?? NOOP_PROGRESS).report(value, total, message, latestProgress ?? undefined);
1349
1485
  },
1350
1486
  };
1487
+ const expectedDetail = granted?.requestedDetail ?? representation.detail;
1488
+ if (expectedDetail !== representation.detail) {
1489
+ await opened?.close().catch(() => undefined);
1490
+ opened = undefined;
1491
+ throw managerError("CUBE_PROTOCOL_ERROR", "Cube returned a grant for a different representation detail.", { operationCreated: true });
1492
+ }
1351
1493
  const operation = {
1352
1494
  operationId,
1353
1495
  operationToken,
1496
+ directProfile: granted?.directProfile ?? recovery?.directProfile ?? null,
1497
+ expectedDetail,
1354
1498
  ...(granted === undefined || opened === undefined
1355
1499
  ? {}
1356
1500
  : {
@@ -1395,18 +1539,19 @@ export function createLocalParseOperationManager(options) {
1395
1539
  recovery?.state === "ACK_PENDING" ||
1396
1540
  recovery?.state === "CLEANUP_PENDING";
1397
1541
  let local;
1542
+ let released;
1398
1543
  if (deliveryRecovery && recovery.resultId !== null) {
1399
1544
  local = await recoverLocalArtifact(recovery);
1400
1545
  }
1401
1546
  else {
1402
1547
  if (deliveryRecovery) {
1403
- await options.iiisClient.downloadResult(operation, progress);
1548
+ released = await options.iiisClient.downloadResult(operation, progress);
1404
1549
  }
1405
1550
  else if (recovery?.state === "UPLOADING" || recovery?.state === "PROCESSING") {
1406
- await options.iiisClient.recoverAndWait(operation);
1551
+ released = await options.iiisClient.recoverAndWait(operation);
1407
1552
  }
1408
1553
  else {
1409
- await options.iiisClient.uploadAndWait(operation);
1554
+ released = await options.iiisClient.uploadAndWait(operation);
1410
1555
  }
1411
1556
  // Non-text results require the durable BundleLocalResult (both named
1412
1557
  // parts verified and fsynced); ACK_PENDING is persisted only after
@@ -1415,6 +1560,27 @@ export function createLocalParseOperationManager(options) {
1415
1560
  ? requireBundleResult(retention)
1416
1561
  : retention.result();
1417
1562
  }
1563
+ if (representation.detail !== "text") {
1564
+ local = await requireExpectedBundleDetail(local, representation.detail, retention);
1565
+ }
1566
+ const directProfile = released?.directProfile
1567
+ ?? recovery?.directProfile
1568
+ ?? operation.directProfile
1569
+ ?? null;
1570
+ if (released?.directProfile !== undefined
1571
+ && released.directProfile !== (operation.directProfile ?? null)) {
1572
+ throw journalIntegrityError("IIIS changed the selected direct profile.", recovery);
1573
+ }
1574
+ const recoveredBilling = recovery?.billing === null || recovery?.billing === undefined
1575
+ ? null
1576
+ : toWireBilling(recovery.billing);
1577
+ const wireBilling = requireWireBillingForSettledProfile(directProfile, true, released?.billing ?? recoveredBilling);
1578
+ if (wireBilling !== undefined) {
1579
+ await options.journal.bindSettlementFacts(input.clientRequestId, {
1580
+ directProfile,
1581
+ billing: toJournalBilling(wireBilling),
1582
+ });
1583
+ }
1418
1584
  const cleanupDeadline = recovery?.resultExpiresAt ?? new Date(now().getTime() + DELIVERY_TTL_SECONDS * 1000).toISOString();
1419
1585
  const resultPatch = {
1420
1586
  fileUploaded: true,
@@ -1465,7 +1631,7 @@ export function createLocalParseOperationManager(options) {
1465
1631
  deliveryResult: "deleted_after_ack",
1466
1632
  },
1467
1633
  },
1468
- result: completedLocalParse(local),
1634
+ result: completedLocalParse(local, wireBilling),
1469
1635
  };
1470
1636
  }
1471
1637
  catch (error) {
@@ -1489,6 +1655,7 @@ export function createLocalParseOperationManager(options) {
1489
1655
  result: processingParse(operationId, stage, latestPercent, confirmed.contentReleased === true),
1490
1656
  };
1491
1657
  }
1658
+ assertDirectTerminalSnapshot(operation.directProfile ?? null, inspected);
1492
1659
  const patch = {
1493
1660
  ...confirmed,
1494
1661
  fileUploaded: confirmed.fileUploaded === true || inspected.fileUploaded,
@@ -1528,7 +1695,9 @@ export function createLocalParseOperationManager(options) {
1528
1695
  },
1529
1696
  };
1530
1697
  }
1531
- if (inspected.status === "FAILED" || inspected.status === "SETTLEMENT_DENIED") {
1698
+ if (inspected.status === "FAILED"
1699
+ || inspected.status === "UNSUPPORTED"
1700
+ || inspected.status === "SETTLEMENT_DENIED") {
1532
1701
  const failed = managerError(inspected.status, `The Omni operation ended with status ${inspected.status}.`, {
1533
1702
  operationCreated: true,
1534
1703
  fileUploaded: patch.fileUploaded,
@@ -1584,12 +1753,14 @@ export function createLocalParseOperationManager(options) {
1584
1753
  state: "FAILED",
1585
1754
  patch: {
1586
1755
  ...failurePatch(stable),
1587
- // The ACK checkpoint may already have recorded a settled charge
1588
- // (snapshot.patch at :1751 carries resultPatch's billed fact).
1589
- // Never regress it assertStableTransition blocks true->false
1590
- // the charge happened; this failure is about the local artifact,
1591
- // not the settlement.
1592
- billed: checkpointBilled || stable.billed,
1756
+ // Recovery starts from persisted monotonic facts. A protocol
1757
+ // rejection cannot make a confirmed upload, parser start,
1758
+ // settlement, or release become false merely because the invalid
1759
+ // response itself was not trusted.
1760
+ fileUploaded: recovery?.fileUploaded === true || stable.fileUploaded,
1761
+ parserStarted: recovery?.parserStarted === true || stable.parserStarted,
1762
+ billed: checkpointBilled || recovery?.billed === true || stable.billed,
1763
+ contentReleased: recovery?.contentReleased === true || stable.contentReleased,
1593
1764
  },
1594
1765
  },
1595
1766
  result: {
@@ -1702,6 +1873,8 @@ export function createLocalParseOperationManager(options) {
1702
1873
  const operation = {
1703
1874
  operationId: record.operationId,
1704
1875
  operationToken: record.operationToken,
1876
+ directProfile: record.directProfile,
1877
+ expectedDetail: record.detail,
1705
1878
  retention: {
1706
1879
  async reset() { },
1707
1880
  async begin() { },
@@ -1714,6 +1887,7 @@ export function createLocalParseOperationManager(options) {
1714
1887
  const inspected = options.iiisClient.cancelOperation === undefined
1715
1888
  ? await options.iiisClient.inspectOperation(operation)
1716
1889
  : await options.iiisClient.cancelOperation(operation);
1890
+ assertDirectTerminalSnapshot(operation.directProfile ?? null, inspected);
1717
1891
  const patch = {
1718
1892
  fileUploaded: inspected.fileUploaded,
1719
1893
  parserStarted: inspected.parserStarted,
@@ -1747,7 +1921,9 @@ export function createLocalParseOperationManager(options) {
1747
1921
  },
1748
1922
  };
1749
1923
  }
1750
- if (inspected.status === "FAILED" || inspected.status === "SETTLEMENT_DENIED") {
1924
+ if (inspected.status === "FAILED"
1925
+ || inspected.status === "UNSUPPORTED"
1926
+ || inspected.status === "SETTLEMENT_DENIED") {
1751
1927
  return {
1752
1928
  state: "FAILED",
1753
1929
  patch: { ...patch, errorCode: inspected.status },
@@ -1773,7 +1949,7 @@ export function createLocalParseOperationManager(options) {
1773
1949
  if (record.operationId === null || record.state !== "COMPLETED")
1774
1950
  return undefined;
1775
1951
  if (record.sourceKind === "local" && record.resultId !== null) {
1776
- return completedLocalParse(await recoverLocalArtifact(record));
1952
+ return completedLocalParse(await recoverLocalArtifact(record), record.billing === null ? undefined : toWireBilling(record.billing));
1777
1953
  }
1778
1954
  if (record.sourceKind === "url" && record.resultId !== null) {
1779
1955
  try {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v4";
3
- export declare const OPERATION_JOURNAL_VERSION = 4;
2
+ export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v5";
3
+ export declare const OPERATION_JOURNAL_VERSION = 5;
4
4
  export declare const BUNDLE_CURSOR_VERSION = 2;
5
5
  export declare const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
6
6
  export declare const GROUNDING_SCHEMA_VERSION = "omni.grounding.v1";
package/dist/protocol.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { RESULT_CHUNK_MAX_BYTES, STATUS_LONG_POLL_MAX_MS } from "./constants.js";
3
- export const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v4";
4
- export const OPERATION_JOURNAL_VERSION = 4;
3
+ export const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v5";
4
+ export const OPERATION_JOURNAL_VERSION = 5;
5
5
  export const BUNDLE_CURSOR_VERSION = 2;
6
6
  // Canonical bundle/grounding literals carried by non-text representations.
7
7
  export const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
@@ -21,6 +21,7 @@ export function normalizeRepresentation(detail) {
21
21
  };
22
22
  }
23
23
  export const MACHINE_INSTRUCTIONS = [
24
+ "Use parse as the only first call for both HTTP(S) URLs and local paths; do not ask the user to choose a local, remote, upload, or URL mode.",
24
25
  "Pass the user-provided source string directly to parse.",
25
26
  "Treat only HTTP(S) as URL; ordinary paths require the local Bridge.",
26
27
  "Never read, attach, base64-encode, or insert local source content before calling Omni.",
@@ -32,6 +33,9 @@ export const MACHINE_INSTRUCTIONS = [
32
33
  "Do not promise background notification when the client lacks task support.",
33
34
  "Do not claim deletion before cleanup is confirmed.",
34
35
  "Report authoritative unit progress when present; never treat partial output as final.",
36
+ "Choose continuation tools from the structured result; do not present the Bridge tool menu to the user.",
37
+ "Text output is Markdown and may retain headings, lists, GFM tables, or raw HTML tables; it lacks grounding/layout sidecars, not all structure.",
38
+ "An empty outline means no recognized headings, not that the text has no structure.",
35
39
  "Answer directly → use inline text when present; otherwise read_result",
36
40
  "Find one section → read_outline, then pass its cursor to read_result",
37
41
  "Read all content → read_result until next_cursor is absent",
@@ -2,7 +2,7 @@ import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { parseReaderCapabilities, selectUrlProfile, } from "./capabilities.js";
3
3
  import { BRIDGE_RELEASE_VERSION, REMOTE_CAPABILITIES_CUSTOM_FIELD, REMOTE_OMNI_MCP_URL, } from "./constants.js";
4
4
  import { API_KEY_URL } from "./onboarding-policy.js";
5
- import { OmniBridgeError } from "./errors.js";
5
+ import { OmniBridgeError, unsupportedDetailError } from "./errors.js";
6
6
  import { parseResultSchema } from "./result-contract.js";
7
7
  import { classifySource } from "./source.js";
8
8
  const INITIALIZE_REQUEST_ID = "initialize";
@@ -108,16 +108,6 @@ function protocolError() {
108
108
  retryable: false,
109
109
  });
110
110
  }
111
- function unsupportedDetail() {
112
- return remoteError({
113
- code: "UNSUPPORTED_DETAIL",
114
- message: "This remote Omni service does not support the requested output detail.",
115
- failureScope: "service",
116
- userAction: "Use plain Markdown output for this source.",
117
- operationCreated: false,
118
- retryable: false,
119
- });
120
- }
121
111
  // Extracts exactly `result.capabilities.experimental["cue.omni-reader"]` from
122
112
  // an MCP initialize envelope; returns undefined when the exact custom field is
123
113
  // absent (server did not declare the capability), throws on contract
@@ -351,7 +341,7 @@ export class HttpRemoteOmniClient {
351
341
  }
352
342
  const custom = initializeCustomCapability(envelope, INITIALIZE_REQUEST_ID);
353
343
  if (custom === undefined) {
354
- throw unsupportedDetail();
344
+ throw unsupportedDetailError("url");
355
345
  }
356
346
  let capabilities;
357
347
  try {
@@ -388,7 +378,7 @@ export class HttpRemoteOmniClient {
388
378
  // non-text v3 call; a missing/mismatched profile fails closed with
389
379
  // UNSUPPORTED_DETAIL and zero tools/call requests.
390
380
  const capabilities = await this.initializeCapabilities(signal);
391
- selectUrlProfile(capabilities, detail);
381
+ selectUrlProfile(capabilities, detail, "url");
392
382
  args = { source: classified.source, detail, wait: false };
393
383
  }
394
384
  return this.#call("parse", args, clientRequestId, signal);