@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.
- package/README.md +139 -80
- package/dist/artifact-store.d.ts +58 -0
- package/dist/artifact-store.js +449 -5
- package/dist/capabilities.d.ts +92 -0
- package/dist/capabilities.js +123 -0
- package/dist/constants.d.ts +34 -1
- package/dist/constants.js +39 -1
- package/dist/cube-client.d.ts +3 -0
- package/dist/cube-client.js +141 -9
- package/dist/cursor.d.ts +12 -0
- package/dist/cursor.js +89 -9
- package/dist/operation-journal.d.ts +19 -5
- package/dist/operation-journal.js +435 -73
- package/dist/operation-manager.d.ts +7 -2
- package/dist/operation-manager.js +215 -16
- package/dist/protocol.d.ts +16 -3
- package/dist/protocol.js +25 -1
- package/dist/remote-client.d.ts +4 -2
- package/dist/remote-client.js +155 -13
- package/dist/result-bundle.d.ts +21 -0
- package/dist/result-bundle.js +320 -0
- package/dist/result-contract.d.ts +398 -4
- package/dist/result-contract.js +175 -19
- package/dist/server.d.ts +17 -0
- package/dist/server.js +45 -1
- package/dist/tools.d.ts +7 -3
- package/dist/tools.js +77 -1
- package/package.json +1 -1
|
@@ -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" | "
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
246
|
+
async #submit(input) {
|
|
203
247
|
let effectiveInput = input;
|
|
204
|
-
|
|
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.
|
|
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
|
-
|
|
214
|
-
matching = await this.#journal.
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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);
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.
|
|
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.
|
|
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
|
package/dist/remote-client.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|