@cueai/omni-reader-mcp 1.6.0 → 1.7.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 +61 -34
- package/dist/capabilities.d.ts +129 -2
- package/dist/capabilities.js +122 -19
- package/dist/cli/agent-config.js +2 -2
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +7 -3
- package/dist/cube-client.d.ts +4 -1
- package/dist/cube-client.js +286 -32
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +15 -0
- package/dist/iiis-client.d.ts +33 -2
- package/dist/iiis-client.js +368 -40
- package/dist/operation-journal.d.ts +13 -0
- package/dist/operation-journal.js +188 -13
- package/dist/operation-manager.d.ts +1 -1
- package/dist/operation-manager.js +203 -27
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js +6 -2
- package/dist/remote-client.js +3 -13
- package/dist/result-contract.d.ts +30 -26
- package/dist/result-contract.js +3 -2
- package/dist/tools.js +4 -15
- package/package.json +1 -1
package/dist/iiis-client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { DEFAULT_IIIS_GRANTED_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, RESULT_CHUNK_MAX_BYTES, } from "./constants.js";
|
|
2
|
+
import { DEFAULT_IIIS_GRANTED_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, MAX_FILE_BYTES, RESULT_CHUNK_MAX_BYTES, } from "./constants.js";
|
|
3
3
|
import { OmniBridgeError } from "./errors.js";
|
|
4
4
|
import { createMultipartBody } from "./multipart-body.js";
|
|
5
5
|
import { NOOP_PROGRESS, } from "./progress.js";
|
|
6
6
|
class TransportFailure extends Error {
|
|
7
7
|
}
|
|
8
8
|
const ACTIVE_STATUSES = new Set(["CLAIMED", "UPLOADING", "PROCESSING", "SETTLING"]);
|
|
9
|
-
const
|
|
9
|
+
const LEGACY_TERMINAL_STATUSES = new Set([
|
|
10
10
|
"EXPIRED",
|
|
11
11
|
"SETTLEMENT_DENIED",
|
|
12
12
|
"FAILED",
|
|
@@ -14,14 +14,27 @@ const TERMINAL_STATUSES = new Set([
|
|
|
14
14
|
"DELIVERY_EXPIRED",
|
|
15
15
|
"DELIVERED",
|
|
16
16
|
]);
|
|
17
|
-
const
|
|
17
|
+
const PHASE2_TERMINAL_STATUSES = new Set([
|
|
18
|
+
...LEGACY_TERMINAL_STATUSES,
|
|
19
|
+
"UNSUPPORTED",
|
|
20
|
+
]);
|
|
21
|
+
const SETTLED_PHASE2_STATUSES = new Set([
|
|
22
|
+
"RELEASED",
|
|
23
|
+
"DELIVERED",
|
|
24
|
+
"DELIVERY_EXPIRED",
|
|
25
|
+
]);
|
|
26
|
+
const LEGACY_OPERATION_STATUSES = new Set([
|
|
18
27
|
"ISSUED",
|
|
19
28
|
"CLAIMED",
|
|
20
29
|
"UPLOADING",
|
|
21
30
|
"PROCESSING",
|
|
22
31
|
"SETTLING",
|
|
23
32
|
"RELEASED",
|
|
24
|
-
...
|
|
33
|
+
...LEGACY_TERMINAL_STATUSES,
|
|
34
|
+
]);
|
|
35
|
+
const PHASE2_OPERATION_STATUSES = new Set([
|
|
36
|
+
...LEGACY_OPERATION_STATUSES,
|
|
37
|
+
"UNSUPPORTED",
|
|
25
38
|
]);
|
|
26
39
|
const RECOVERABLE_SETTLEMENT_ERRORS = new Set([
|
|
27
40
|
"SETTLEMENT_IN_PROGRESS",
|
|
@@ -33,6 +46,15 @@ const RECOVERABLE_SETTLEMENT_ERRORS = new Set([
|
|
|
33
46
|
]);
|
|
34
47
|
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
35
48
|
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
49
|
+
const STREAM_V2_PROTOCOL = "omni.granted_parse_stream.v2";
|
|
50
|
+
const STREAM_V3_PROTOCOL = "omni.granted_parse_stream.v3";
|
|
51
|
+
const OPERATION_V2_PROTOCOL = "omni.direct_operation.v2";
|
|
52
|
+
const OPERATION_V3_PROTOCOL = "omni.direct_operation.v3";
|
|
53
|
+
const TEXT_BILLING_PROFILE = "omni.direct_text_billing.v1";
|
|
54
|
+
const GROUNDING_BILLING_PROFILE = "omni.direct_grounding_billing.v1";
|
|
55
|
+
const LEGACY_GROUNDING_PROFILE = "omni.direct_grounding.v1";
|
|
56
|
+
const TEXT_MEDIA_TYPE = "text/markdown; charset=utf-8";
|
|
57
|
+
const BUNDLE_MEDIA_TYPE = "application/vnd.cue.omni-result-bundle+json; version=1";
|
|
36
58
|
const ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
37
59
|
const RFC3339_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[Tt](?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/u;
|
|
38
60
|
const PROGRESS_UNITS = new Set([
|
|
@@ -59,6 +81,7 @@ function bridgeError(code, message, facts = {}) {
|
|
|
59
81
|
function recoveryFacts(value) {
|
|
60
82
|
return {
|
|
61
83
|
fileUploaded: value?.fileUploaded ?? false,
|
|
84
|
+
parserStarted: value?.parserStarted ?? false,
|
|
62
85
|
billed: value?.billed ?? false,
|
|
63
86
|
contentReleased: value?.contentReleased ?? false,
|
|
64
87
|
};
|
|
@@ -66,6 +89,7 @@ function recoveryFacts(value) {
|
|
|
66
89
|
function mergeRecoveryFacts(left, right) {
|
|
67
90
|
return {
|
|
68
91
|
fileUploaded: left.fileUploaded || right.fileUploaded === true,
|
|
92
|
+
parserStarted: left.parserStarted || right.parserStarted === true,
|
|
69
93
|
billed: left.billed || right.billed === true,
|
|
70
94
|
contentReleased: left.contentReleased || right.contentReleased === true,
|
|
71
95
|
};
|
|
@@ -73,6 +97,7 @@ function mergeRecoveryFacts(left, right) {
|
|
|
73
97
|
function errorWithRecoveryFacts(error, facts) {
|
|
74
98
|
return bridgeError(error.code, error.message, {
|
|
75
99
|
fileUploaded: error.fileUploaded || facts.fileUploaded,
|
|
100
|
+
parserStarted: error.parserStarted || facts.parserStarted,
|
|
76
101
|
billed: error.billed || facts.billed,
|
|
77
102
|
contentReleased: error.contentReleased || facts.contentReleased,
|
|
78
103
|
retryable: error.retryable,
|
|
@@ -125,6 +150,154 @@ function safeInteger(value, minimum = 0) {
|
|
|
125
150
|
&& Number.isSafeInteger(value)
|
|
126
151
|
&& value >= minimum;
|
|
127
152
|
}
|
|
153
|
+
function inputDirectProfile(input) {
|
|
154
|
+
return input.directProfile ?? null;
|
|
155
|
+
}
|
|
156
|
+
function isBillingProfile(profile) {
|
|
157
|
+
return profile === TEXT_BILLING_PROFILE || profile === GROUNDING_BILLING_PROFILE;
|
|
158
|
+
}
|
|
159
|
+
function operationStatusesFor(profile) {
|
|
160
|
+
return isBillingProfile(profile)
|
|
161
|
+
? PHASE2_OPERATION_STATUSES
|
|
162
|
+
: LEGACY_OPERATION_STATUSES;
|
|
163
|
+
}
|
|
164
|
+
function terminalStatusesFor(profile) {
|
|
165
|
+
return isBillingProfile(profile)
|
|
166
|
+
? PHASE2_TERMINAL_STATUSES
|
|
167
|
+
: LEGACY_TERMINAL_STATUSES;
|
|
168
|
+
}
|
|
169
|
+
function streamProtocolFor(profile) {
|
|
170
|
+
if (isBillingProfile(profile))
|
|
171
|
+
return STREAM_V3_PROTOCOL;
|
|
172
|
+
if (profile === LEGACY_GROUNDING_PROFILE)
|
|
173
|
+
return STREAM_V2_PROTOCOL;
|
|
174
|
+
return GRANTED_STREAM_PROTOCOL_VERSION;
|
|
175
|
+
}
|
|
176
|
+
function operationProtocolFor(profile) {
|
|
177
|
+
if (isBillingProfile(profile))
|
|
178
|
+
return OPERATION_V3_PROTOCOL;
|
|
179
|
+
if (profile === LEGACY_GROUNDING_PROFILE)
|
|
180
|
+
return OPERATION_V2_PROTOCOL;
|
|
181
|
+
return GRANTED_STREAM_PROTOCOL_VERSION;
|
|
182
|
+
}
|
|
183
|
+
function invalidPhase2Response() {
|
|
184
|
+
return bridgeError("IIIS_INVALID_RESPONSE", "IIIS returned invalid billing-aware operation data.");
|
|
185
|
+
}
|
|
186
|
+
const PHASE2_STATUS_REQUIRED_KEYS = [
|
|
187
|
+
"protocol_version",
|
|
188
|
+
"operation_id",
|
|
189
|
+
"status",
|
|
190
|
+
"parser_started",
|
|
191
|
+
"file_uploaded",
|
|
192
|
+
"billed",
|
|
193
|
+
"content_released",
|
|
194
|
+
"retryable",
|
|
195
|
+
"expires_at",
|
|
196
|
+
"profile",
|
|
197
|
+
"detail",
|
|
198
|
+
"approved_result_max_bytes",
|
|
199
|
+
];
|
|
200
|
+
const PHASE2_RELEASE_KEYS = [
|
|
201
|
+
"type",
|
|
202
|
+
"protocol_version",
|
|
203
|
+
"operation_id",
|
|
204
|
+
"stream_protocol_version",
|
|
205
|
+
"profile",
|
|
206
|
+
"detail",
|
|
207
|
+
"approved_result_max_bytes",
|
|
208
|
+
"result",
|
|
209
|
+
"billing",
|
|
210
|
+
"billed",
|
|
211
|
+
"content_released",
|
|
212
|
+
];
|
|
213
|
+
const PHASE2_PROGRESS_KEYS = [
|
|
214
|
+
"type",
|
|
215
|
+
"protocol_version",
|
|
216
|
+
"source",
|
|
217
|
+
"stage",
|
|
218
|
+
"done",
|
|
219
|
+
"total",
|
|
220
|
+
"message",
|
|
221
|
+
];
|
|
222
|
+
const PHASE2_RESULT_CHUNK_KEYS = [
|
|
223
|
+
"type",
|
|
224
|
+
"protocol_version",
|
|
225
|
+
"operation_id",
|
|
226
|
+
"offset",
|
|
227
|
+
"decoded_bytes",
|
|
228
|
+
"data",
|
|
229
|
+
];
|
|
230
|
+
const PHASE2_RESULT_COMPLETE_KEYS = [
|
|
231
|
+
"type",
|
|
232
|
+
"protocol_version",
|
|
233
|
+
"operation_id",
|
|
234
|
+
"result_bytes",
|
|
235
|
+
"result_digest",
|
|
236
|
+
];
|
|
237
|
+
const PHASE2_ERROR_KEYS = [
|
|
238
|
+
"type",
|
|
239
|
+
"protocol_version",
|
|
240
|
+
"code",
|
|
241
|
+
"message",
|
|
242
|
+
"file_uploaded",
|
|
243
|
+
"billed",
|
|
244
|
+
"content_released",
|
|
245
|
+
"retryable",
|
|
246
|
+
];
|
|
247
|
+
function hasExactKeys(value, required, optional = []) {
|
|
248
|
+
const allowed = new Set([...required, ...optional]);
|
|
249
|
+
return required.every((key) => Object.prototype.hasOwnProperty.call(value, key))
|
|
250
|
+
&& Object.keys(value).every((key) => allowed.has(key));
|
|
251
|
+
}
|
|
252
|
+
function billingValue(value) {
|
|
253
|
+
if (!isRecord(value)
|
|
254
|
+
|| typeof value.credits_charged !== "number"
|
|
255
|
+
|| !Number.isFinite(value.credits_charged)
|
|
256
|
+
|| value.credits_charged < 0
|
|
257
|
+
|| typeof value.credits_remaining !== "number"
|
|
258
|
+
|| !Number.isFinite(value.credits_remaining)
|
|
259
|
+
|| value.credits_remaining < 0
|
|
260
|
+
|| Object.keys(value).some((key) => key !== "credits_charged" && key !== "credits_remaining")) {
|
|
261
|
+
throw invalidPhase2Response();
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
credits_charged: value.credits_charged,
|
|
265
|
+
credits_remaining: value.credits_remaining,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function profileDetail(profile, detail) {
|
|
269
|
+
return profile === TEXT_BILLING_PROFILE
|
|
270
|
+
? detail === "text"
|
|
271
|
+
: detail === "grounded" || detail === "layout";
|
|
272
|
+
}
|
|
273
|
+
function expectedDetailFor(input, profile) {
|
|
274
|
+
const detail = input.expectedDetail;
|
|
275
|
+
if (!profileDetail(profile, detail))
|
|
276
|
+
throw invalidPhase2Response();
|
|
277
|
+
return detail;
|
|
278
|
+
}
|
|
279
|
+
function profileMaxBytes(profile) {
|
|
280
|
+
return profile === TEXT_BILLING_PROFILE ? MAX_FILE_BYTES : 67108864;
|
|
281
|
+
}
|
|
282
|
+
function profileMediaType(profile) {
|
|
283
|
+
return profile === TEXT_BILLING_PROFILE ? TEXT_MEDIA_TYPE : BUNDLE_MEDIA_TYPE;
|
|
284
|
+
}
|
|
285
|
+
function resultIdentityValue(value, profile) {
|
|
286
|
+
if (!isRecord(value)
|
|
287
|
+
|| typeof value.digest !== "string"
|
|
288
|
+
|| !SHA256_PATTERN.test(value.digest)
|
|
289
|
+
|| !safeInteger(value.bytes)
|
|
290
|
+
|| value.bytes > profileMaxBytes(profile)
|
|
291
|
+
|| value.media_type !== profileMediaType(profile)
|
|
292
|
+
|| Object.keys(value).some((key) => key !== "digest" && key !== "bytes" && key !== "media_type")) {
|
|
293
|
+
throw invalidPhase2Response();
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
digest: value.digest,
|
|
297
|
+
bytes: value.bytes,
|
|
298
|
+
mediaType: value.media_type,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
128
301
|
function isRfc3339(value) {
|
|
129
302
|
if (typeof value !== "string")
|
|
130
303
|
return false;
|
|
@@ -141,8 +314,8 @@ function isRfc3339(value) {
|
|
|
141
314
|
const days = month === 2 && leapYear ? 29 : monthDays[month - 1];
|
|
142
315
|
return day <= days;
|
|
143
316
|
}
|
|
144
|
-
function validateProtocol(event) {
|
|
145
|
-
if (event.protocol_version !==
|
|
317
|
+
function validateProtocol(event, expectedProtocol = GRANTED_STREAM_PROTOCOL_VERSION) {
|
|
318
|
+
if (event.protocol_version !== expectedProtocol) {
|
|
146
319
|
throw bridgeError("PROTOCOL_MISMATCH", "IIIS returned an unsupported protocol version.");
|
|
147
320
|
}
|
|
148
321
|
}
|
|
@@ -169,11 +342,31 @@ function stableProgressMessage(stage) {
|
|
|
169
342
|
return "Finalizing result";
|
|
170
343
|
return "Processing document";
|
|
171
344
|
}
|
|
172
|
-
function progressValue(event) {
|
|
173
|
-
|
|
345
|
+
function progressValue(event, expectedProtocol = GRANTED_STREAM_PROTOCOL_VERSION, retained = false) {
|
|
346
|
+
if (expectedProtocol === STREAM_V3_PROTOCOL) {
|
|
347
|
+
const requiredKeys = retained
|
|
348
|
+
? [...PHASE2_PROGRESS_KEYS, "sequence"]
|
|
349
|
+
: PHASE2_PROGRESS_KEYS;
|
|
350
|
+
if (!hasExactKeys(event, requiredKeys)
|
|
351
|
+
|| event.type !== "progress"
|
|
352
|
+
|| event.protocol_version !== STREAM_V3_PROTOCOL
|
|
353
|
+
|| (event.source !== "bridge" && event.source !== "l1")
|
|
354
|
+
|| typeof event.stage !== "string"
|
|
355
|
+
|| event.stage.length < 1
|
|
356
|
+
|| event.stage.length > 64
|
|
357
|
+
|| typeof event.message !== "string"
|
|
358
|
+
|| event.message.length < 1
|
|
359
|
+
|| event.message.length > 256
|
|
360
|
+
|| (retained && !safeInteger(event.sequence, 1))) {
|
|
361
|
+
throw invalidPhase2Response();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
validateProtocol(event, expectedProtocol);
|
|
174
365
|
if (!safeInteger(event.done) ||
|
|
175
366
|
!safeInteger(event.total) ||
|
|
176
367
|
event.done > event.total) {
|
|
368
|
+
if (expectedProtocol === STREAM_V3_PROTOCOL)
|
|
369
|
+
throw invalidPhase2Response();
|
|
177
370
|
throw bridgeError("INVALID_PROGRESS_EVENT", "IIIS returned an invalid progress event.");
|
|
178
371
|
}
|
|
179
372
|
const unit = typeof event.unit === "string" &&
|
|
@@ -316,21 +509,65 @@ async function protocolJson(response, input, description) {
|
|
|
316
509
|
throw bridgeError("PROTOCOL_MISMATCH", `IIIS returned invalid ${description} JSON.`);
|
|
317
510
|
}
|
|
318
511
|
}
|
|
319
|
-
function operationStatusValue(value, operationId) {
|
|
512
|
+
function operationStatusValue(value, operationId, directProfile, expectedDetail) {
|
|
320
513
|
if (!isRecord(value)
|
|
321
|
-
|| value.protocol_version !==
|
|
514
|
+
|| value.protocol_version !== operationProtocolFor(directProfile)
|
|
322
515
|
|| value.operation_id !== operationId
|
|
323
516
|
|| typeof value.status !== "string"
|
|
324
|
-
|| !
|
|
517
|
+
|| !operationStatusesFor(directProfile).has(value.status)
|
|
325
518
|
|| typeof value.parser_started !== "boolean"
|
|
326
519
|
|| typeof value.file_uploaded !== "boolean"
|
|
327
520
|
|| typeof value.billed !== "boolean"
|
|
328
521
|
|| typeof value.content_released !== "boolean"
|
|
329
522
|
|| typeof value.retryable !== "boolean"
|
|
330
523
|
|| !(value.expires_at === null || isRfc3339(value.expires_at))) {
|
|
524
|
+
if (isBillingProfile(directProfile))
|
|
525
|
+
throw invalidPhase2Response();
|
|
331
526
|
throw bridgeError("PROTOCOL_MISMATCH", "IIIS returned an invalid operation status.");
|
|
332
527
|
}
|
|
333
|
-
|
|
528
|
+
let billing = null;
|
|
529
|
+
let result = null;
|
|
530
|
+
if (isBillingProfile(directProfile)) {
|
|
531
|
+
if (!hasExactKeys(value, PHASE2_STATUS_REQUIRED_KEYS, ["result", "billing"])
|
|
532
|
+
|| value.profile !== directProfile
|
|
533
|
+
|| value.protocol_version !== OPERATION_V3_PROTOCOL
|
|
534
|
+
|| !profileDetail(directProfile, expectedDetail)
|
|
535
|
+
|| value.detail !== expectedDetail
|
|
536
|
+
|| value.approved_result_max_bytes !== profileMaxBytes(directProfile)) {
|
|
537
|
+
throw invalidPhase2Response();
|
|
538
|
+
}
|
|
539
|
+
const settled = SETTLED_PHASE2_STATUSES.has(value.status);
|
|
540
|
+
if (settled) {
|
|
541
|
+
if (!value.billed
|
|
542
|
+
|| !value.content_released
|
|
543
|
+
|| !Object.prototype.hasOwnProperty.call(value, "result")
|
|
544
|
+
|| !Object.prototype.hasOwnProperty.call(value, "billing")) {
|
|
545
|
+
throw invalidPhase2Response();
|
|
546
|
+
}
|
|
547
|
+
result = resultIdentityValue(value.result, directProfile);
|
|
548
|
+
billing = billingValue(value.billing);
|
|
549
|
+
}
|
|
550
|
+
else if (value.billed
|
|
551
|
+
|| value.content_released
|
|
552
|
+
|| Object.prototype.hasOwnProperty.call(value, "result")
|
|
553
|
+
|| Object.prototype.hasOwnProperty.call(value, "billing")) {
|
|
554
|
+
throw invalidPhase2Response();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
protocol_version: value.protocol_version,
|
|
559
|
+
operation_id: value.operation_id,
|
|
560
|
+
status: value.status,
|
|
561
|
+
parser_started: value.parser_started,
|
|
562
|
+
file_uploaded: value.file_uploaded,
|
|
563
|
+
billed: value.billed,
|
|
564
|
+
content_released: value.content_released,
|
|
565
|
+
retryable: value.retryable,
|
|
566
|
+
expires_at: value.expires_at,
|
|
567
|
+
directProfile,
|
|
568
|
+
billing,
|
|
569
|
+
result,
|
|
570
|
+
};
|
|
334
571
|
}
|
|
335
572
|
function operationSnapshot(status) {
|
|
336
573
|
return {
|
|
@@ -341,6 +578,8 @@ function operationSnapshot(status) {
|
|
|
341
578
|
contentReleased: status.content_released,
|
|
342
579
|
retryable: status.retryable,
|
|
343
580
|
expiresAt: status.expires_at,
|
|
581
|
+
directProfile: status.directProfile,
|
|
582
|
+
billing: status.billing,
|
|
344
583
|
};
|
|
345
584
|
}
|
|
346
585
|
async function* sseEvents(response) {
|
|
@@ -454,10 +693,16 @@ export class IiisClient {
|
|
|
454
693
|
return this.inspectOperation(input);
|
|
455
694
|
}
|
|
456
695
|
const value = await protocolJson(response, input, "operation cancellation");
|
|
457
|
-
return operationSnapshot(operationStatusValue(value, input.operationId));
|
|
696
|
+
return operationSnapshot(operationStatusValue(value, input.operationId, inputDirectProfile(input), input.expectedDetail));
|
|
458
697
|
}
|
|
459
|
-
async downloadResult(input, progress = NOOP_PROGRESS) {
|
|
698
|
+
async downloadResult(input, progress = NOOP_PROGRESS, settledStatus) {
|
|
460
699
|
throwIfCanceled(input);
|
|
700
|
+
const directProfile = settledStatus?.directProfile ?? inputDirectProfile(input);
|
|
701
|
+
const billing = settledStatus?.billing ?? null;
|
|
702
|
+
const expectedResult = settledStatus?.result ?? null;
|
|
703
|
+
if (isBillingProfile(directProfile) && (billing === null || expectedResult === null)) {
|
|
704
|
+
throw invalidPhase2Response();
|
|
705
|
+
}
|
|
461
706
|
const confirmedFacts = recoveryFacts({
|
|
462
707
|
fileUploaded: true,
|
|
463
708
|
billed: true,
|
|
@@ -501,15 +746,22 @@ export class IiisClient {
|
|
|
501
746
|
throw errorWithRecoveryFacts(await errorFromResponse(response, input), confirmedFacts);
|
|
502
747
|
}
|
|
503
748
|
const declared = Number(response.headers.get("content-length"));
|
|
749
|
+
const mediaType = response.headers.get("content-type")
|
|
750
|
+
?? "application/octet-stream";
|
|
504
751
|
if (!safeInteger(declared)) {
|
|
505
752
|
throw bridgeError("RESULT_INTEGRITY_FAILED", "The recovered result length is invalid.", {
|
|
506
753
|
...confirmedFacts,
|
|
507
754
|
});
|
|
508
755
|
}
|
|
756
|
+
if (expectedResult !== null
|
|
757
|
+
&& (declared !== expectedResult.bytes
|
|
758
|
+
|| mediaType !== expectedResult.mediaType)) {
|
|
759
|
+
throw invalidPhase2Response();
|
|
760
|
+
}
|
|
509
761
|
const start = {
|
|
510
762
|
operationId: input.operationId,
|
|
511
763
|
resultBytes: declared,
|
|
512
|
-
mediaType
|
|
764
|
+
mediaType,
|
|
513
765
|
source: "recovery",
|
|
514
766
|
};
|
|
515
767
|
const hash = createHash("sha256");
|
|
@@ -549,9 +801,16 @@ export class IiisClient {
|
|
|
549
801
|
...confirmedFacts,
|
|
550
802
|
});
|
|
551
803
|
}
|
|
804
|
+
const resultDigest = `sha256:${hash.digest("hex")}`;
|
|
805
|
+
if (expectedResult !== null
|
|
806
|
+
&& resultDigest !== expectedResult.digest) {
|
|
807
|
+
throw invalidPhase2Response();
|
|
808
|
+
}
|
|
552
809
|
const metadata = {
|
|
553
810
|
...start,
|
|
554
|
-
resultDigest
|
|
811
|
+
resultDigest,
|
|
812
|
+
directProfile,
|
|
813
|
+
billing,
|
|
555
814
|
};
|
|
556
815
|
await input.retention.complete(metadata);
|
|
557
816
|
throwIfCanceled(input);
|
|
@@ -644,6 +903,11 @@ export class IiisClient {
|
|
|
644
903
|
async #consumeReleasedSse(input, response, progress) {
|
|
645
904
|
let released;
|
|
646
905
|
let completedMetadata;
|
|
906
|
+
const directProfile = inputDirectProfile(input);
|
|
907
|
+
const expectedProtocol = streamProtocolFor(directProfile);
|
|
908
|
+
const expectedDetail = isBillingProfile(directProfile)
|
|
909
|
+
? expectedDetailFor(input, directProfile)
|
|
910
|
+
: undefined;
|
|
647
911
|
let offset = 0;
|
|
648
912
|
let complete = false;
|
|
649
913
|
let streamFacts = recoveryFacts();
|
|
@@ -660,37 +924,84 @@ export class IiisClient {
|
|
|
660
924
|
});
|
|
661
925
|
}
|
|
662
926
|
if (event.type === "progress") {
|
|
663
|
-
const parsed = progressValue(event);
|
|
927
|
+
const parsed = progressValue(event, expectedProtocol);
|
|
664
928
|
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message, parsed.detail);
|
|
665
929
|
continue;
|
|
666
930
|
}
|
|
667
931
|
if (event.type === "error") {
|
|
932
|
+
if (isBillingProfile(directProfile)) {
|
|
933
|
+
if (!hasExactKeys(event, PHASE2_ERROR_KEYS)
|
|
934
|
+
|| event.protocol_version !== STREAM_V3_PROTOCOL
|
|
935
|
+
|| typeof event.code !== "string"
|
|
936
|
+
|| !ERROR_CODE_PATTERN.test(event.code)
|
|
937
|
+
|| typeof event.message !== "string"
|
|
938
|
+
|| event.message.length < 1
|
|
939
|
+
|| event.message.length > 512
|
|
940
|
+
|| typeof event.file_uploaded !== "boolean"
|
|
941
|
+
|| event.billed !== false
|
|
942
|
+
|| event.content_released !== false
|
|
943
|
+
|| typeof event.retryable !== "boolean") {
|
|
944
|
+
throw invalidPhase2Response();
|
|
945
|
+
}
|
|
946
|
+
}
|
|
668
947
|
const code = stableErrorCode(event.code, "IIIS_PARSE_FAILED");
|
|
669
948
|
throw bridgeError(code, stableErrorMessage(code), {
|
|
670
949
|
fileUploaded: event.file_uploaded === true,
|
|
671
|
-
billed:
|
|
672
|
-
|
|
950
|
+
billed: isBillingProfile(directProfile)
|
|
951
|
+
? false
|
|
952
|
+
: event.billed === true,
|
|
953
|
+
contentReleased: isBillingProfile(directProfile)
|
|
954
|
+
? false
|
|
955
|
+
: event.content_released === true,
|
|
673
956
|
retryable: event.retryable === true,
|
|
674
957
|
});
|
|
675
958
|
}
|
|
676
959
|
if (event.type === "released") {
|
|
677
|
-
|
|
960
|
+
if (isBillingProfile(directProfile)
|
|
961
|
+
&& !hasExactKeys(event, PHASE2_RELEASE_KEYS)) {
|
|
962
|
+
throw invalidPhase2Response();
|
|
963
|
+
}
|
|
964
|
+
validateProtocol(event, expectedProtocol);
|
|
678
965
|
if (released !== undefined
|
|
679
966
|
|| event.operation_id !== input.operationId
|
|
680
967
|
|| event.billed !== true
|
|
681
|
-
|| event.content_released !== true
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|| event.media_type.length === 0
|
|
685
|
-
|| typeof event.result_digest !== "string"
|
|
686
|
-
|| !SHA256_PATTERN.test(event.result_digest)) {
|
|
968
|
+
|| event.content_released !== true) {
|
|
969
|
+
if (isBillingProfile(directProfile))
|
|
970
|
+
throw invalidPhase2Response();
|
|
687
971
|
throw bridgeError("INVALID_RELEASE_EVENT", "IIIS returned an invalid release event.");
|
|
688
972
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
973
|
+
if (isBillingProfile(directProfile)) {
|
|
974
|
+
if (event.stream_protocol_version !== STREAM_V3_PROTOCOL
|
|
975
|
+
|| event.profile !== directProfile
|
|
976
|
+
|| event.detail !== expectedDetail
|
|
977
|
+
|| event.approved_result_max_bytes !== profileMaxBytes(directProfile)) {
|
|
978
|
+
throw invalidPhase2Response();
|
|
979
|
+
}
|
|
980
|
+
const result = resultIdentityValue(event.result, directProfile);
|
|
981
|
+
released = {
|
|
982
|
+
resultBytes: result.bytes,
|
|
983
|
+
mediaType: result.mediaType,
|
|
984
|
+
resultDigest: result.digest,
|
|
985
|
+
directProfile,
|
|
986
|
+
billing: billingValue(event.billing),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
if (!safeInteger(event.result_bytes)
|
|
991
|
+
|| typeof event.media_type !== "string"
|
|
992
|
+
|| event.media_type.length === 0
|
|
993
|
+
|| typeof event.result_digest !== "string"
|
|
994
|
+
|| !SHA256_PATTERN.test(event.result_digest)) {
|
|
995
|
+
throw bridgeError("INVALID_RELEASE_EVENT", "IIIS returned an invalid release event.");
|
|
996
|
+
}
|
|
997
|
+
released = {
|
|
998
|
+
resultBytes: event.result_bytes,
|
|
999
|
+
mediaType: event.media_type,
|
|
1000
|
+
resultDigest: event.result_digest,
|
|
1001
|
+
directProfile,
|
|
1002
|
+
billing: null,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
694
1005
|
streamFacts = recoveryFacts({
|
|
695
1006
|
fileUploaded: true,
|
|
696
1007
|
billed: true,
|
|
@@ -705,7 +1016,11 @@ export class IiisClient {
|
|
|
705
1016
|
continue;
|
|
706
1017
|
}
|
|
707
1018
|
if (event.type === "result_chunk") {
|
|
708
|
-
|
|
1019
|
+
if (isBillingProfile(directProfile)
|
|
1020
|
+
&& !hasExactKeys(event, PHASE2_RESULT_CHUNK_KEYS)) {
|
|
1021
|
+
throw invalidPhase2Response();
|
|
1022
|
+
}
|
|
1023
|
+
validateProtocol(event, expectedProtocol);
|
|
709
1024
|
if (released === undefined) {
|
|
710
1025
|
throw bridgeError("RESULT_BEFORE_RELEASE", "IIIS sent result bytes before billing release.");
|
|
711
1026
|
}
|
|
@@ -734,7 +1049,11 @@ export class IiisClient {
|
|
|
734
1049
|
continue;
|
|
735
1050
|
}
|
|
736
1051
|
if (event.type === "result_complete") {
|
|
737
|
-
|
|
1052
|
+
if (isBillingProfile(directProfile)
|
|
1053
|
+
&& !hasExactKeys(event, PHASE2_RESULT_COMPLETE_KEYS)) {
|
|
1054
|
+
throw invalidPhase2Response();
|
|
1055
|
+
}
|
|
1056
|
+
validateProtocol(event, expectedProtocol);
|
|
738
1057
|
if (released === undefined
|
|
739
1058
|
|| event.operation_id !== input.operationId
|
|
740
1059
|
|| !safeInteger(event.result_bytes)
|
|
@@ -754,6 +1073,8 @@ export class IiisClient {
|
|
|
754
1073
|
mediaType: released.mediaType,
|
|
755
1074
|
resultDigest: released.resultDigest,
|
|
756
1075
|
source: "sse",
|
|
1076
|
+
directProfile: released.directProfile,
|
|
1077
|
+
billing: released.billing,
|
|
757
1078
|
};
|
|
758
1079
|
await input.retention.complete(metadata);
|
|
759
1080
|
throwIfCanceled(input);
|
|
@@ -761,6 +1082,8 @@ export class IiisClient {
|
|
|
761
1082
|
complete = true;
|
|
762
1083
|
continue;
|
|
763
1084
|
}
|
|
1085
|
+
if (isBillingProfile(directProfile))
|
|
1086
|
+
throw invalidPhase2Response();
|
|
764
1087
|
throw bridgeError("INVALID_SSE_EVENT", "IIIS returned an unsupported progress event.");
|
|
765
1088
|
}
|
|
766
1089
|
if (completedMetadata !== undefined) {
|
|
@@ -772,7 +1095,9 @@ export class IiisClient {
|
|
|
772
1095
|
}
|
|
773
1096
|
catch (error) {
|
|
774
1097
|
const facts = error instanceof OmniBridgeError
|
|
775
|
-
?
|
|
1098
|
+
? error.code === "IIIS_INVALID_RESPONSE"
|
|
1099
|
+
? recoveryFacts(error)
|
|
1100
|
+
: mergeRecoveryFacts(streamFacts, recoveryFacts(error))
|
|
776
1101
|
: streamFacts;
|
|
777
1102
|
try {
|
|
778
1103
|
await input.retention.abort();
|
|
@@ -808,7 +1133,7 @@ export class IiisClient {
|
|
|
808
1133
|
if (!response.ok)
|
|
809
1134
|
throw await errorFromResponse(response, input);
|
|
810
1135
|
const value = await protocolJson(response, input, "operation status");
|
|
811
|
-
return operationStatusValue(value, input.operationId);
|
|
1136
|
+
return operationStatusValue(value, input.operationId, inputDirectProfile(input), input.expectedDetail);
|
|
812
1137
|
}
|
|
813
1138
|
async #events(input, afterSequence) {
|
|
814
1139
|
throwIfCanceled(input);
|
|
@@ -823,8 +1148,9 @@ export class IiisClient {
|
|
|
823
1148
|
if (!response.ok)
|
|
824
1149
|
throw await errorFromResponse(response, input);
|
|
825
1150
|
const value = await protocolJson(response, input, "operation events");
|
|
1151
|
+
const expectedProtocol = streamProtocolFor(inputDirectProfile(input));
|
|
826
1152
|
if (!isRecord(value)
|
|
827
|
-
|| value.protocol_version !==
|
|
1153
|
+
|| value.protocol_version !== expectedProtocol
|
|
828
1154
|
|| value.operation_id !== input.operationId
|
|
829
1155
|
|| typeof value.status !== "string"
|
|
830
1156
|
|| value.after_sequence !== afterSequence
|
|
@@ -839,7 +1165,7 @@ export class IiisClient {
|
|
|
839
1165
|
if (!isRecord(raw) || raw.sequence !== expected || raw.type !== "progress") {
|
|
840
1166
|
throw bridgeError("EVENT_CURSOR_EXPIRED", "IIIS returned a non-contiguous operation event page.");
|
|
841
1167
|
}
|
|
842
|
-
progressValue(raw);
|
|
1168
|
+
progressValue(raw, expectedProtocol, true);
|
|
843
1169
|
events.push(raw);
|
|
844
1170
|
expected += 1;
|
|
845
1171
|
}
|
|
@@ -859,6 +1185,7 @@ export class IiisClient {
|
|
|
859
1185
|
const status = await this.#status(input);
|
|
860
1186
|
facts = mergeRecoveryFacts(facts, {
|
|
861
1187
|
fileUploaded: status.file_uploaded,
|
|
1188
|
+
parserStarted: status.parser_started,
|
|
862
1189
|
billed: status.billed,
|
|
863
1190
|
contentReleased: status.content_released,
|
|
864
1191
|
});
|
|
@@ -881,19 +1208,20 @@ export class IiisClient {
|
|
|
881
1208
|
}
|
|
882
1209
|
}
|
|
883
1210
|
else if (status.status === "RELEASED") {
|
|
884
|
-
return await this.downloadResult(input, progress);
|
|
1211
|
+
return await this.downloadResult(input, progress, status);
|
|
885
1212
|
}
|
|
886
1213
|
else if (ACTIVE_STATUSES.has(status.status)) {
|
|
887
1214
|
const page = await this.#events(input, eventCursor);
|
|
888
1215
|
for (const event of page.events) {
|
|
889
|
-
const parsed = progressValue(event);
|
|
1216
|
+
const parsed = progressValue(event, streamProtocolFor(inputDirectProfile(input)), true);
|
|
890
1217
|
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message, parsed.detail);
|
|
891
1218
|
}
|
|
892
1219
|
eventCursor = page.nextSequence;
|
|
893
1220
|
}
|
|
894
|
-
else if (
|
|
1221
|
+
else if (terminalStatusesFor(inputDirectProfile(input)).has(status.status)) {
|
|
895
1222
|
throw bridgeError(status.status, stableErrorMessage(status.status), {
|
|
896
1223
|
fileUploaded: status.file_uploaded,
|
|
1224
|
+
parserStarted: status.parser_started,
|
|
897
1225
|
billed: status.billed,
|
|
898
1226
|
contentReleased: status.content_released,
|
|
899
1227
|
retryable: status.retryable,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DirectProfile } from "./capabilities.js";
|
|
1
2
|
import { type RepresentationIntent } from "./protocol.js";
|
|
2
3
|
export type JournalState = "CREATED" | "GRANT_PENDING" | "GRANT_ISSUED" | "UPLOADING" | "PROCESSING" | "RESULT_READY" | "ACK_PENDING" | "CLEANUP_PENDING" | "COMPLETED" | "FAILED" | "CANCELED" | "EXPIRED";
|
|
3
4
|
export type JournalProgressUnit = "page" | "sheet" | "slide" | "frame" | "segment";
|
|
@@ -9,6 +10,14 @@ export interface JournalProgress {
|
|
|
9
10
|
readonly completed: number;
|
|
10
11
|
readonly total: number;
|
|
11
12
|
}
|
|
13
|
+
export interface JournalBillingFacts {
|
|
14
|
+
readonly creditsCharged: number;
|
|
15
|
+
readonly creditsRemaining: number;
|
|
16
|
+
}
|
|
17
|
+
export interface JournalSettlementFacts {
|
|
18
|
+
readonly directProfile: DirectProfile | null;
|
|
19
|
+
readonly billing: JournalBillingFacts;
|
|
20
|
+
}
|
|
12
21
|
export type JournalSourceKind = "local" | "url";
|
|
13
22
|
export type JournalDetail = "text" | "grounded" | "layout";
|
|
14
23
|
export type JournalGroundingSchemaVersion = "none" | "omni.grounding.v1";
|
|
@@ -24,6 +33,8 @@ export interface JournalRecord {
|
|
|
24
33
|
readonly groundingSchemaVersion: JournalGroundingSchemaVersion;
|
|
25
34
|
readonly bundleProtocolVersion: JournalBundleProtocolVersion;
|
|
26
35
|
readonly resultDeliveryEffective: JournalResultDelivery;
|
|
36
|
+
readonly directProfile: DirectProfile | null;
|
|
37
|
+
readonly billing: JournalBillingFacts | null;
|
|
27
38
|
readonly operationId: string | null;
|
|
28
39
|
readonly operationToken: string | null;
|
|
29
40
|
readonly uploadUrl: string | null;
|
|
@@ -91,6 +102,8 @@ export declare class OperationJournal {
|
|
|
91
102
|
migrateLegacyRecord(clientRequestId: string, canonicalIdentityJson: string, sourceLocator?: string | null): Promise<JournalRecord | null>;
|
|
92
103
|
transition(clientRequestId: string, expectedState: JournalState, nextState: JournalState, patch: JournalPatch): Promise<JournalRecord>;
|
|
93
104
|
strengthenResultDelivery(clientRequestId: string, requested: JournalResultDelivery): Promise<JournalRecord>;
|
|
105
|
+
bindDirectProfile(clientRequestId: string, directProfile: DirectProfile): Promise<JournalRecord>;
|
|
106
|
+
bindSettlementFacts(clientRequestId: string, facts: JournalSettlementFacts): Promise<JournalRecord>;
|
|
94
107
|
loadByRequestId(clientRequestId: string): Promise<JournalRecord | null>;
|
|
95
108
|
loadByOperationId(operationId: string): Promise<JournalRecord | null>;
|
|
96
109
|
loadLatestByRequestIdentityHmac(requestIdentityHmac: string): Promise<JournalRecord | null>;
|