@cueai/omni-reader-mcp 1.1.2 → 1.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.
@@ -1,8 +1,9 @@
1
- import type { ArtifactReadResult, LocalResult } from "./artifact-store.js";
1
+ import type { ArtifactReadResult, BundleLocalResult, LocalResult } from "./artifact-store.js";
2
2
  import type { CubeGrantClient } from "./cube-client.js";
3
3
  import type { IiisClient, ResultRetentionSink } from "./iiis-client.js";
4
4
  import type { JournalPatch, JournalRecord, JournalState, OperationJournal } from "./operation-journal.js";
5
5
  import { type OpenAllowedFileOptions, type OpenedAllowedFile } from "./path-security.js";
6
+ import { type RepresentationIntent } from "./protocol.js";
6
7
  import type { RemoteOmniClient } from "./remote-client.js";
7
8
  import type { ParseResult } from "./result-contract.js";
8
9
  export interface SubmitOperationInput {
@@ -11,6 +12,7 @@ export interface SubmitOperationInput {
11
12
  readonly clientRequestId: string;
12
13
  readonly signal?: AbortSignal;
13
14
  readonly context?: unknown;
15
+ readonly representation?: RepresentationIntent;
14
16
  }
15
17
  export interface OperationDriverUpdate {
16
18
  readonly state: JournalState;
@@ -36,11 +38,12 @@ export interface OperationManagerDriver {
36
38
  result?(record: JournalRecord, signal: AbortSignal): Promise<ParseResult | void>;
37
39
  }
38
40
  export interface OperationManagerOptions {
39
- readonly journal: Pick<OperationJournal, "beginIntent" | "transition" | "loadByRequestId" | "loadByOperationId" | "loadLatestByRequestHash" | "loadLatestBySourceLocatorHash" | "listRecoverable">;
41
+ readonly journal: Pick<OperationJournal, "beginIntent" | "migrateLegacyRecord" | "requestIdentityHmac" | "sourceLocatorHmac" | "transition" | "loadByRequestId" | "loadByOperationId" | "loadLatestByRequestIdentityHmac" | "loadLatestBySourceLocatorHmac" | "listRecoverable">;
40
42
  readonly driver: OperationManagerDriver;
41
43
  readonly now?: () => number;
42
44
  readonly sleep?: (milliseconds: number) => Promise<void>;
43
45
  }
46
+ export declare function canonicalRequestIdentity(input: Pick<SubmitOperationInput, "sourceKind" | "sourceFacts">): string;
44
47
  export declare function operationRequestHash(input: Pick<SubmitOperationInput, "sourceKind" | "sourceFacts">): string;
45
48
  export declare class OperationManager {
46
49
  #private;
@@ -54,10 +57,12 @@ export declare class OperationManager {
54
57
  }
55
58
  interface LocalRetention extends ResultRetentionSink {
56
59
  result(): LocalResult;
60
+ bundleResult?(): BundleLocalResult;
57
61
  }
58
62
  interface LocalArtifactStore {
59
63
  createRetention(): LocalRetention;
60
64
  read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
65
+ readBundleDescriptor?(resultId: string): Promise<BundleLocalResult | null>;
61
66
  }
62
67
  export interface LocalParseOperationManagerOptions {
63
68
  readonly journal: OperationJournal;
@@ -1,8 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { DELIVERY_TTL_SECONDS, FOREGROUND_BUDGET_MS, INLINE_RESULT_MAX_BYTES, STATUS_LONG_POLL_MAX_MS, STATUS_POLL_AFTER_SECONDS, } from "./constants.js";
3
3
  import { OmniBridgeError } from "./errors.js";
4
+ import { LEGACY_RECOVERY_FAILURE_CODE } from "./operation-journal.js";
4
5
  import { openAllowedFile, } from "./path-security.js";
5
6
  import { NOOP_PROGRESS } from "./progress.js";
7
+ import { normalizeRepresentation } from "./protocol.js";
6
8
  const TERMINAL_STATES = new Set([
7
9
  "COMPLETED",
8
10
  "FAILED",
@@ -51,12 +53,54 @@ function canonicalValue(value) {
51
53
  }
52
54
  throw managerError("INVALID_SOURCE_FACTS", "The operation source facts contain an unsupported value.");
53
55
  }
54
- export function operationRequestHash(input) {
55
- const serialized = JSON.stringify({
56
+ // Exact canonical serialization of the request identity. The normalized
57
+ // representation tuple lives inside sourceFacts, so omitted/text and each
58
+ // non-text detail serialize to distinct canonical strings; the journal derives
59
+ // its keyed request identity HMAC from exactly this string.
60
+ export function canonicalRequestIdentity(input) {
61
+ return JSON.stringify({
56
62
  source_kind: input.sourceKind,
57
63
  source_facts: canonicalValue(input.sourceFacts),
58
64
  });
59
- return `sha256:${createHash("sha256").update(serialized, "utf8").digest("hex")}`;
65
+ }
66
+ export function operationRequestHash(input) {
67
+ return `sha256:${createHash("sha256").update(canonicalRequestIdentity(input), "utf8").digest("hex")}`;
68
+ }
69
+ function representationFromSourceFacts(sourceFacts) {
70
+ const detail = sourceFacts.detail;
71
+ if (detail !== undefined &&
72
+ detail !== "text" &&
73
+ detail !== "grounded" &&
74
+ detail !== "layout") {
75
+ throw managerError("INVALID_REPRESENTATION", "The operation request representation is invalid.");
76
+ }
77
+ const normalized = normalizeRepresentation(detail === undefined ? undefined : detail);
78
+ if (sourceFacts.groundingSchemaVersion !== undefined &&
79
+ sourceFacts.groundingSchemaVersion !== normalized.groundingSchemaVersion) {
80
+ throw managerError("INVALID_REPRESENTATION", "The operation request representation is invalid.");
81
+ }
82
+ if (sourceFacts.bundleProtocolVersion !== undefined &&
83
+ sourceFacts.bundleProtocolVersion !== normalized.bundleProtocolVersion) {
84
+ throw managerError("INVALID_REPRESENTATION", "The operation request representation is invalid.");
85
+ }
86
+ return normalized;
87
+ }
88
+ function recordRepresentation(record) {
89
+ return {
90
+ detail: record.detail,
91
+ groundingSchemaVersion: record.groundingSchemaVersion,
92
+ bundleProtocolVersion: record.bundleProtocolVersion,
93
+ };
94
+ }
95
+ function sameRepresentationTuple(left, right) {
96
+ return (left.detail === right.detail &&
97
+ left.groundingSchemaVersion === right.groundingSchemaVersion &&
98
+ left.bundleProtocolVersion === right.bundleProtocolVersion);
99
+ }
100
+ function assertSameRepresentation(input, record) {
101
+ if (!sameRepresentationTuple(representationFromSourceFacts(input.sourceFacts), recordRepresentation(record))) {
102
+ throw managerError("REPRESENTATION_MISMATCH", "The operation request representation differs from the stored record.", { operationCreated: true });
103
+ }
60
104
  }
61
105
  function defaultSleep(milliseconds) {
62
106
  return new Promise((resolve) => {
@@ -188,7 +232,7 @@ export class OperationManager {
188
232
  const active = this.#submissions.get(requestHash);
189
233
  if (active !== undefined)
190
234
  return active;
191
- const pending = this.#submit(input, requestHash);
235
+ const pending = this.#submit(input);
192
236
  this.#submissions.set(requestHash, pending);
193
237
  try {
194
238
  return await pending;
@@ -199,30 +243,60 @@ export class OperationManager {
199
243
  }
200
244
  }
201
245
  }
202
- async #submit(input, requestHash) {
246
+ async #submit(input) {
203
247
  let effectiveInput = input;
204
- const sourceLocatorHash = typeof input.sourceFacts.sourceHash === "string"
248
+ // The exact canonical identity (which includes the normalized
249
+ // representation tuple) drives the keyed journal identity and every
250
+ // recovery selection; a different representation is a different identity.
251
+ const identityJson = canonicalRequestIdentity(input);
252
+ const representation = representationFromSourceFacts(input.sourceFacts);
253
+ if (input.representation !== undefined &&
254
+ !sameRepresentationTuple(input.representation, representation)) {
255
+ throw managerError("REPRESENTATION_MISMATCH", "The operation request representation is inconsistent.");
256
+ }
257
+ const requestIdentityHmac = await this.#journal.requestIdentityHmac(identityJson);
258
+ const sourceLocator = typeof input.sourceFacts.sourceHash === "string"
205
259
  ? input.sourceFacts.sourceHash
206
260
  : null;
261
+ const sourceLocatorHmac = sourceLocator === null
262
+ ? null
263
+ : await this.#journal.sourceLocatorHmac(sourceLocator);
207
264
  let record = await this.#journal.loadByRequestId(input.clientRequestId);
208
265
  if (record === null) {
209
- let matching = await this.#journal.loadLatestByRequestHash(requestHash);
266
+ let matching = await this.#journal.loadLatestByRequestIdentityHmac(requestIdentityHmac);
210
267
  if (matching === null &&
211
268
  input.sourceKind === "local" &&
212
269
  input.sourceFacts.sourceFingerprint === undefined &&
213
- sourceLocatorHash !== null) {
214
- matching = await this.#journal.loadLatestBySourceLocatorHash(sourceLocatorHash);
270
+ sourceLocatorHmac !== null) {
271
+ matching = await this.#journal.loadLatestBySourceLocatorHmac(sourceLocatorHmac);
272
+ // The locator does not bind the representation, so an exact match is
273
+ // required before a record may be reused across submissions.
274
+ if (matching !== null &&
275
+ !sameRepresentationTuple(recordRepresentation(matching), representation)) {
276
+ matching = null;
277
+ }
215
278
  }
216
279
  if (matching !== null && this.#shouldReuseByRequestHash(matching)) {
217
280
  record = matching;
218
281
  effectiveInput = { ...input, clientRequestId: matching.clientRequestId };
219
282
  }
220
283
  else {
221
- record = await this.#journal.beginIntent(input.clientRequestId, requestHash, input.sourceKind, sourceLocatorHash);
284
+ record = await this.#journal.beginIntent(input.clientRequestId, identityJson, input.sourceKind, sourceLocator, representation);
222
285
  }
223
286
  }
287
+ else if (record.requestIdentityHmac === null) {
288
+ // Legacy v1/v2 record: reconstruct the keyed identity from the safe
289
+ // canonical source facts, or fail safe as a terminal source-free
290
+ // recovery failure. It can never be resumed as grounded/layout.
291
+ const migrated = await this.#journal.migrateLegacyRecord(input.clientRequestId, identityJson, sourceLocator);
292
+ record = migrated === null
293
+ ? await this.#journal.beginIntent(input.clientRequestId, identityJson, input.sourceKind, sourceLocator, representation)
294
+ : migrated;
295
+ if (TERMINAL_STATES.has(record.state))
296
+ return record;
297
+ }
224
298
  else {
225
- record = await this.#journal.beginIntent(input.clientRequestId, requestHash, input.sourceKind, sourceLocatorHash);
299
+ record = await this.#journal.beginIntent(input.clientRequestId, identityJson, input.sourceKind, sourceLocator, representation);
226
300
  }
227
301
  const signal = effectiveInput.signal ?? new AbortController().signal;
228
302
  if (TERMINAL_STATES.has(record.state))
@@ -248,7 +322,7 @@ export class OperationManager {
248
322
  : this.#resume(raced, effectiveInput, signal);
249
323
  }
250
324
  try {
251
- const start = await this.#driver.create(effectiveInput, requestHash, signal);
325
+ const start = await this.#driver.create(effectiveInput, requestIdentityHmac, signal);
252
326
  return this.#acceptStart(record, start);
253
327
  }
254
328
  catch (error) {
@@ -489,6 +563,24 @@ export class OperationManager {
489
563
  if (record === null) {
490
564
  throw managerError("OPERATION_NOT_FOUND", "The requested parse operation is not available.");
491
565
  }
566
+ // A legacy v1/v2 record without a reconstructed keyed identity cannot be
567
+ // recovered safely (no proof of the request identity). It becomes a
568
+ // terminal source-free recovery failure instead of resuming, reparsing,
569
+ // or rebilling anything.
570
+ if (record.requestIdentityHmac === null) {
571
+ try {
572
+ return await this.#journal.transition(record.clientRequestId, record.state, "FAILED", { errorCode: LEGACY_RECOVERY_FAILURE_CODE, stage: "failed" });
573
+ }
574
+ catch (error) {
575
+ if (!(error instanceof OmniBridgeError) || error.code !== "JOURNAL_STATE_CONFLICT") {
576
+ throw error;
577
+ }
578
+ const latest = await this.#journal.loadByOperationId(operationId);
579
+ if (latest === null)
580
+ throw error;
581
+ return latest;
582
+ }
583
+ }
492
584
  return record;
493
585
  }
494
586
  async #applyUpdate(record, update) {
@@ -684,9 +776,49 @@ function remoteResultUpdate(result, expectedOperationId) {
684
776
  result,
685
777
  };
686
778
  }
779
+ // The v3 bundle descriptor carries the normalized representation tuple
780
+ // (detail/grounding schema/bundle protocol) plus the exact per-part metadata.
781
+ // The schema-level ParseResult union accepts the descriptor; the type-level
782
+ // union is narrower, so the boundary needs a cast (the tools layer validates
783
+ // through parseResultSchema).
784
+ function bundleDescriptorValue(bundle) {
785
+ const contentStorage = bundle.parts.content.storage.kind === "inline"
786
+ ? { kind: "inline", text: bundle.parts.content.storage.text }
787
+ : { kind: "artifact", next_cursor: bundle.parts.content.storage.nextCursor };
788
+ const groundingStorage = bundle.parts.grounding.storage.kind === "inline"
789
+ ? { kind: "inline", value: bundle.parts.grounding.storage.value }
790
+ : { kind: "artifact", next_cursor: bundle.parts.grounding.storage.nextCursor };
791
+ return {
792
+ kind: "bundle",
793
+ result_id: bundle.resultId,
794
+ detail: bundle.detail,
795
+ bundle_protocol_version: bundle.bundleProtocolVersion,
796
+ bundle_digest: bundle.bundleDigest,
797
+ bundle_bytes: bundle.bundleBytes,
798
+ expires_at: bundle.expiresAt,
799
+ parts: {
800
+ content: {
801
+ part: "content",
802
+ media_type: bundle.parts.content.mediaType,
803
+ result_bytes: bundle.parts.content.resultBytes,
804
+ digest: bundle.parts.content.digest,
805
+ storage: contentStorage,
806
+ },
807
+ grounding: {
808
+ part: "grounding",
809
+ media_type: bundle.parts.grounding.mediaType,
810
+ result_bytes: bundle.parts.grounding.resultBytes,
811
+ digest: bundle.parts.grounding.digest,
812
+ storage: groundingStorage,
813
+ },
814
+ },
815
+ };
816
+ }
687
817
  function localResultValue(local) {
688
818
  if (local.kind === "inline")
689
819
  return { kind: "inline", text: local.text };
820
+ if (local.kind === "bundle")
821
+ return bundleDescriptorValue(local);
690
822
  if (local.nextCursor === undefined) {
691
823
  throw managerError("LOCAL_RESULT_INTEGRITY_FAILED", "The local result artifact cursor is missing.", {
692
824
  operationCreated: true,
@@ -717,7 +849,7 @@ function completedLocalParse(local) {
717
849
  original_source: "unchanged",
718
850
  remote_content_retained: false,
719
851
  },
720
- ...(local.kind === "artifact" ? {
852
+ ...(local.kind === "artifact" || local.kind === "bundle" ? {
721
853
  local_result_cache: {
722
854
  expires_at: local.expiresAt,
723
855
  discard_action: "discard_result",
@@ -813,6 +945,18 @@ function localFailure(error) {
813
945
  ? error
814
946
  : managerError("BRIDGE_INTERNAL_ERROR", "The local Omni operation could not complete.", { operationCreated: true });
815
947
  }
948
+ function requireBundleResult(retention) {
949
+ if (retention.bundleResult === undefined) {
950
+ throw managerError("LOCAL_RESULT_INTEGRITY_FAILED", "The retained local result bundle is incomplete.", {
951
+ operationCreated: true,
952
+ fileUploaded: true,
953
+ parserStarted: true,
954
+ billed: true,
955
+ contentReleased: false,
956
+ });
957
+ }
958
+ return retention.bundleResult();
959
+ }
816
960
  export function createLocalParseOperationManager(options) {
817
961
  const now = options.now ?? (() => new Date());
818
962
  const openFile = options.openFile ?? openAllowedFile;
@@ -835,6 +979,9 @@ export function createLocalParseOperationManager(options) {
835
979
  }
836
980
  throw managerError("JOURNAL_STATE_CONFLICT", "The operation changed repeatedly while saving a delivery checkpoint.", { operationCreated: true, retryable: true });
837
981
  }
982
+ // A retained logical bundle is reconstructed through its closed descriptor
983
+ // and keeps the stored representation tuple; without the descriptor the
984
+ // recovery fails safe instead of degrading the bundle to text.
838
985
  async function recoverLocalArtifact(record) {
839
986
  if (record.operationId === null || record.resultId === null) {
840
987
  throw managerError("RESULT_NOT_AVAILABLE", "The retained local result artifact is unavailable.", {
@@ -845,6 +992,28 @@ export function createLocalParseOperationManager(options) {
845
992
  contentReleased: record.contentReleased,
846
993
  });
847
994
  }
995
+ if (record.detail !== "text") {
996
+ if (options.artifactStore.readBundleDescriptor === undefined) {
997
+ throw managerError("RESULT_NOT_AVAILABLE", "The retained local result bundle is unavailable.", {
998
+ operationCreated: true,
999
+ fileUploaded: record.fileUploaded,
1000
+ parserStarted: record.parserStarted,
1001
+ billed: record.billed,
1002
+ contentReleased: record.contentReleased,
1003
+ });
1004
+ }
1005
+ const descriptor = await options.artifactStore.readBundleDescriptor(record.resultId);
1006
+ if (descriptor === null || descriptor.kind !== "bundle") {
1007
+ throw managerError("RESULT_NOT_AVAILABLE", "The retained local result bundle is unavailable.", {
1008
+ operationCreated: true,
1009
+ fileUploaded: record.fileUploaded,
1010
+ parserStarted: record.parserStarted,
1011
+ billed: record.billed,
1012
+ contentReleased: record.contentReleased,
1013
+ });
1014
+ }
1015
+ return descriptor;
1016
+ }
848
1017
  const preview = await options.artifactStore.read(record.resultId, undefined, 2_048);
849
1018
  if (preview.resultBytes <= INLINE_RESULT_MAX_BYTES) {
850
1019
  const recovered = preview.nextCursor === undefined
@@ -879,6 +1048,16 @@ export function createLocalParseOperationManager(options) {
879
1048
  };
880
1049
  }
881
1050
  async function startExecution(input, recovery) {
1051
+ // Recovery keeps the stored representation tuple exactly: status, download
1052
+ // (recoverLocalArtifact), and ACK all operate on the representation the
1053
+ // record was created with, never on the caller's (possibly absent) one.
1054
+ if (recovery !== undefined) {
1055
+ assertSameRepresentation(input, recovery);
1056
+ }
1057
+ const representation = recovery === undefined
1058
+ ? representationFromSourceFacts(input.sourceFacts)
1059
+ : recordRepresentation(recovery);
1060
+ const expectedBundle = representation.detail !== "text";
882
1061
  const recoveryOrder = recovery === undefined ? undefined : STATE_ORDER.get(recovery.state);
883
1062
  const needsUploadContext = recovery === undefined ||
884
1063
  recoveryOrder === undefined ||
@@ -903,12 +1082,16 @@ export function createLocalParseOperationManager(options) {
903
1082
  throw managerError("SOURCE_CHANGED_DURING_SUBMISSION", "The local source changed before the upload operation was created.", { retryable: true });
904
1083
  }
905
1084
  try {
1085
+ // D2-D Task 14: the normalized detail reaches the grant boundary so
1086
+ // CubeGrantClient selects the exact direct profile BEFORE constructing
1087
+ // the v3 request and requires the v3 response tuple before any upload.
906
1088
  granted = await options.cubeClient.createGrant({
907
1089
  contentLength: opened.size,
908
1090
  contentType: opened.contentType,
909
1091
  fileExtension: opened.safeExtension,
910
1092
  noStore: true,
911
1093
  output: "markdown",
1094
+ ...(representation.detail === "text" ? {} : { detail: representation.detail }),
912
1095
  }, input.clientRequestId, input.signal, { journal: false });
913
1096
  }
914
1097
  catch (error) {
@@ -1031,7 +1214,12 @@ export function createLocalParseOperationManager(options) {
1031
1214
  else {
1032
1215
  await options.iiisClient.uploadAndWait(operation);
1033
1216
  }
1034
- local = retention.result();
1217
+ // Non-text results require the durable BundleLocalResult (both named
1218
+ // parts verified and fsynced); ACK_PENDING is persisted only after
1219
+ // it exists, so a partial bundle can never be acknowledged.
1220
+ local = expectedBundle
1221
+ ? requireBundleResult(retention)
1222
+ : retention.result();
1035
1223
  }
1036
1224
  const cleanupDeadline = recovery?.resultExpiresAt ?? new Date(now().getTime() + DELIVERY_TTL_SECONDS * 1000).toISOString();
1037
1225
  const resultPatch = {
@@ -1220,8 +1408,17 @@ export function createLocalParseOperationManager(options) {
1220
1408
  if (options.remoteClient === undefined) {
1221
1409
  throw managerError("REMOTE_PARSE_UNAVAILABLE", "Remote URL parsing is not available in this Bridge build.", { retryable: true });
1222
1410
  }
1411
+ // Remote recovery must also equal the stored representation tuple; the
1412
+ // exact tuple is passed to the remote boundary so the URL profile is
1413
+ // selected before any non-text tools/call is sent.
1414
+ const representation = recovery === undefined
1415
+ ? representationFromSourceFacts(input.sourceFacts)
1416
+ : recordRepresentation(recovery);
1417
+ if (recovery !== undefined) {
1418
+ assertSameRepresentation(input, recovery);
1419
+ }
1223
1420
  const context = remoteContext(input.context);
1224
- const result = await options.remoteClient.parse(context.source, input.clientRequestId, input.signal ?? new AbortController().signal);
1421
+ const result = await options.remoteClient.parse(context.source, input.clientRequestId, input.signal ?? new AbortController().signal, representation.detail);
1225
1422
  return remoteResultUpdate(result, recovery?.operationId ?? undefined);
1226
1423
  }
1227
1424
  const driver = {
@@ -1242,9 +1439,11 @@ export function createLocalParseOperationManager(options) {
1242
1439
  const order = STATE_ORDER.get(record.state);
1243
1440
  if (order === undefined || order < STATE_ORDER.get("UPLOADING"))
1244
1441
  return undefined;
1442
+ // The recovery input carries the record's own stored representation
1443
+ // tuple so status always recovers exactly what was requested.
1245
1444
  return startExecution({
1246
1445
  sourceKind: "local",
1247
- sourceFacts: {},
1446
+ sourceFacts: { ...recordRepresentation(record) },
1248
1447
  clientRequestId: record.clientRequestId,
1249
1448
  signal,
1250
1449
  }, record);
@@ -1,12 +1,25 @@
1
1
  import { z } from "zod";
2
- export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v2";
2
+ export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
3
+ export declare const OPERATION_JOURNAL_VERSION = 3;
4
+ export declare const BUNDLE_CURSOR_VERSION = 2;
5
+ export declare const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
6
+ export declare const GROUNDING_SCHEMA_VERSION = "omni.grounding.v1";
7
+ export interface RepresentationIntent {
8
+ readonly detail: "text" | "grounded" | "layout";
9
+ readonly groundingSchemaVersion: "none" | "omni.grounding.v1";
10
+ readonly bundleProtocolVersion: "none" | "omni.result_bundle.v1";
11
+ }
12
+ export declare function normalizeRepresentation(detail?: "text" | "grounded" | "layout"): RepresentationIntent;
3
13
  export declare const MACHINE_INSTRUCTIONS: string;
4
14
  export declare const parseSchema: z.ZodObject<{
5
15
  source: z.ZodEffects<z.ZodString, string, string>;
16
+ detail: z.ZodOptional<z.ZodEnum<["text", "grounded", "layout"]>>;
6
17
  }, "strict", z.ZodTypeAny, {
7
18
  source: string;
19
+ detail?: "grounded" | "layout" | "text" | undefined;
8
20
  }, {
9
21
  source: string;
22
+ detail?: "grounded" | "layout" | "text" | undefined;
10
23
  }>;
11
24
  export declare const getParseStatusSchema: z.ZodObject<{
12
25
  operation_id: z.ZodString;
@@ -31,12 +44,12 @@ export declare const readResultSchema: z.ZodObject<{
31
44
  max_bytes: z.ZodOptional<z.ZodNumber>;
32
45
  }, "strict", z.ZodTypeAny, {
33
46
  result_id: string;
34
- max_bytes?: number | undefined;
35
47
  cursor?: string | undefined;
48
+ max_bytes?: number | undefined;
36
49
  }, {
37
50
  result_id: string;
38
- max_bytes?: number | undefined;
39
51
  cursor?: string | undefined;
52
+ max_bytes?: number | undefined;
40
53
  }>;
41
54
  export declare const discardResultSchema: z.ZodObject<{
42
55
  result_id: z.ZodString;
package/dist/protocol.js CHANGED
@@ -1,6 +1,29 @@
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.v2";
3
+ export const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
4
+ // D2-D item 1: versioned persistence literals frozen with the v3 contract.
5
+ // The journal record version and the bundle cursor version are bumped
6
+ // together with the tool contract; legacy v2 journal records still recover
7
+ // text operations unchanged and are never auto-upgraded.
8
+ export const OPERATION_JOURNAL_VERSION = 3;
9
+ export const BUNDLE_CURSOR_VERSION = 2;
10
+ // Canonical bundle/grounding literals carried by non-text representations.
11
+ export const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
12
+ export const GROUNDING_SCHEMA_VERSION = "omni.grounding.v1";
13
+ export function normalizeRepresentation(detail) {
14
+ if (detail === "grounded" || detail === "layout") {
15
+ return {
16
+ detail,
17
+ groundingSchemaVersion: GROUNDING_SCHEMA_VERSION,
18
+ bundleProtocolVersion: RESULT_BUNDLE_PROTOCOL_VERSION,
19
+ };
20
+ }
21
+ return {
22
+ detail: "text",
23
+ groundingSchemaVersion: "none",
24
+ bundleProtocolVersion: "none",
25
+ };
26
+ }
4
27
  export const MACHINE_INSTRUCTIONS = [
5
28
  "Pass the user-provided source string directly to parse.",
6
29
  "Treat only HTTP(S) as URL; ordinary paths require the local Bridge.",
@@ -24,6 +47,7 @@ export const parseSchema = z
24
47
  .min(1)
25
48
  .max(8192)
26
49
  .refine((value) => !value.includes("\0")),
50
+ detail: z.enum(["text", "grounded", "layout"]).optional(),
27
51
  })
28
52
  .strict();
29
53
  export const getParseStatusSchema = z
@@ -1,6 +1,7 @@
1
+ import { type ReaderCapabilitiesV1 } from "./capabilities.js";
1
2
  import { type ParseResult } from "./result-contract.js";
2
3
  export interface RemoteOmniClient {
3
- parse(source: string, clientRequestId: string, signal: AbortSignal): Promise<ParseResult>;
4
+ parse(source: string, clientRequestId: string, signal: AbortSignal, detail?: "text" | "grounded" | "layout"): Promise<ParseResult>;
4
5
  status(operationId: string, waitMs: number | undefined, signal: AbortSignal): Promise<ParseResult>;
5
6
  cancel(operationId: string, signal: AbortSignal): Promise<ParseResult>;
6
7
  }
@@ -11,7 +12,8 @@ export interface HttpRemoteOmniClientOptions {
11
12
  export declare class HttpRemoteOmniClient implements RemoteOmniClient {
12
13
  #private;
13
14
  constructor(options: HttpRemoteOmniClientOptions);
14
- parse(source: string, clientRequestId: string, signal: AbortSignal): Promise<ParseResult>;
15
+ initializeCapabilities(signal: AbortSignal): Promise<ReaderCapabilitiesV1>;
16
+ parse(source: string, clientRequestId: string, signal: AbortSignal, detail?: "text" | "grounded" | "layout"): Promise<ParseResult>;
15
17
  status(operationId: string, waitMs: number | undefined, signal: AbortSignal): Promise<ParseResult>;
16
18
  cancel(operationId: string, signal: AbortSignal): Promise<ParseResult>;
17
19
  }