@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.
- package/README.md +66 -37
- 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 +287 -33
- 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/cube-client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { BRIDGE_RELEASE_VERSION, CUBE_CAPABILITIES_PATH, CUBE_GRANT_PROTOCOL_VERSION, DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES, } from "./constants.js";
|
|
4
|
-
import { parseReaderCapabilities, selectDirectProfile, } from "./capabilities.js";
|
|
5
|
-
import { OmniBridgeError } from "./errors.js";
|
|
3
|
+
import { BRIDGE_RELEASE_VERSION, CAPABILITIES_V2_PATH, CUBE_CAPABILITIES_PATH, CUBE_GRANT_PROTOCOL_VERSION, DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES, } from "./constants.js";
|
|
4
|
+
import { DIRECT_GROUNDING_BILLING_PROFILE, DIRECT_TEXT_BILLING_PROFILE, parseReaderCapabilities, parseReaderCapabilitiesV2, selectDirectProfile, selectDirectProfileV2, } from "./capabilities.js";
|
|
5
|
+
import { OmniBridgeError, unsupportedDetailError, } from "./errors.js";
|
|
6
6
|
const GRANT_PATH = "/api/omni-reader/direct-upload/v1/parse-grants";
|
|
7
7
|
const BRIDGE_PACKAGE = "@cueai/omni-reader-mcp";
|
|
8
8
|
// Legacy grant response for UNPROFILED (detail-less) requests. cube-mcp serves
|
|
@@ -48,6 +48,16 @@ const bridgeUpgradeRequiredSchema = z
|
|
|
48
48
|
retryable: z.literal(false),
|
|
49
49
|
})
|
|
50
50
|
.strict();
|
|
51
|
+
const unsupportedDetailSchema = z
|
|
52
|
+
.object({
|
|
53
|
+
code: z.literal("UNSUPPORTED_DETAIL"),
|
|
54
|
+
message: z.literal("The declared capability profile is not supported by this control plane."),
|
|
55
|
+
file_uploaded: z.literal(false),
|
|
56
|
+
billed: z.literal(false),
|
|
57
|
+
content_released: z.literal(false),
|
|
58
|
+
retryable: z.literal(false),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
51
61
|
// Closed v3 grant response: the exact tuple of the selected direct profile is
|
|
52
62
|
// required before the upload phase may start. The tuple is re-checked against
|
|
53
63
|
// the profile after schema parsing so a drifting server cannot slip through.
|
|
@@ -70,6 +80,90 @@ const grantResponseV3Schema = z
|
|
|
70
80
|
approved_result_max_bytes: z.literal(67108864),
|
|
71
81
|
})
|
|
72
82
|
.strict();
|
|
83
|
+
const grantResponseV4Schema = z
|
|
84
|
+
.object({
|
|
85
|
+
grant_id: z.string().min(1),
|
|
86
|
+
operation_id: z.string().min(1),
|
|
87
|
+
parse_grant: z.string().min(1),
|
|
88
|
+
operation_token: z.string().min(1),
|
|
89
|
+
upload_url: z
|
|
90
|
+
.string()
|
|
91
|
+
.url()
|
|
92
|
+
.refine((value) => new URL(value).protocol === "https:"),
|
|
93
|
+
expires_at: z.string().datetime({ offset: true }),
|
|
94
|
+
max_bytes: z.literal(MAX_FILE_BYTES),
|
|
95
|
+
protocol_version: z.literal("omni.parse_grant.v4"),
|
|
96
|
+
})
|
|
97
|
+
.strict();
|
|
98
|
+
const textRepresentationV4Schema = z
|
|
99
|
+
.object({
|
|
100
|
+
kind: z.literal("text"),
|
|
101
|
+
media_type: z.literal("text/markdown; charset=utf-8"),
|
|
102
|
+
usage_schema_version: z.literal("omni_parse_usage.v2"),
|
|
103
|
+
billing_contract_version: z.literal("omni_billing.v2"),
|
|
104
|
+
})
|
|
105
|
+
.strict();
|
|
106
|
+
const bundleRepresentationV4Schema = z
|
|
107
|
+
.object({
|
|
108
|
+
kind: z.literal("result_bundle"),
|
|
109
|
+
media_type: z.literal("application/vnd.cue.omni-result-bundle+json; version=1"),
|
|
110
|
+
bundle_protocol_version: z.literal("omni.result_bundle.v1"),
|
|
111
|
+
grounding_schema_version: z.literal("omni.grounding.v1"),
|
|
112
|
+
usage_schema_version: z.literal("omni_parse_usage.v2"),
|
|
113
|
+
billing_contract_version: z.literal("omni_billing.v2"),
|
|
114
|
+
})
|
|
115
|
+
.strict();
|
|
116
|
+
const signedGrantV4BaseShape = {
|
|
117
|
+
protocol_version: z.literal("omni.parse_grant.v4"),
|
|
118
|
+
iss: z.string().min(1).max(64),
|
|
119
|
+
aud: z.literal("omni-reader-l1"),
|
|
120
|
+
sub: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
|
|
121
|
+
jti: z.string().regex(/^pg_[A-Za-z0-9_-]+$/u),
|
|
122
|
+
grant_id: z.string().regex(/^pg_[A-Za-z0-9_-]+$/u),
|
|
123
|
+
operation_id: z.string().regex(/^op_[A-Za-z0-9_-]+$/u),
|
|
124
|
+
usage_event_id: z.string().regex(/^[a-f0-9]{32}$/u),
|
|
125
|
+
request_id: z.string().max(128).regex(/^req_[A-Za-z0-9_-]+$/u),
|
|
126
|
+
stream_protocol: z.literal("omni.granted_parse_stream.v3"),
|
|
127
|
+
operation_protocol: z.literal("omni.direct_operation.v3"),
|
|
128
|
+
settlement_protocol: z.literal("omni.grant_settlement.v5"),
|
|
129
|
+
release_protocol: z.literal("omni.release_decision.v3"),
|
|
130
|
+
settlement_journal_protocol: z.literal("omni.direct_settlement_journal.v3"),
|
|
131
|
+
usage_schema_version: z.literal("omni_parse_usage.v2"),
|
|
132
|
+
billing_contract_version: z.literal("omni_billing.v2"),
|
|
133
|
+
bridge_protocol: z.literal("omni.local_bridge_tools.v5"),
|
|
134
|
+
method: z.literal("POST"),
|
|
135
|
+
path: z.literal("/omni/granted/parse_stream"),
|
|
136
|
+
content_length: z.number().int().min(1).max(MAX_FILE_BYTES),
|
|
137
|
+
content_type: z.string().min(1).max(128),
|
|
138
|
+
file_extension: z.string().regex(/^\.[a-z0-9]{1,10}$/u),
|
|
139
|
+
no_store: z.boolean(),
|
|
140
|
+
output: z.enum(["markdown", "hypertext", "chunks"]),
|
|
141
|
+
max_bytes: z.literal(MAX_FILE_BYTES),
|
|
142
|
+
nonce: z.string().min(32).max(256),
|
|
143
|
+
iat: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
144
|
+
nbf: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
145
|
+
exp: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
146
|
+
};
|
|
147
|
+
const signedGrantV4Schema = z.discriminatedUnion("profile", [
|
|
148
|
+
z
|
|
149
|
+
.object({
|
|
150
|
+
...signedGrantV4BaseShape,
|
|
151
|
+
profile: z.literal(DIRECT_TEXT_BILLING_PROFILE.profile),
|
|
152
|
+
detail: z.literal("text"),
|
|
153
|
+
representation: textRepresentationV4Schema,
|
|
154
|
+
approved_result_max_bytes: z.literal(DIRECT_TEXT_BILLING_PROFILE.max_result_bytes),
|
|
155
|
+
})
|
|
156
|
+
.strict(),
|
|
157
|
+
z
|
|
158
|
+
.object({
|
|
159
|
+
...signedGrantV4BaseShape,
|
|
160
|
+
profile: z.literal(DIRECT_GROUNDING_BILLING_PROFILE.profile),
|
|
161
|
+
detail: z.enum(["grounded", "layout"]),
|
|
162
|
+
representation: bundleRepresentationV4Schema,
|
|
163
|
+
approved_result_max_bytes: z.literal(DIRECT_GROUNDING_BILLING_PROFILE.max_result_bytes),
|
|
164
|
+
})
|
|
165
|
+
.strict(),
|
|
166
|
+
]);
|
|
73
167
|
function bridgeError(code, message, retryable, options = {}) {
|
|
74
168
|
return new OmniBridgeError({
|
|
75
169
|
code,
|
|
@@ -81,6 +175,7 @@ function bridgeError(code, message, retryable, options = {}) {
|
|
|
81
175
|
contentReleased: false,
|
|
82
176
|
retryable,
|
|
83
177
|
...(options.failureScope === undefined ? {} : { failureScope: options.failureScope }),
|
|
178
|
+
...(options.sourceKind === undefined ? {} : { sourceKind: options.sourceKind }),
|
|
84
179
|
...(options.userAction === undefined ? {} : { userAction: options.userAction }),
|
|
85
180
|
...(options.retryAfter === undefined ? {} : { retryAfter: options.retryAfter }),
|
|
86
181
|
});
|
|
@@ -125,6 +220,19 @@ function grantRequestBody(input, profile) {
|
|
|
125
220
|
approved_result_max_bytes: profile.max_result_bytes,
|
|
126
221
|
};
|
|
127
222
|
}
|
|
223
|
+
function v4GrantRequestBody(input, profile, detail, clientRequestId) {
|
|
224
|
+
const base = grantRequestBody(input);
|
|
225
|
+
const requestId = `req_${createHash("sha256")
|
|
226
|
+
.update(clientRequestId, "utf8")
|
|
227
|
+
.digest("base64url")
|
|
228
|
+
.slice(0, 22)}`;
|
|
229
|
+
return {
|
|
230
|
+
...base,
|
|
231
|
+
profile,
|
|
232
|
+
detail,
|
|
233
|
+
request_id: requestId,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
128
236
|
function requireV3GrantTuple(parsed, profile, detail) {
|
|
129
237
|
if (parsed.protocol_version !== profile.grant_protocol ||
|
|
130
238
|
parsed.stream_protocol_version !== profile.stream_protocol ||
|
|
@@ -134,6 +242,56 @@ function requireV3GrantTuple(parsed, profile, detail) {
|
|
|
134
242
|
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned a parse grant outside the negotiated representation.", false);
|
|
135
243
|
}
|
|
136
244
|
}
|
|
245
|
+
function parseSignedGrantV4(parseGrant) {
|
|
246
|
+
const segments = parseGrant.split(".");
|
|
247
|
+
if (segments.length !== 3 ||
|
|
248
|
+
segments.some((segment) => segment.length === 0 || !/^[A-Za-z0-9_-]+$/u.test(segment))) {
|
|
249
|
+
throw new Error("invalid compact JWT");
|
|
250
|
+
}
|
|
251
|
+
const payload = segments[1];
|
|
252
|
+
const decoded = Buffer.from(payload, "base64url");
|
|
253
|
+
if (decoded.length === 0 || decoded.toString("base64url") !== payload) {
|
|
254
|
+
throw new Error("noncanonical JWT payload");
|
|
255
|
+
}
|
|
256
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(decoded));
|
|
257
|
+
return signedGrantV4Schema.parse(value);
|
|
258
|
+
}
|
|
259
|
+
function requireV4GrantBinding(parsed, profile, detail, body) {
|
|
260
|
+
let claims;
|
|
261
|
+
try {
|
|
262
|
+
claims = parseSignedGrantV4(parsed.parse_grant);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned a parse grant outside the negotiated representation.", false);
|
|
266
|
+
}
|
|
267
|
+
const expiresAt = Date.parse(parsed.expires_at);
|
|
268
|
+
const expiryMatches = expiresAt % 1000 === 0 && claims.exp === expiresAt / 1000;
|
|
269
|
+
if (claims.jti !== parsed.grant_id ||
|
|
270
|
+
claims.grant_id !== parsed.grant_id ||
|
|
271
|
+
claims.operation_id !== parsed.operation_id ||
|
|
272
|
+
claims.request_id !== body.request_id ||
|
|
273
|
+
claims.protocol_version !== profile.grant_protocol ||
|
|
274
|
+
claims.profile !== profile.profile ||
|
|
275
|
+
claims.detail !== detail ||
|
|
276
|
+
claims.approved_result_max_bytes !== profile.max_result_bytes ||
|
|
277
|
+
claims.stream_protocol !== profile.stream_protocol ||
|
|
278
|
+
claims.operation_protocol !== profile.operation_protocol ||
|
|
279
|
+
claims.settlement_protocol !== profile.settlement_protocol ||
|
|
280
|
+
claims.release_protocol !== profile.release_protocol ||
|
|
281
|
+
claims.settlement_journal_protocol !== profile.settlement_journal_protocol ||
|
|
282
|
+
claims.usage_schema_version !== profile.usage_protocol ||
|
|
283
|
+
claims.billing_contract_version !== profile.billing_protocol ||
|
|
284
|
+
claims.bridge_protocol !== profile.bridge_protocol ||
|
|
285
|
+
claims.content_length !== body.content_length ||
|
|
286
|
+
claims.content_type !== body.content_type ||
|
|
287
|
+
claims.file_extension !== body.file_extension ||
|
|
288
|
+
claims.no_store !== body.no_store ||
|
|
289
|
+
claims.output !== body.output ||
|
|
290
|
+
claims.max_bytes !== MAX_FILE_BYTES ||
|
|
291
|
+
!expiryMatches) {
|
|
292
|
+
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned a parse grant outside the negotiated representation.", false);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
137
295
|
export function grantRequestHash(body) {
|
|
138
296
|
return `sha256:${createHash("sha256")
|
|
139
297
|
.update(JSON.stringify(body), "utf8")
|
|
@@ -144,24 +302,38 @@ export function createClientRequestId() {
|
|
|
144
302
|
}
|
|
145
303
|
function responseError(status) {
|
|
146
304
|
if (status === 401) {
|
|
147
|
-
return bridgeError("INVALID_CUE_API_KEY", "The Cue API Key is invalid or expired.", false);
|
|
305
|
+
return bridgeError("INVALID_CUE_API_KEY", "The Cue API Key is invalid or expired.", false, { failureScope: "authentication", sourceKind: "local" });
|
|
148
306
|
}
|
|
149
307
|
if (status === 403) {
|
|
150
|
-
return bridgeError("OMNI_NOT_ENTITLED", "This Cue account is not entitled to use Omni Reader.", false);
|
|
308
|
+
return bridgeError("OMNI_NOT_ENTITLED", "This Cue account is not entitled to use Omni Reader.", false, { failureScope: "authentication", sourceKind: "local" });
|
|
151
309
|
}
|
|
152
310
|
if (status === 402) {
|
|
153
|
-
return bridgeError("INSUFFICIENT_BALANCE", "The Cue account does not have enough balance to start this parse.", false);
|
|
311
|
+
return bridgeError("INSUFFICIENT_BALANCE", "The Cue account does not have enough balance to start this parse.", false, { failureScope: "billing", sourceKind: "local" });
|
|
154
312
|
}
|
|
155
313
|
if (status === 409) {
|
|
156
|
-
return bridgeError("IDEMPOTENCY_COLLISION", "This grant request identifier is already bound to different metadata.", false);
|
|
314
|
+
return bridgeError("IDEMPOTENCY_COLLISION", "This grant request identifier is already bound to different metadata.", false, { failureScope: "operation", sourceKind: "local" });
|
|
157
315
|
}
|
|
158
316
|
if (status === 404) {
|
|
159
|
-
return bridgeError("
|
|
317
|
+
return bridgeError("DIRECT_UPLOAD_UNAVAILABLE", "The Omni service's direct-upload route is unavailable.", false, {
|
|
318
|
+
failureScope: "local_capability",
|
|
319
|
+
sourceKind: "local",
|
|
320
|
+
userAction: "Do not infer an account restriction; HTTP 403 is the entitlement signal. Retry only after the service route is enabled, or parse an existing HTTP(S) source instead.",
|
|
321
|
+
});
|
|
160
322
|
}
|
|
161
323
|
if (status === 429) {
|
|
162
|
-
return bridgeError("CUBE_BUSY", "Cube is temporarily busy. Retry this same grant request later.", true);
|
|
324
|
+
return bridgeError("CUBE_BUSY", "Cube is temporarily busy. Retry this same grant request later.", true, { failureScope: "service", sourceKind: "local" });
|
|
163
325
|
}
|
|
164
|
-
return bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", status >= 500);
|
|
326
|
+
return bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", status >= 500, { failureScope: "service", sourceKind: "local" });
|
|
327
|
+
}
|
|
328
|
+
function capabilitiesResponseError(status) {
|
|
329
|
+
if (status === 404) {
|
|
330
|
+
return bridgeError("DETAIL_CAPABILITIES_UNAVAILABLE", "This Omni service does not advertise grounded or layout parsing.", false, {
|
|
331
|
+
failureScope: "local_capability",
|
|
332
|
+
sourceKind: "local",
|
|
333
|
+
userAction: "Use text output only when plain Markdown is acceptable; text may retain headings, lists, and tables but does not include grounding or layout sidecars.",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return responseError(status);
|
|
165
337
|
}
|
|
166
338
|
function retryAfterSeconds(response) {
|
|
167
339
|
const value = response.headers.get("retry-after");
|
|
@@ -171,6 +343,19 @@ function retryAfterSeconds(response) {
|
|
|
171
343
|
return Number.isSafeInteger(seconds) && seconds > 0 ? seconds : undefined;
|
|
172
344
|
}
|
|
173
345
|
async function grantResponseError(response) {
|
|
346
|
+
if (response.status === 422 && !response.headers.has("retry-after")) {
|
|
347
|
+
let body;
|
|
348
|
+
try {
|
|
349
|
+
body = await response.json();
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return responseError(response.status);
|
|
353
|
+
}
|
|
354
|
+
if (unsupportedDetailSchema.safeParse(body).success) {
|
|
355
|
+
return unsupportedDetailError("local");
|
|
356
|
+
}
|
|
357
|
+
return responseError(response.status);
|
|
358
|
+
}
|
|
174
359
|
// The strict 426 marker is only recognized when there is exactly a 426 status,
|
|
175
360
|
// no Retry-After, and a body matching the six frozen literal fields. Any drift
|
|
176
361
|
// degrades through `responseError` to the generic nonretryable CUBE_UNAVAILABLE.
|
|
@@ -185,7 +370,8 @@ async function grantResponseError(response) {
|
|
|
185
370
|
if (bridgeUpgradeRequiredSchema.safeParse(body).success) {
|
|
186
371
|
return bridgeError("BRIDGE_UPGRADE_REQUIRED", "This Bridge release is not accepted for direct local-file parsing.", false, {
|
|
187
372
|
failureScope: "service",
|
|
188
|
-
|
|
373
|
+
sourceKind: "local",
|
|
374
|
+
userAction: "Install the latest published @cueai/omni-reader-mcp release using the official Omni setup instructions, then retry once. If you are already running the latest published release, do not reinstall or retry; run doctor --json and ask the service operator to verify Bridge admission.",
|
|
189
375
|
});
|
|
190
376
|
}
|
|
191
377
|
}
|
|
@@ -201,6 +387,7 @@ async function grantResponseError(response) {
|
|
|
201
387
|
if (principalConcurrencyErrorSchema.safeParse(body).success) {
|
|
202
388
|
return bridgeError("PRINCIPAL_CONCURRENCY_LIMIT", "The maximum number of concurrent parsing operations is already active.", true, {
|
|
203
389
|
failureScope: "service",
|
|
390
|
+
sourceKind: "local",
|
|
204
391
|
userAction: "Wait for retry_after seconds, then retry the same local-file parse.",
|
|
205
392
|
retryAfter,
|
|
206
393
|
});
|
|
@@ -212,9 +399,11 @@ export class CubeGrantClient {
|
|
|
212
399
|
#journal;
|
|
213
400
|
#apiKey;
|
|
214
401
|
#grantUrl;
|
|
215
|
-
#
|
|
402
|
+
#capabilitiesV1Url;
|
|
403
|
+
#capabilitiesV2Url;
|
|
216
404
|
#fetch;
|
|
217
|
-
#
|
|
405
|
+
#capabilitiesV1Cache;
|
|
406
|
+
#capabilitiesV2Cache;
|
|
218
407
|
constructor(options) {
|
|
219
408
|
this.#journal = options.journal;
|
|
220
409
|
this.#apiKey =
|
|
@@ -229,7 +418,8 @@ export class CubeGrantClient {
|
|
|
229
418
|
throw bridgeError("INSECURE_CUBE_BASE_URL", "The Cube control endpoint must use HTTPS without embedded credentials.", false);
|
|
230
419
|
}
|
|
231
420
|
this.#grantUrl = new URL(GRANT_PATH, baseUrl).toString();
|
|
232
|
-
this.#
|
|
421
|
+
this.#capabilitiesV1Url = new URL(CUBE_CAPABILITIES_PATH, baseUrl).toString();
|
|
422
|
+
this.#capabilitiesV2Url = new URL(CAPABILITIES_V2_PATH, baseUrl).toString();
|
|
233
423
|
this.#fetch = options.fetchImpl ?? fetch;
|
|
234
424
|
}
|
|
235
425
|
// Preflight for any non-text grant request (D2-D item 4): authenticated
|
|
@@ -242,13 +432,13 @@ export class CubeGrantClient {
|
|
|
242
432
|
throw bridgeError("MISSING_CUE_API_KEY", "Set CUE_API_KEY before using local Omni document parsing.", false);
|
|
243
433
|
}
|
|
244
434
|
const now = Date.now();
|
|
245
|
-
const cached = this.#
|
|
435
|
+
const cached = this.#capabilitiesV1Cache;
|
|
246
436
|
if (cached !== undefined && cached.expiresAt > now) {
|
|
247
437
|
return cached.value;
|
|
248
438
|
}
|
|
249
439
|
let response;
|
|
250
440
|
try {
|
|
251
|
-
response = await this.#fetch(this.#
|
|
441
|
+
response = await this.#fetch(this.#capabilitiesV1Url, {
|
|
252
442
|
method: "GET",
|
|
253
443
|
headers: {
|
|
254
444
|
authorization: `Bearer ${this.#apiKey}`,
|
|
@@ -264,7 +454,7 @@ export class CubeGrantClient {
|
|
|
264
454
|
throw bridgeError("CUBE_UNAVAILABLE", "Cube could not provide reader capabilities. Retry the same request later.", true);
|
|
265
455
|
}
|
|
266
456
|
if (!response.ok) {
|
|
267
|
-
throw
|
|
457
|
+
throw capabilitiesResponseError(response.status);
|
|
268
458
|
}
|
|
269
459
|
let capabilities;
|
|
270
460
|
try {
|
|
@@ -273,7 +463,57 @@ export class CubeGrantClient {
|
|
|
273
463
|
catch {
|
|
274
464
|
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned invalid reader capability profiles.", false);
|
|
275
465
|
}
|
|
276
|
-
this.#
|
|
466
|
+
this.#capabilitiesV1Cache = {
|
|
467
|
+
expiresAt: capabilities.expires_at.getTime(),
|
|
468
|
+
value: capabilities,
|
|
469
|
+
};
|
|
470
|
+
return capabilities;
|
|
471
|
+
}
|
|
472
|
+
async getCapabilitiesV2(signal) {
|
|
473
|
+
if (this.#apiKey.length === 0) {
|
|
474
|
+
throw bridgeError("MISSING_CUE_API_KEY", "Set CUE_API_KEY before using local Omni document parsing.", false);
|
|
475
|
+
}
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
const cached = this.#capabilitiesV2Cache;
|
|
478
|
+
if (cached !== undefined && cached.expiresAt > now) {
|
|
479
|
+
return cached.value;
|
|
480
|
+
}
|
|
481
|
+
let response;
|
|
482
|
+
try {
|
|
483
|
+
response = await this.#fetch(this.#capabilitiesV2Url, {
|
|
484
|
+
method: "GET",
|
|
485
|
+
headers: {
|
|
486
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
487
|
+
accept: "application/json",
|
|
488
|
+
},
|
|
489
|
+
signal,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
if (signal?.aborted) {
|
|
494
|
+
throw bridgeError("CAPABILITIES_REQUEST_CANCELED", "The capabilities request was canceled.", false);
|
|
495
|
+
}
|
|
496
|
+
throw bridgeError("CUBE_UNAVAILABLE", "Cube could not provide reader capabilities. Retry the same request later.", true);
|
|
497
|
+
}
|
|
498
|
+
if (response.status === 404) {
|
|
499
|
+
try {
|
|
500
|
+
await response.body?.cancel();
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
throw bridgeError("CUBE_UNAVAILABLE", "Cube could not release the capabilities response. Retry the same request later.", true);
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
if (!response.ok)
|
|
508
|
+
throw responseError(response.status);
|
|
509
|
+
let capabilities;
|
|
510
|
+
try {
|
|
511
|
+
capabilities = parseReaderCapabilitiesV2(await response.json(), new Date(now));
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned invalid reader capability profiles.", false);
|
|
515
|
+
}
|
|
516
|
+
this.#capabilitiesV2Cache = {
|
|
277
517
|
expiresAt: capabilities.expires_at.getTime(),
|
|
278
518
|
value: capabilities,
|
|
279
519
|
};
|
|
@@ -285,23 +525,27 @@ export class CubeGrantClient {
|
|
|
285
525
|
// UNSUPPORTED_DETAIL before any grant request is constructed.
|
|
286
526
|
async #directProfileFor(detail, signal) {
|
|
287
527
|
const capabilities = await this.getCapabilities(signal);
|
|
288
|
-
return selectDirectProfile(capabilities, detail);
|
|
528
|
+
return selectDirectProfile(capabilities, detail, "local");
|
|
289
529
|
}
|
|
290
530
|
async createGrant(input, clientRequestId, signal, options = {}) {
|
|
291
531
|
if (this.#apiKey.length === 0) {
|
|
292
532
|
throw bridgeError("MISSING_CUE_API_KEY", "Set CUE_API_KEY before using local Omni document parsing.", false);
|
|
293
533
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
534
|
+
const requestedDetail = input.detail ?? "text";
|
|
535
|
+
const capabilitiesV2 = await this.getCapabilitiesV2(signal);
|
|
536
|
+
const profileV2 = capabilitiesV2 === null
|
|
537
|
+
? undefined
|
|
538
|
+
: selectDirectProfileV2(capabilitiesV2, requestedDetail, "local");
|
|
539
|
+
const legacyDetail = input.detail === "grounded" || input.detail === "layout"
|
|
299
540
|
? input.detail
|
|
300
541
|
: undefined;
|
|
301
|
-
const
|
|
302
|
-
?
|
|
303
|
-
:
|
|
304
|
-
const
|
|
542
|
+
const profileV1 = capabilitiesV2 === null && legacyDetail !== undefined
|
|
543
|
+
? await this.#directProfileFor(legacyDetail, signal)
|
|
544
|
+
: undefined;
|
|
545
|
+
const directProfile = profileV2?.profile ?? profileV1?.profile ?? null;
|
|
546
|
+
const body = profileV2 === undefined
|
|
547
|
+
? grantRequestBody(input, profileV1)
|
|
548
|
+
: v4GrantRequestBody(input, profileV2, requestedDetail, clientRequestId);
|
|
305
549
|
const serializedBody = JSON.stringify(body);
|
|
306
550
|
const requestHash = grantRequestHash(body);
|
|
307
551
|
if (options.journal !== false) {
|
|
@@ -329,18 +573,26 @@ export class CubeGrantClient {
|
|
|
329
573
|
if (!response.ok) {
|
|
330
574
|
throw await grantResponseError(response);
|
|
331
575
|
}
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
// confirmed by the server.
|
|
576
|
+
// Profiled responses are accepted only for the exact negotiated grant
|
|
577
|
+
// generation. Legacy response meanings remain frozen.
|
|
335
578
|
let parsed;
|
|
336
|
-
if (
|
|
579
|
+
if (profileV2 !== undefined) {
|
|
580
|
+
try {
|
|
581
|
+
parsed = grantResponseV4Schema.parse(await response.json());
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned a parse grant outside the negotiated representation.", false);
|
|
585
|
+
}
|
|
586
|
+
requireV4GrantBinding(parsed, profileV2, requestedDetail, body);
|
|
587
|
+
}
|
|
588
|
+
else if (profileV1 !== undefined) {
|
|
337
589
|
try {
|
|
338
590
|
parsed = grantResponseV3Schema.parse(await response.json());
|
|
339
591
|
}
|
|
340
592
|
catch {
|
|
341
593
|
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned a parse grant outside the negotiated representation.", false);
|
|
342
594
|
}
|
|
343
|
-
requireV3GrantTuple(parsed,
|
|
595
|
+
requireV3GrantTuple(parsed, profileV1, legacyDetail);
|
|
344
596
|
}
|
|
345
597
|
else {
|
|
346
598
|
try {
|
|
@@ -369,6 +621,8 @@ export class CubeGrantClient {
|
|
|
369
621
|
expiresAt: parsed.expires_at,
|
|
370
622
|
maxBytes: parsed.max_bytes,
|
|
371
623
|
protocolVersion: parsed.protocol_version,
|
|
624
|
+
directProfile,
|
|
625
|
+
requestedDetail,
|
|
372
626
|
};
|
|
373
627
|
}
|
|
374
628
|
}
|
package/dist/errors.d.ts
CHANGED
package/dist/errors.js
CHANGED
|
@@ -49,3 +49,18 @@ export class OmniBridgeError extends Error {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
export function unsupportedDetailError(sourceKind) {
|
|
53
|
+
return new OmniBridgeError({
|
|
54
|
+
code: "UNSUPPORTED_DETAIL",
|
|
55
|
+
message: "The requested parsing representation is not supported by this control plane.",
|
|
56
|
+
failureScope: "local_capability",
|
|
57
|
+
...(sourceKind === undefined ? {} : { sourceKind }),
|
|
58
|
+
userAction: "Use text output only when Markdown without grounding/layout sidecars is acceptable; otherwise do not retry unchanged.",
|
|
59
|
+
operationCreated: false,
|
|
60
|
+
fileUploaded: false,
|
|
61
|
+
parserStarted: false,
|
|
62
|
+
billed: false,
|
|
63
|
+
contentReleased: false,
|
|
64
|
+
retryable: false,
|
|
65
|
+
});
|
|
66
|
+
}
|
package/dist/iiis-client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DirectProfile, RequestedDirectDetailV2 } from "./capabilities.js";
|
|
1
2
|
import type { OpenedAllowedFile } from "./path-security.js";
|
|
2
3
|
import { type ProgressSink } from "./progress.js";
|
|
3
4
|
export interface ResultRetentionStart {
|
|
@@ -22,14 +23,22 @@ export interface IiisOperationInput {
|
|
|
22
23
|
readonly operationToken: string;
|
|
23
24
|
readonly uploadUrl?: string;
|
|
24
25
|
readonly expiresAt?: string;
|
|
26
|
+
readonly directProfile?: DirectProfile | null;
|
|
27
|
+
readonly expectedDetail?: RequestedDirectDetailV2;
|
|
25
28
|
readonly openedFile?: OpenedAllowedFile;
|
|
26
29
|
readonly retention: ResultRetentionSink;
|
|
27
30
|
readonly progress?: ProgressSink;
|
|
28
31
|
readonly signal?: AbortSignal;
|
|
29
32
|
}
|
|
33
|
+
export interface WireBillingFacts {
|
|
34
|
+
readonly credits_charged: number;
|
|
35
|
+
readonly credits_remaining: number;
|
|
36
|
+
}
|
|
30
37
|
export interface ReleasedResult extends ReleasedMetadata {
|
|
38
|
+
readonly directProfile: DirectProfile | null;
|
|
39
|
+
readonly billing: WireBillingFacts | null;
|
|
31
40
|
}
|
|
32
|
-
export type IiisOperationStatus = "ISSUED" | "CLAIMED" | "UPLOADING" | "PROCESSING" | "SETTLING" | "RELEASED" | "EXPIRED" | "SETTLEMENT_DENIED" | "FAILED" | "CANCELED" | "DELIVERY_EXPIRED" | "DELIVERED";
|
|
41
|
+
export type IiisOperationStatus = "ISSUED" | "CLAIMED" | "UPLOADING" | "PROCESSING" | "SETTLING" | "RELEASED" | "EXPIRED" | "SETTLEMENT_DENIED" | "FAILED" | "UNSUPPORTED" | "CANCELED" | "DELIVERY_EXPIRED" | "DELIVERED";
|
|
33
42
|
export interface IiisOperationSnapshot {
|
|
34
43
|
readonly status: IiisOperationStatus;
|
|
35
44
|
readonly parserStarted: boolean;
|
|
@@ -38,6 +47,8 @@ export interface IiisOperationSnapshot {
|
|
|
38
47
|
readonly contentReleased: boolean;
|
|
39
48
|
readonly retryable: boolean;
|
|
40
49
|
readonly expiresAt: string | null;
|
|
50
|
+
readonly directProfile: DirectProfile | null;
|
|
51
|
+
readonly billing: WireBillingFacts | null;
|
|
41
52
|
}
|
|
42
53
|
export interface IiisClientOptions {
|
|
43
54
|
readonly fetchImpl?: typeof fetch;
|
|
@@ -45,6 +56,25 @@ export interface IiisClientOptions {
|
|
|
45
56
|
readonly maxPolls?: number;
|
|
46
57
|
readonly operationBaseUrl?: string;
|
|
47
58
|
}
|
|
59
|
+
interface ResultIdentity {
|
|
60
|
+
readonly digest: string;
|
|
61
|
+
readonly bytes: number;
|
|
62
|
+
readonly mediaType: string;
|
|
63
|
+
}
|
|
64
|
+
interface OperationStatus {
|
|
65
|
+
readonly protocol_version: string;
|
|
66
|
+
readonly operation_id: string;
|
|
67
|
+
readonly status: IiisOperationStatus;
|
|
68
|
+
readonly parser_started: boolean;
|
|
69
|
+
readonly file_uploaded: boolean;
|
|
70
|
+
readonly billed: boolean;
|
|
71
|
+
readonly content_released: boolean;
|
|
72
|
+
readonly retryable: boolean;
|
|
73
|
+
readonly expires_at: string | null;
|
|
74
|
+
readonly directProfile: DirectProfile | null;
|
|
75
|
+
readonly billing: WireBillingFacts | null;
|
|
76
|
+
readonly result: ResultIdentity | null;
|
|
77
|
+
}
|
|
48
78
|
export declare class IiisClient {
|
|
49
79
|
#private;
|
|
50
80
|
constructor(options?: IiisClientOptions);
|
|
@@ -52,6 +82,7 @@ export declare class IiisClient {
|
|
|
52
82
|
recoverAndWait(input: IiisOperationInput): Promise<ReleasedResult>;
|
|
53
83
|
inspectOperation(input: IiisOperationInput): Promise<IiisOperationSnapshot>;
|
|
54
84
|
cancelOperation(input: IiisOperationInput): Promise<IiisOperationSnapshot>;
|
|
55
|
-
downloadResult(input: IiisOperationInput, progress?: ProgressSink): Promise<ReleasedResult>;
|
|
85
|
+
downloadResult(input: IiisOperationInput, progress?: ProgressSink, settledStatus?: OperationStatus): Promise<ReleasedResult>;
|
|
56
86
|
ack(input: IiisOperationInput): Promise<void>;
|
|
57
87
|
}
|
|
88
|
+
export {};
|