@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.
Files changed (48) hide show
  1. package/README.md +115 -26
  2. package/dist/artifact-store.d.ts +11 -0
  3. package/dist/artifact-store.js +94 -48
  4. package/dist/cli/agent-config.d.ts +29 -4
  5. package/dist/cli/agent-config.js +910 -107
  6. package/dist/cli/arguments.d.ts +32 -0
  7. package/dist/cli/arguments.js +120 -0
  8. package/dist/cli/doctor.d.ts +42 -1
  9. package/dist/cli/doctor.js +109 -36
  10. package/dist/cli/setup.d.ts +3 -0
  11. package/dist/cli/setup.js +103 -18
  12. package/dist/cli/uninstall.d.ts +6 -0
  13. package/dist/cli/uninstall.js +37 -0
  14. package/dist/constants.d.ts +7 -0
  15. package/dist/constants.js +7 -0
  16. package/dist/cube-client.d.ts +5 -3
  17. package/dist/cube-client.js +16 -11
  18. package/dist/cursor.js +2 -0
  19. package/dist/errors.d.ts +32 -1
  20. package/dist/errors.js +26 -1
  21. package/dist/iiis-client.d.ts +18 -4
  22. package/dist/iiis-client.js +194 -40
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +93 -32
  25. package/dist/multipart-body.js +2 -0
  26. package/dist/onboarding-policy.d.ts +10 -0
  27. package/dist/onboarding-policy.js +58 -0
  28. package/dist/operation-journal.d.ts +50 -1
  29. package/dist/operation-journal.js +473 -114
  30. package/dist/operation-manager.d.ts +75 -0
  31. package/dist/operation-manager.js +1324 -0
  32. package/dist/path-security.d.ts +1 -0
  33. package/dist/path-security.js +26 -6
  34. package/dist/progress.d.ts +6 -1
  35. package/dist/protocol.d.ts +26 -13
  36. package/dist/protocol.js +34 -10
  37. package/dist/remote-client.d.ts +17 -0
  38. package/dist/remote-client.js +233 -0
  39. package/dist/result-contract.d.ts +199 -0
  40. package/dist/result-contract.js +235 -0
  41. package/dist/server.js +21 -4
  42. package/dist/source.d.ts +8 -0
  43. package/dist/source.js +37 -0
  44. package/dist/task-runtime.d.ts +13 -0
  45. package/dist/task-runtime.js +94 -0
  46. package/dist/tools.d.ts +19 -1
  47. package/dist/tools.js +317 -112
  48. package/package.json +3 -3
@@ -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, CUBE_GRANT_PROTOCOL_VERSION, } from "./constants.js";
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: BRIDGE_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
- await this.#journal.begin(clientRequestId, requestHash);
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
- await this.#journal.markGrantIssued(clientRequestId, {
143
- operationId: parsed.operation_id,
144
- operationToken: parsed.operation_token,
145
- uploadUrl: parsed.upload_url,
146
- expiresAt: parsed.expires_at,
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
@@ -7,7 +7,9 @@ function cursorError(code, message) {
7
7
  return new OmniBridgeError({
8
8
  code,
9
9
  message,
10
+ operationCreated: true,
10
11
  fileUploaded: true,
12
+ parserStarted: true,
11
13
  billed: true,
12
14
  contentReleased: true,
13
15
  retryable: false,
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
- retryable: boolean;
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
- retryable: this.retryable,
47
+ ...(this.retryAfter === undefined ? {} : { retry_after: this.retryAfter }),
48
+ ...(this.constraints === undefined ? {} : { constraints: this.constraints }),
24
49
  };
25
50
  }
26
51
  }
@@ -17,27 +17,41 @@ export interface ResultRetentionSink {
17
17
  abort(): Promise<void>;
18
18
  }
19
19
  export interface IiisOperationInput {
20
- readonly parseGrant: string;
20
+ readonly parseGrant?: string;
21
21
  readonly operationId: string;
22
22
  readonly operationToken: string;
23
- readonly uploadUrl: string;
24
- readonly expiresAt: string;
25
- readonly openedFile: OpenedAllowedFile;
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
  }
@@ -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 operationUrl(input, suffix = "") {
70
- const base = new URL(input.uploadUrl);
71
- if (base.protocol !== "https:" || base.username !== "" || base.password !== "") {
72
- throw bridgeError("INSECURE_IIIS_URL", "The IIIS upload endpoint must use credential-free HTTPS.");
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) || !safeInteger(event.total)) {
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: String(event.message ?? event.stage ?? "Parsing"),
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
- await sink.report(next, total, message);
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" && typeof body.message === "string") {
122
- return bridgeError(body.code, body.message, {
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 body = createMultipartBody(input.openedFile, {
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 = input.openedFile.size === 0
534
+ const scaled = openedFile.size === 0
373
535
  ? 40
374
- : Math.floor(bytes * 40 / input.openedFile.size);
375
- await progress.report(scaled, 100, `Uploading ${bytes}/${input.openedFile.size} 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(input.uploadUrl, {
542
+ response = await this.#fetch(uploadUrl, {
381
543
  method: "POST",
382
544
  headers: {
383
- authorization: `Bearer ${input.parseGrant}`,
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
- throw bridgeError(String(event.code ?? "IIIS_PARSE_FAILED"), String(event.message ?? "IIIS parsing failed."), {
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
- if (!isRecord(value)
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" && mayRetryUpload && Date.parse(input.expiresAt) > Date.now()) {
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, `The IIIS operation ended with status ${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>;