@cueai/omni-reader-mcp 1.0.2 → 1.1.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 +115 -26
- package/dist/artifact-store.d.ts +11 -0
- package/dist/artifact-store.js +94 -48
- package/dist/cli/agent-config.d.ts +29 -4
- package/dist/cli/agent-config.js +910 -107
- package/dist/cli/arguments.d.ts +32 -0
- package/dist/cli/arguments.js +120 -0
- package/dist/cli/doctor.d.ts +42 -1
- package/dist/cli/doctor.js +109 -36
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.js +103 -18
- package/dist/cli/uninstall.d.ts +6 -0
- package/dist/cli/uninstall.js +37 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/cube-client.d.ts +5 -3
- package/dist/cube-client.js +16 -11
- package/dist/cursor.js +2 -0
- package/dist/errors.d.ts +32 -1
- package/dist/errors.js +26 -1
- package/dist/iiis-client.d.ts +18 -4
- package/dist/iiis-client.js +194 -40
- package/dist/index.d.ts +3 -0
- package/dist/index.js +93 -32
- package/dist/multipart-body.js +2 -0
- package/dist/onboarding-policy.d.ts +10 -0
- package/dist/onboarding-policy.js +58 -0
- package/dist/operation-journal.d.ts +50 -1
- package/dist/operation-journal.js +473 -114
- package/dist/operation-manager.d.ts +75 -0
- package/dist/operation-manager.js +1324 -0
- package/dist/path-security.d.ts +1 -0
- package/dist/path-security.js +26 -6
- package/dist/progress.d.ts +6 -1
- package/dist/protocol.d.ts +26 -13
- package/dist/protocol.js +34 -10
- package/dist/remote-client.d.ts +17 -0
- package/dist/remote-client.js +233 -0
- package/dist/result-contract.d.ts +199 -0
- package/dist/result-contract.js +235 -0
- package/dist/server.js +21 -4
- package/dist/source.d.ts +8 -0
- package/dist/source.js +37 -0
- package/dist/task-runtime.d.ts +13 -0
- package/dist/task-runtime.js +94 -0
- package/dist/tools.d.ts +19 -1
- package/dist/tools.js +317 -112
- package/package.json +3 -3
package/dist/cube-client.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES,
|
|
3
|
+
import { BRIDGE_RELEASE_VERSION, CUBE_GRANT_PROTOCOL_VERSION, DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES, } from "./constants.js";
|
|
4
4
|
import { OmniBridgeError } from "./errors.js";
|
|
5
5
|
const GRANT_PATH = "/api/omni-reader/direct-upload/v1/parse-grants";
|
|
6
6
|
const BRIDGE_PACKAGE = "@cueai/omni-reader-mcp";
|
|
7
|
-
const BRIDGE_VERSION = "1.0.2";
|
|
8
7
|
const grantResponseSchema = z
|
|
9
8
|
.object({
|
|
10
9
|
grant_id: z.string().min(1),
|
|
@@ -24,7 +23,9 @@ function bridgeError(code, message, retryable) {
|
|
|
24
23
|
return new OmniBridgeError({
|
|
25
24
|
code,
|
|
26
25
|
message,
|
|
26
|
+
operationCreated: false,
|
|
27
27
|
fileUploaded: false,
|
|
28
|
+
parserStarted: false,
|
|
28
29
|
billed: false,
|
|
29
30
|
contentReleased: false,
|
|
30
31
|
retryable,
|
|
@@ -48,7 +49,7 @@ function grantRequestBody(input) {
|
|
|
48
49
|
output: input.output,
|
|
49
50
|
bridge: {
|
|
50
51
|
package: BRIDGE_PACKAGE,
|
|
51
|
-
version:
|
|
52
|
+
version: BRIDGE_RELEASE_VERSION,
|
|
52
53
|
},
|
|
53
54
|
};
|
|
54
55
|
}
|
|
@@ -102,14 +103,16 @@ export class CubeGrantClient {
|
|
|
102
103
|
this.#grantUrl = new URL(GRANT_PATH, baseUrl).toString();
|
|
103
104
|
this.#fetch = options.fetchImpl ?? fetch;
|
|
104
105
|
}
|
|
105
|
-
async createGrant(input, clientRequestId, signal) {
|
|
106
|
+
async createGrant(input, clientRequestId, signal, options = {}) {
|
|
106
107
|
if (this.#apiKey.length === 0) {
|
|
107
108
|
throw bridgeError("MISSING_CUE_API_KEY", "Set CUE_API_KEY before using local Omni document parsing.", false);
|
|
108
109
|
}
|
|
109
110
|
const body = grantRequestBody(input);
|
|
110
111
|
const serializedBody = JSON.stringify(body);
|
|
111
112
|
const requestHash = grantRequestHash(body);
|
|
112
|
-
|
|
113
|
+
if (options.journal !== false) {
|
|
114
|
+
await this.#journal.begin(clientRequestId, requestHash);
|
|
115
|
+
}
|
|
113
116
|
let response;
|
|
114
117
|
try {
|
|
115
118
|
response = await this.#fetch(this.#grantUrl, {
|
|
@@ -139,12 +142,14 @@ export class CubeGrantClient {
|
|
|
139
142
|
catch {
|
|
140
143
|
throw bridgeError("CUBE_PROTOCOL_ERROR", "Cube returned an invalid parse grant response.", false);
|
|
141
144
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
if (options.journal !== false) {
|
|
146
|
+
await this.#journal.markGrantIssued(clientRequestId, {
|
|
147
|
+
operationId: parsed.operation_id,
|
|
148
|
+
operationToken: parsed.operation_token,
|
|
149
|
+
uploadUrl: parsed.upload_url,
|
|
150
|
+
expiresAt: parsed.expires_at,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
148
153
|
return {
|
|
149
154
|
clientRequestId,
|
|
150
155
|
requestHash,
|
package/dist/cursor.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -1,25 +1,56 @@
|
|
|
1
|
+
export type OmniFailureScope = "source" | "local_capability" | "authentication" | "billing" | "service" | "parser" | "operation" | "cleanup" | "bridge";
|
|
2
|
+
export type OmniSourceKind = "local" | "url";
|
|
3
|
+
export interface OmniErrorConstraints {
|
|
4
|
+
readonly max_bytes?: number;
|
|
5
|
+
readonly supported_extensions?: string[];
|
|
6
|
+
}
|
|
1
7
|
export interface OmniBridgeErrorInit {
|
|
2
8
|
code: string;
|
|
3
9
|
message: string;
|
|
10
|
+
failureScope?: OmniFailureScope;
|
|
11
|
+
sourceKind?: OmniSourceKind;
|
|
12
|
+
userAction?: string;
|
|
13
|
+
requestId?: string;
|
|
14
|
+
operationCreated: boolean;
|
|
4
15
|
fileUploaded: boolean;
|
|
16
|
+
parserStarted: boolean;
|
|
5
17
|
billed: boolean;
|
|
6
18
|
contentReleased: boolean;
|
|
7
19
|
retryable: boolean;
|
|
20
|
+
retryAfter?: number;
|
|
21
|
+
constraints?: OmniErrorConstraints;
|
|
8
22
|
}
|
|
9
23
|
export interface OmniBridgeErrorPayload {
|
|
24
|
+
ok: false;
|
|
10
25
|
code: string;
|
|
26
|
+
failure_scope?: OmniFailureScope;
|
|
27
|
+
source_kind?: OmniSourceKind;
|
|
28
|
+
retryable: boolean;
|
|
11
29
|
message: string;
|
|
30
|
+
user_action?: string;
|
|
31
|
+
request_id?: string;
|
|
32
|
+
operation_created: boolean;
|
|
12
33
|
file_uploaded: boolean;
|
|
34
|
+
parser_started: boolean;
|
|
13
35
|
billed: boolean;
|
|
14
36
|
content_released: boolean;
|
|
15
|
-
|
|
37
|
+
retry_after?: number;
|
|
38
|
+
constraints?: OmniErrorConstraints;
|
|
16
39
|
}
|
|
17
40
|
export declare class OmniBridgeError extends Error {
|
|
18
41
|
readonly code: string;
|
|
42
|
+
readonly failureScope?: OmniFailureScope;
|
|
43
|
+
readonly sourceKind?: OmniSourceKind;
|
|
44
|
+
readonly userAction?: string;
|
|
45
|
+
readonly requestId?: string;
|
|
46
|
+
readonly operationCreated: boolean;
|
|
19
47
|
readonly fileUploaded: boolean;
|
|
48
|
+
readonly parserStarted: boolean;
|
|
20
49
|
readonly billed: boolean;
|
|
21
50
|
readonly contentReleased: boolean;
|
|
22
51
|
readonly retryable: boolean;
|
|
52
|
+
readonly retryAfter?: number;
|
|
53
|
+
readonly constraints?: OmniErrorConstraints;
|
|
23
54
|
constructor(init: OmniBridgeErrorInit);
|
|
24
55
|
toJSON(): OmniBridgeErrorPayload;
|
|
25
56
|
}
|
package/dist/errors.js
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
export class OmniBridgeError extends Error {
|
|
2
2
|
code;
|
|
3
|
+
failureScope;
|
|
4
|
+
sourceKind;
|
|
5
|
+
userAction;
|
|
6
|
+
requestId;
|
|
7
|
+
operationCreated;
|
|
3
8
|
fileUploaded;
|
|
9
|
+
parserStarted;
|
|
4
10
|
billed;
|
|
5
11
|
contentReleased;
|
|
6
12
|
retryable;
|
|
13
|
+
retryAfter;
|
|
14
|
+
constraints;
|
|
7
15
|
constructor(init) {
|
|
8
16
|
super(init.message);
|
|
9
17
|
this.name = "OmniBridgeError";
|
|
10
18
|
this.code = init.code;
|
|
19
|
+
this.failureScope = init.failureScope;
|
|
20
|
+
this.sourceKind = init.sourceKind;
|
|
21
|
+
this.userAction = init.userAction;
|
|
22
|
+
this.requestId = init.requestId;
|
|
23
|
+
this.operationCreated = init.operationCreated;
|
|
11
24
|
this.fileUploaded = init.fileUploaded;
|
|
25
|
+
this.parserStarted = init.parserStarted;
|
|
12
26
|
this.billed = init.billed;
|
|
13
27
|
this.contentReleased = init.contentReleased;
|
|
14
28
|
this.retryable = init.retryable;
|
|
29
|
+
this.retryAfter = init.retryAfter;
|
|
30
|
+
this.constraints = init.constraints;
|
|
15
31
|
}
|
|
16
32
|
toJSON() {
|
|
17
33
|
return {
|
|
34
|
+
ok: false,
|
|
18
35
|
code: this.code,
|
|
36
|
+
...(this.failureScope === undefined ? {} : { failure_scope: this.failureScope }),
|
|
37
|
+
...(this.sourceKind === undefined ? {} : { source_kind: this.sourceKind }),
|
|
38
|
+
retryable: this.retryable,
|
|
19
39
|
message: this.message,
|
|
40
|
+
...(this.userAction === undefined ? {} : { user_action: this.userAction }),
|
|
41
|
+
...(this.requestId === undefined ? {} : { request_id: this.requestId }),
|
|
42
|
+
operation_created: this.operationCreated,
|
|
20
43
|
file_uploaded: this.fileUploaded,
|
|
44
|
+
parser_started: this.parserStarted,
|
|
21
45
|
billed: this.billed,
|
|
22
46
|
content_released: this.contentReleased,
|
|
23
|
-
|
|
47
|
+
...(this.retryAfter === undefined ? {} : { retry_after: this.retryAfter }),
|
|
48
|
+
...(this.constraints === undefined ? {} : { constraints: this.constraints }),
|
|
24
49
|
};
|
|
25
50
|
}
|
|
26
51
|
}
|
package/dist/iiis-client.d.ts
CHANGED
|
@@ -17,27 +17,41 @@ export interface ResultRetentionSink {
|
|
|
17
17
|
abort(): Promise<void>;
|
|
18
18
|
}
|
|
19
19
|
export interface IiisOperationInput {
|
|
20
|
-
readonly parseGrant
|
|
20
|
+
readonly parseGrant?: string;
|
|
21
21
|
readonly operationId: string;
|
|
22
22
|
readonly operationToken: string;
|
|
23
|
-
readonly uploadUrl
|
|
24
|
-
readonly expiresAt
|
|
25
|
-
readonly openedFile
|
|
23
|
+
readonly uploadUrl?: string;
|
|
24
|
+
readonly expiresAt?: string;
|
|
25
|
+
readonly openedFile?: OpenedAllowedFile;
|
|
26
26
|
readonly retention: ResultRetentionSink;
|
|
27
27
|
readonly progress?: ProgressSink;
|
|
28
28
|
readonly signal?: AbortSignal;
|
|
29
29
|
}
|
|
30
30
|
export interface ReleasedResult extends ReleasedMetadata {
|
|
31
31
|
}
|
|
32
|
+
export type IiisOperationStatus = "ISSUED" | "CLAIMED" | "UPLOADING" | "PROCESSING" | "SETTLING" | "RELEASED" | "EXPIRED" | "SETTLEMENT_DENIED" | "FAILED" | "CANCELED" | "DELIVERY_EXPIRED" | "DELIVERED";
|
|
33
|
+
export interface IiisOperationSnapshot {
|
|
34
|
+
readonly status: IiisOperationStatus;
|
|
35
|
+
readonly parserStarted: boolean;
|
|
36
|
+
readonly fileUploaded: boolean;
|
|
37
|
+
readonly billed: boolean;
|
|
38
|
+
readonly contentReleased: boolean;
|
|
39
|
+
readonly retryable: boolean;
|
|
40
|
+
readonly expiresAt: string | null;
|
|
41
|
+
}
|
|
32
42
|
export interface IiisClientOptions {
|
|
33
43
|
readonly fetchImpl?: typeof fetch;
|
|
34
44
|
readonly pollIntervalMs?: number;
|
|
35
45
|
readonly maxPolls?: number;
|
|
46
|
+
readonly operationBaseUrl?: string;
|
|
36
47
|
}
|
|
37
48
|
export declare class IiisClient {
|
|
38
49
|
#private;
|
|
39
50
|
constructor(options?: IiisClientOptions);
|
|
40
51
|
uploadAndWait(input: IiisOperationInput): Promise<ReleasedResult>;
|
|
52
|
+
recoverAndWait(input: IiisOperationInput): Promise<ReleasedResult>;
|
|
53
|
+
inspectOperation(input: IiisOperationInput): Promise<IiisOperationSnapshot>;
|
|
54
|
+
cancelOperation(input: IiisOperationInput): Promise<IiisOperationSnapshot>;
|
|
41
55
|
downloadResult(input: IiisOperationInput, progress?: ProgressSink): Promise<ReleasedResult>;
|
|
42
56
|
ack(input: IiisOperationInput): Promise<void>;
|
|
43
57
|
}
|
package/dist/iiis-client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { GRANTED_STREAM_PROTOCOL_VERSION, RESULT_CHUNK_MAX_BYTES, } from "./constants.js";
|
|
2
|
+
import { DEFAULT_IIIS_GRANTED_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, RESULT_CHUNK_MAX_BYTES, } from "./constants.js";
|
|
3
3
|
import { OmniBridgeError } from "./errors.js";
|
|
4
4
|
import { createMultipartBody } from "./multipart-body.js";
|
|
5
|
-
import { NOOP_PROGRESS } from "./progress.js";
|
|
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"]);
|
|
@@ -14,6 +14,15 @@ const TERMINAL_STATUSES = new Set([
|
|
|
14
14
|
"DELIVERY_EXPIRED",
|
|
15
15
|
"DELIVERED",
|
|
16
16
|
]);
|
|
17
|
+
const OPERATION_STATUSES = new Set([
|
|
18
|
+
"ISSUED",
|
|
19
|
+
"CLAIMED",
|
|
20
|
+
"UPLOADING",
|
|
21
|
+
"PROCESSING",
|
|
22
|
+
"SETTLING",
|
|
23
|
+
"RELEASED",
|
|
24
|
+
...TERMINAL_STATUSES,
|
|
25
|
+
]);
|
|
17
26
|
const RECOVERABLE_SETTLEMENT_ERRORS = new Set([
|
|
18
27
|
"SETTLEMENT_IN_PROGRESS",
|
|
19
28
|
"SETTLEMENT_RETRYABLE",
|
|
@@ -24,11 +33,24 @@ const RECOVERABLE_SETTLEMENT_ERRORS = new Set([
|
|
|
24
33
|
]);
|
|
25
34
|
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
26
35
|
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
36
|
+
const ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
37
|
+
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
|
+
const PROGRESS_UNITS = new Set([
|
|
39
|
+
"page",
|
|
40
|
+
"sheet",
|
|
41
|
+
"slide",
|
|
42
|
+
"frame",
|
|
43
|
+
"segment",
|
|
44
|
+
]);
|
|
27
45
|
function bridgeError(code, message, facts = {}) {
|
|
28
46
|
return new OmniBridgeError({
|
|
29
47
|
code,
|
|
30
48
|
message,
|
|
49
|
+
operationCreated: facts.operationCreated ?? true,
|
|
31
50
|
fileUploaded: facts.fileUploaded ?? false,
|
|
51
|
+
parserStarted: (facts.parserStarted ?? false) ||
|
|
52
|
+
facts.billed === true ||
|
|
53
|
+
facts.contentReleased === true,
|
|
32
54
|
billed: facts.billed ?? false,
|
|
33
55
|
contentReleased: facts.contentReleased ?? false,
|
|
34
56
|
retryable: facts.retryable ?? false,
|
|
@@ -66,11 +88,17 @@ function throwIfCanceled(input) {
|
|
|
66
88
|
if (input.signal?.aborted)
|
|
67
89
|
throw canceledAfterUpload();
|
|
68
90
|
}
|
|
69
|
-
function
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
throw bridgeError("INSECURE_IIIS_URL", "The IIIS
|
|
91
|
+
function secureIiisUrl(value) {
|
|
92
|
+
const url = new URL(value);
|
|
93
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "") {
|
|
94
|
+
throw bridgeError("INSECURE_IIIS_URL", "The IIIS endpoint must use credential-free HTTPS.");
|
|
73
95
|
}
|
|
96
|
+
return url;
|
|
97
|
+
}
|
|
98
|
+
function operationUrl(input, operationBaseUrl, suffix = "") {
|
|
99
|
+
const base = input.uploadUrl === undefined
|
|
100
|
+
? secureIiisUrl(operationBaseUrl)
|
|
101
|
+
: secureIiisUrl(input.uploadUrl);
|
|
74
102
|
return new URL(`operations/${encodeURIComponent(input.operationId)}${suffix}`, base).toString();
|
|
75
103
|
}
|
|
76
104
|
function operationHeaders(input) {
|
|
@@ -84,29 +112,94 @@ function safeInteger(value, minimum = 0) {
|
|
|
84
112
|
&& Number.isSafeInteger(value)
|
|
85
113
|
&& value >= minimum;
|
|
86
114
|
}
|
|
115
|
+
function isRfc3339(value) {
|
|
116
|
+
if (typeof value !== "string")
|
|
117
|
+
return false;
|
|
118
|
+
const match = RFC3339_PATTERN.exec(value);
|
|
119
|
+
if (match === null)
|
|
120
|
+
return false;
|
|
121
|
+
const year = Number(match[1]);
|
|
122
|
+
const month = Number(match[2]);
|
|
123
|
+
const day = Number(match[3]);
|
|
124
|
+
if (month < 1 || month > 12 || day < 1)
|
|
125
|
+
return false;
|
|
126
|
+
const monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
127
|
+
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
128
|
+
const days = month === 2 && leapYear ? 29 : monthDays[month - 1];
|
|
129
|
+
return day <= days;
|
|
130
|
+
}
|
|
87
131
|
function validateProtocol(event) {
|
|
88
132
|
if (event.protocol_version !== GRANTED_STREAM_PROTOCOL_VERSION) {
|
|
89
133
|
throw bridgeError("PROTOCOL_MISMATCH", "IIIS returned an unsupported protocol version.");
|
|
90
134
|
}
|
|
91
135
|
}
|
|
136
|
+
function stableErrorCode(value, fallback) {
|
|
137
|
+
return typeof value === "string" && ERROR_CODE_PATTERN.test(value)
|
|
138
|
+
? value
|
|
139
|
+
: fallback;
|
|
140
|
+
}
|
|
141
|
+
function stableErrorMessage(code) {
|
|
142
|
+
if (code === "SETTLEMENT_DENIED")
|
|
143
|
+
return "Billing did not authorize content release.";
|
|
144
|
+
if (code === "DELIVERY_EXPIRED")
|
|
145
|
+
return "The temporary delivery result has expired.";
|
|
146
|
+
if (code === "CLEANUP_PENDING")
|
|
147
|
+
return "Omni cleanup is still pending.";
|
|
148
|
+
if (code === "CANCELED")
|
|
149
|
+
return "The Omni operation was canceled.";
|
|
150
|
+
return "The Omni parsing service could not complete the operation.";
|
|
151
|
+
}
|
|
152
|
+
function stableProgressMessage(stage) {
|
|
153
|
+
if (stage === "parse" || stage === "parsing")
|
|
154
|
+
return "Parsing document";
|
|
155
|
+
if (stage === "settling" || stage === "settlement")
|
|
156
|
+
return "Finalizing result";
|
|
157
|
+
return "Processing document";
|
|
158
|
+
}
|
|
92
159
|
function progressValue(event) {
|
|
93
160
|
validateProtocol(event);
|
|
94
|
-
if (!safeInteger(event.done) ||
|
|
161
|
+
if (!safeInteger(event.done) ||
|
|
162
|
+
!safeInteger(event.total) ||
|
|
163
|
+
event.done > event.total) {
|
|
95
164
|
throw bridgeError("INVALID_PROGRESS_EVENT", "IIIS returned an invalid progress event.");
|
|
96
165
|
}
|
|
166
|
+
const unit = typeof event.unit === "string" &&
|
|
167
|
+
PROGRESS_UNITS.has(event.unit)
|
|
168
|
+
? event.unit
|
|
169
|
+
: undefined;
|
|
97
170
|
return {
|
|
98
171
|
done: event.done,
|
|
99
172
|
total: event.total,
|
|
100
|
-
message:
|
|
173
|
+
message: stableProgressMessage(event.stage),
|
|
174
|
+
...(unit === undefined || event.total <= 0
|
|
175
|
+
? {}
|
|
176
|
+
: {
|
|
177
|
+
detail: {
|
|
178
|
+
unit,
|
|
179
|
+
completed: Math.min(event.done, event.total),
|
|
180
|
+
total: event.total,
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
101
183
|
};
|
|
102
184
|
}
|
|
103
185
|
function monotonicProgress(sink) {
|
|
104
186
|
let last = 0;
|
|
187
|
+
let lastDetail;
|
|
105
188
|
return {
|
|
106
|
-
async report(progress, total, message) {
|
|
189
|
+
async report(progress, total, message, detail) {
|
|
107
190
|
const next = Math.min(total, Math.max(last, progress));
|
|
108
191
|
last = next;
|
|
109
|
-
|
|
192
|
+
const nextDetail = detail === undefined
|
|
193
|
+
? undefined
|
|
194
|
+
: lastDetail?.unit === detail.unit
|
|
195
|
+
? {
|
|
196
|
+
...detail,
|
|
197
|
+
completed: Math.min(detail.total, Math.max(lastDetail.completed, detail.completed)),
|
|
198
|
+
}
|
|
199
|
+
: detail;
|
|
200
|
+
if (nextDetail !== undefined)
|
|
201
|
+
lastDetail = nextDetail;
|
|
202
|
+
await sink.report(next, total, message, nextDetail);
|
|
110
203
|
},
|
|
111
204
|
};
|
|
112
205
|
}
|
|
@@ -118,8 +211,9 @@ function parserProgress(done, total) {
|
|
|
118
211
|
async function errorFromResponse(response, input) {
|
|
119
212
|
try {
|
|
120
213
|
const body = await response.json();
|
|
121
|
-
if (isRecord(body) && typeof body.code === "string"
|
|
122
|
-
|
|
214
|
+
if (isRecord(body) && typeof body.code === "string") {
|
|
215
|
+
const code = stableErrorCode(body.code, "IIIS_UNAVAILABLE");
|
|
216
|
+
return bridgeError(code, stableErrorMessage(code), {
|
|
123
217
|
fileUploaded: body.file_uploaded === true,
|
|
124
218
|
billed: body.billed === true,
|
|
125
219
|
contentReleased: body.content_released === true,
|
|
@@ -144,6 +238,33 @@ async function protocolJson(response, input, description) {
|
|
|
144
238
|
throw bridgeError("PROTOCOL_MISMATCH", `IIIS returned invalid ${description} JSON.`);
|
|
145
239
|
}
|
|
146
240
|
}
|
|
241
|
+
function operationStatusValue(value, operationId) {
|
|
242
|
+
if (!isRecord(value)
|
|
243
|
+
|| value.protocol_version !== GRANTED_STREAM_PROTOCOL_VERSION
|
|
244
|
+
|| value.operation_id !== operationId
|
|
245
|
+
|| typeof value.status !== "string"
|
|
246
|
+
|| !OPERATION_STATUSES.has(value.status)
|
|
247
|
+
|| typeof value.parser_started !== "boolean"
|
|
248
|
+
|| typeof value.file_uploaded !== "boolean"
|
|
249
|
+
|| typeof value.billed !== "boolean"
|
|
250
|
+
|| typeof value.content_released !== "boolean"
|
|
251
|
+
|| typeof value.retryable !== "boolean"
|
|
252
|
+
|| !(value.expires_at === null || isRfc3339(value.expires_at))) {
|
|
253
|
+
throw bridgeError("PROTOCOL_MISMATCH", "IIIS returned an invalid operation status.");
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function operationSnapshot(status) {
|
|
258
|
+
return {
|
|
259
|
+
status: status.status,
|
|
260
|
+
parserStarted: status.parser_started,
|
|
261
|
+
fileUploaded: status.file_uploaded,
|
|
262
|
+
billed: status.billed,
|
|
263
|
+
contentReleased: status.content_released,
|
|
264
|
+
retryable: status.retryable,
|
|
265
|
+
expiresAt: status.expires_at,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
147
268
|
async function* sseEvents(response) {
|
|
148
269
|
if (response.body === null)
|
|
149
270
|
throw new TransportFailure("missing SSE body");
|
|
@@ -198,10 +319,17 @@ export class IiisClient {
|
|
|
198
319
|
#fetch;
|
|
199
320
|
#pollIntervalMs;
|
|
200
321
|
#maxPolls;
|
|
322
|
+
#operationBaseUrl;
|
|
201
323
|
constructor(options = {}) {
|
|
202
324
|
this.#fetch = options.fetchImpl ?? fetch;
|
|
203
325
|
this.#pollIntervalMs = Math.max(0, options.pollIntervalMs ?? 250);
|
|
204
326
|
this.#maxPolls = Math.max(1, options.maxPolls ?? 240);
|
|
327
|
+
const operationBase = secureIiisUrl(options.operationBaseUrl ?? DEFAULT_IIIS_GRANTED_BASE_URL);
|
|
328
|
+
operationBase.search = "";
|
|
329
|
+
operationBase.hash = "";
|
|
330
|
+
if (!operationBase.pathname.endsWith("/"))
|
|
331
|
+
operationBase.pathname += "/";
|
|
332
|
+
this.#operationBaseUrl = operationBase.toString();
|
|
205
333
|
}
|
|
206
334
|
async uploadAndWait(input) {
|
|
207
335
|
const progress = monotonicProgress(input.progress ?? NOOP_PROGRESS);
|
|
@@ -222,6 +350,34 @@ export class IiisClient {
|
|
|
222
350
|
return this.#recover(input, true, progress);
|
|
223
351
|
}
|
|
224
352
|
}
|
|
353
|
+
async recoverAndWait(input) {
|
|
354
|
+
return this.#recover(input, false, monotonicProgress(input.progress ?? NOOP_PROGRESS));
|
|
355
|
+
}
|
|
356
|
+
async inspectOperation(input) {
|
|
357
|
+
return operationSnapshot(await this.#status({ ...input, signal: undefined }));
|
|
358
|
+
}
|
|
359
|
+
async cancelOperation(input) {
|
|
360
|
+
throwIfCanceled(input);
|
|
361
|
+
let response;
|
|
362
|
+
try {
|
|
363
|
+
response = await this.#fetch(operationUrl(input, this.#operationBaseUrl, "/cancel"), {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: operationHeaders(input),
|
|
366
|
+
signal: input.signal,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
throwIfCanceled(input);
|
|
371
|
+
throw bridgeError("IIIS_UNAVAILABLE", "IIIS operation cancellation is unavailable.", { retryable: true });
|
|
372
|
+
}
|
|
373
|
+
if (!response.ok)
|
|
374
|
+
throw await errorFromResponse(response, input);
|
|
375
|
+
if (response.status === 202 || response.status === 204) {
|
|
376
|
+
return this.inspectOperation(input);
|
|
377
|
+
}
|
|
378
|
+
const value = await protocolJson(response, input, "operation cancellation");
|
|
379
|
+
return operationSnapshot(operationStatusValue(value, input.operationId));
|
|
380
|
+
}
|
|
225
381
|
async downloadResult(input, progress = NOOP_PROGRESS) {
|
|
226
382
|
throwIfCanceled(input);
|
|
227
383
|
const confirmedFacts = recoveryFacts({
|
|
@@ -249,7 +405,7 @@ export class IiisClient {
|
|
|
249
405
|
}
|
|
250
406
|
let response;
|
|
251
407
|
try {
|
|
252
|
-
response = await this.#fetch(operationUrl(input, "/result"), {
|
|
408
|
+
response = await this.#fetch(operationUrl(input, this.#operationBaseUrl, "/result"), {
|
|
253
409
|
headers: operationHeaders(input),
|
|
254
410
|
signal: input.signal,
|
|
255
411
|
});
|
|
@@ -347,7 +503,7 @@ export class IiisClient {
|
|
|
347
503
|
throwIfCanceled(input);
|
|
348
504
|
let response;
|
|
349
505
|
try {
|
|
350
|
-
response = await this.#fetch(operationUrl(input, "/ack"), {
|
|
506
|
+
response = await this.#fetch(operationUrl(input, this.#operationBaseUrl, "/ack"), {
|
|
351
507
|
method: "POST",
|
|
352
508
|
headers: operationHeaders(input),
|
|
353
509
|
signal: input.signal,
|
|
@@ -366,21 +522,27 @@ export class IiisClient {
|
|
|
366
522
|
throw await errorFromResponse(response, input);
|
|
367
523
|
}
|
|
368
524
|
async #uploadOnce(input, progress) {
|
|
369
|
-
const
|
|
525
|
+
const openedFile = input.openedFile;
|
|
526
|
+
const parseGrant = input.parseGrant;
|
|
527
|
+
const uploadUrl = input.uploadUrl;
|
|
528
|
+
if (openedFile === undefined || parseGrant === undefined || uploadUrl === undefined) {
|
|
529
|
+
throw bridgeError("UPLOAD_CONTEXT_UNAVAILABLE", "The local upload context is unavailable for this recovery operation.", { retryable: true });
|
|
530
|
+
}
|
|
531
|
+
const body = createMultipartBody(openedFile, {
|
|
370
532
|
signal: input.signal,
|
|
371
533
|
onProgress: async (bytes) => {
|
|
372
|
-
const scaled =
|
|
534
|
+
const scaled = openedFile.size === 0
|
|
373
535
|
? 40
|
|
374
|
-
: Math.floor(bytes * 40 /
|
|
375
|
-
await progress.report(scaled, 100, `Uploading ${bytes}/${
|
|
536
|
+
: Math.floor(bytes * 40 / openedFile.size);
|
|
537
|
+
await progress.report(scaled, 100, `Uploading ${bytes}/${openedFile.size} bytes`);
|
|
376
538
|
},
|
|
377
539
|
});
|
|
378
540
|
let response;
|
|
379
541
|
try {
|
|
380
|
-
response = await this.#fetch(
|
|
542
|
+
response = await this.#fetch(uploadUrl, {
|
|
381
543
|
method: "POST",
|
|
382
544
|
headers: {
|
|
383
|
-
authorization: `Bearer ${
|
|
545
|
+
authorization: `Bearer ${parseGrant}`,
|
|
384
546
|
"content-type": body.contentType,
|
|
385
547
|
"content-length": String(body.contentLength),
|
|
386
548
|
},
|
|
@@ -421,11 +583,12 @@ export class IiisClient {
|
|
|
421
583
|
}
|
|
422
584
|
if (event.type === "progress") {
|
|
423
585
|
const parsed = progressValue(event);
|
|
424
|
-
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message);
|
|
586
|
+
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message, parsed.detail);
|
|
425
587
|
continue;
|
|
426
588
|
}
|
|
427
589
|
if (event.type === "error") {
|
|
428
|
-
|
|
590
|
+
const code = stableErrorCode(event.code, "IIIS_PARSE_FAILED");
|
|
591
|
+
throw bridgeError(code, stableErrorMessage(code), {
|
|
429
592
|
fileUploaded: event.file_uploaded === true,
|
|
430
593
|
billed: event.billed === true,
|
|
431
594
|
contentReleased: event.content_released === true,
|
|
@@ -555,7 +718,7 @@ export class IiisClient {
|
|
|
555
718
|
throwIfCanceled(input);
|
|
556
719
|
let response;
|
|
557
720
|
try {
|
|
558
|
-
response = await this.#fetch(operationUrl(input), {
|
|
721
|
+
response = await this.#fetch(operationUrl(input, this.#operationBaseUrl), {
|
|
559
722
|
headers: operationHeaders(input),
|
|
560
723
|
signal: input.signal,
|
|
561
724
|
});
|
|
@@ -567,25 +730,13 @@ export class IiisClient {
|
|
|
567
730
|
if (!response.ok)
|
|
568
731
|
throw await errorFromResponse(response, input);
|
|
569
732
|
const value = await protocolJson(response, input, "operation status");
|
|
570
|
-
|
|
571
|
-
|| value.protocol_version !== GRANTED_STREAM_PROTOCOL_VERSION
|
|
572
|
-
|| value.operation_id !== input.operationId
|
|
573
|
-
|| typeof value.status !== "string"
|
|
574
|
-
|| typeof value.parser_started !== "boolean"
|
|
575
|
-
|| typeof value.file_uploaded !== "boolean"
|
|
576
|
-
|| typeof value.billed !== "boolean"
|
|
577
|
-
|| typeof value.content_released !== "boolean"
|
|
578
|
-
|| typeof value.retryable !== "boolean"
|
|
579
|
-
|| !(value.expires_at === null || typeof value.expires_at === "string")) {
|
|
580
|
-
throw bridgeError("PROTOCOL_MISMATCH", "IIIS returned an invalid operation status.");
|
|
581
|
-
}
|
|
582
|
-
return value;
|
|
733
|
+
return operationStatusValue(value, input.operationId);
|
|
583
734
|
}
|
|
584
735
|
async #events(input, afterSequence) {
|
|
585
736
|
throwIfCanceled(input);
|
|
586
737
|
let response;
|
|
587
738
|
try {
|
|
588
|
-
response = await this.#fetch(operationUrl(input, `/events?after_sequence=${afterSequence}`), { headers: operationHeaders(input), signal: input.signal });
|
|
739
|
+
response = await this.#fetch(operationUrl(input, this.#operationBaseUrl, `/events?after_sequence=${afterSequence}`), { headers: operationHeaders(input), signal: input.signal });
|
|
589
740
|
}
|
|
590
741
|
catch {
|
|
591
742
|
throwIfCanceled(input);
|
|
@@ -637,7 +788,10 @@ export class IiisClient {
|
|
|
637
788
|
await progress.report(45, 100, "Parser started");
|
|
638
789
|
parserStartedReported = true;
|
|
639
790
|
}
|
|
640
|
-
if (status.status === "ISSUED" &&
|
|
791
|
+
if (status.status === "ISSUED" &&
|
|
792
|
+
mayRetryUpload &&
|
|
793
|
+
input.expiresAt !== undefined &&
|
|
794
|
+
Date.parse(input.expiresAt) > Date.now()) {
|
|
641
795
|
try {
|
|
642
796
|
return await this.#uploadOnce(input, progress);
|
|
643
797
|
}
|
|
@@ -655,12 +809,12 @@ export class IiisClient {
|
|
|
655
809
|
const page = await this.#events(input, eventCursor);
|
|
656
810
|
for (const event of page.events) {
|
|
657
811
|
const parsed = progressValue(event);
|
|
658
|
-
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message);
|
|
812
|
+
await progress.report(parserProgress(parsed.done, parsed.total), 100, parsed.message, parsed.detail);
|
|
659
813
|
}
|
|
660
814
|
eventCursor = page.nextSequence;
|
|
661
815
|
}
|
|
662
816
|
else if (TERMINAL_STATUSES.has(status.status)) {
|
|
663
|
-
throw bridgeError(status.status,
|
|
817
|
+
throw bridgeError(status.status, stableErrorMessage(status.status), {
|
|
664
818
|
fileUploaded: status.file_uploaded,
|
|
665
819
|
billed: status.billed,
|
|
666
820
|
contentReleased: status.content_released,
|
package/dist/index.d.ts
CHANGED
|
@@ -8,10 +8,13 @@ export interface RunCliOptions {
|
|
|
8
8
|
readonly cwd?: string;
|
|
9
9
|
readonly artifactRoot?: string;
|
|
10
10
|
readonly platform?: NodeJS.Platform;
|
|
11
|
+
readonly temporaryDirectory?: string;
|
|
12
|
+
readonly stdinIsTTY?: boolean;
|
|
11
13
|
readonly now?: () => Date;
|
|
12
14
|
readonly npmVersion?: string;
|
|
13
15
|
readonly packageVersion?: string;
|
|
14
16
|
readonly fetchImpl?: typeof fetch;
|
|
17
|
+
readonly iiisOperationBaseUrl?: string;
|
|
15
18
|
readonly stdout?: WritableLike;
|
|
16
19
|
readonly stderr?: WritableLike;
|
|
17
20
|
readonly ask?: (question: string) => Promise<string>;
|