@cueai/omni-reader-mcp 1.0.2 → 1.1.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.
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 +1311 -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
@@ -0,0 +1,1311 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DELIVERY_TTL_SECONDS, FOREGROUND_BUDGET_MS, INLINE_RESULT_MAX_BYTES, STATUS_LONG_POLL_MAX_MS, STATUS_POLL_AFTER_SECONDS, } from "./constants.js";
3
+ import { OmniBridgeError } from "./errors.js";
4
+ import { openAllowedFile, } from "./path-security.js";
5
+ import { NOOP_PROGRESS } from "./progress.js";
6
+ const TERMINAL_STATES = new Set([
7
+ "COMPLETED",
8
+ "FAILED",
9
+ "CANCELED",
10
+ "EXPIRED",
11
+ ]);
12
+ const STATE_ORDER = new Map([
13
+ ["CREATED", 0],
14
+ ["GRANT_PENDING", 1],
15
+ ["GRANT_ISSUED", 2],
16
+ ["UPLOADING", 3],
17
+ ["PROCESSING", 4],
18
+ ["RESULT_READY", 5],
19
+ ["ACK_PENDING", 6],
20
+ ["CLEANUP_PENDING", 7],
21
+ ["COMPLETED", 8],
22
+ ]);
23
+ function managerError(code, message, facts = {}) {
24
+ return new OmniBridgeError({
25
+ code,
26
+ message,
27
+ failureScope: "operation",
28
+ operationCreated: facts.operationCreated ?? false,
29
+ fileUploaded: facts.fileUploaded ?? false,
30
+ parserStarted: facts.parserStarted ?? false,
31
+ billed: facts.billed ?? false,
32
+ contentReleased: facts.contentReleased ?? false,
33
+ retryable: facts.retryable ?? false,
34
+ });
35
+ }
36
+ function canonicalValue(value) {
37
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
38
+ return value;
39
+ }
40
+ if (typeof value === "number") {
41
+ if (!Number.isFinite(value)) {
42
+ throw managerError("INVALID_SOURCE_FACTS", "The operation source facts contain a non-finite number.");
43
+ }
44
+ return value;
45
+ }
46
+ if (Array.isArray(value))
47
+ return value.map(canonicalValue);
48
+ if (typeof value === "object") {
49
+ const object = value;
50
+ return Object.fromEntries(Object.keys(object).sort().map((key) => [key, canonicalValue(object[key])]));
51
+ }
52
+ throw managerError("INVALID_SOURCE_FACTS", "The operation source facts contain an unsupported value.");
53
+ }
54
+ export function operationRequestHash(input) {
55
+ const serialized = JSON.stringify({
56
+ source_kind: input.sourceKind,
57
+ source_facts: canonicalValue(input.sourceFacts),
58
+ });
59
+ return `sha256:${createHash("sha256").update(serialized, "utf8").digest("hex")}`;
60
+ }
61
+ function defaultSleep(milliseconds) {
62
+ return new Promise((resolve) => {
63
+ const timeout = setTimeout(resolve, milliseconds);
64
+ timeout.unref?.();
65
+ });
66
+ }
67
+ function safeToRetryCreation(error) {
68
+ return !error.operationCreated && !error.billed && error.retryable;
69
+ }
70
+ function safeToRecoverCreation(error) {
71
+ return error.operationCreated && !error.billed && error.retryable;
72
+ }
73
+ function failurePatch(error) {
74
+ return {
75
+ fileUploaded: error.fileUploaded,
76
+ parserStarted: error.parserStarted,
77
+ billed: error.billed,
78
+ contentReleased: error.contentReleased,
79
+ errorCode: error.code,
80
+ };
81
+ }
82
+ function isExecution(value) {
83
+ return "initial" in value && "completed" in value;
84
+ }
85
+ function recordDataHandling(record) {
86
+ const processingCopy = record.processingCopy === "deleted"
87
+ ? "deleted"
88
+ : record.processingCopy === "pending"
89
+ ? "pending"
90
+ : "in_use";
91
+ const temporaryData = record.temporaryData === "deleted"
92
+ ? "deleted"
93
+ : record.temporaryData === "pending"
94
+ ? "pending"
95
+ : "in_use";
96
+ const deliveryResult = record.deliveryResult === "deleted_after_ack"
97
+ ? "deleted_after_ack"
98
+ : record.deliveryResult === "pending"
99
+ ? "pending"
100
+ : record.contentReleased && record.resultExpiresAt !== null
101
+ ? { expires_at: record.resultExpiresAt }
102
+ : "not_created";
103
+ return {
104
+ processing_copy: processingCopy,
105
+ temporary_data: temporaryData,
106
+ delivery_result: deliveryResult,
107
+ original_source: "unchanged",
108
+ };
109
+ }
110
+ function resultFromRecord(record) {
111
+ if (record.operationId === null) {
112
+ const error = managerError("OPERATION_ID_UNAVAILABLE", "The parse operation has not received a recoverable operation identifier.", { retryable: true });
113
+ return { status: "failed", error: error.toJSON() };
114
+ }
115
+ if (record.state === "EXPIRED") {
116
+ return {
117
+ status: "expired",
118
+ operation_id: record.operationId,
119
+ requires_user_confirmation: true,
120
+ };
121
+ }
122
+ if (record.state === "CANCELED") {
123
+ const cleanupDeadline = record.resultExpiresAt ?? record.expiresAt;
124
+ return {
125
+ status: "canceled",
126
+ operation_id: record.operationId,
127
+ ...(cleanupDeadline === null ? {} : { cleanup_deadline: cleanupDeadline }),
128
+ data_handling: recordDataHandling(record),
129
+ };
130
+ }
131
+ if (record.state === "FAILED") {
132
+ const code = record.errorCode ?? "OPERATION_FAILED";
133
+ const error = managerError(code, `The Omni operation ended with status ${code}.`, {
134
+ operationCreated: true,
135
+ fileUploaded: record.fileUploaded,
136
+ parserStarted: record.parserStarted,
137
+ billed: record.billed,
138
+ contentReleased: record.contentReleased,
139
+ });
140
+ return { status: "failed", error: error.toJSON() };
141
+ }
142
+ if (record.state === "CLEANUP_PENDING") {
143
+ return {
144
+ status: "cleanup_pending",
145
+ operation_id: record.operationId,
146
+ cleanup_deadline: record.resultExpiresAt ?? record.expiresAt ?? new Date(0).toISOString(),
147
+ data_handling: recordDataHandling(record),
148
+ };
149
+ }
150
+ if (record.state === "COMPLETED") {
151
+ const error = managerError("RESULT_NOT_AVAILABLE", "The completed result is no longer available in this Bridge process.", {
152
+ operationCreated: true,
153
+ fileUploaded: record.fileUploaded,
154
+ parserStarted: record.parserStarted,
155
+ billed: record.billed,
156
+ contentReleased: record.contentReleased,
157
+ });
158
+ return { status: "failed", error: error.toJSON() };
159
+ }
160
+ return {
161
+ status: "processing",
162
+ operation_id: record.operationId,
163
+ stage: record.stage ?? record.state.toLowerCase(),
164
+ progress: record.progressPercent,
165
+ ...(record.progress === null ? {} : { progress_detail: record.progress }),
166
+ next_action: "check_status",
167
+ poll_after_seconds: STATUS_POLL_AFTER_SECONDS,
168
+ can_cancel: true,
169
+ data_handling: recordDataHandling(record),
170
+ };
171
+ }
172
+ export class OperationManager {
173
+ #journal;
174
+ #driver;
175
+ #now;
176
+ #sleep;
177
+ #submissions = new Map();
178
+ #executions = new Map();
179
+ #results = new Map();
180
+ constructor(options) {
181
+ this.#journal = options.journal;
182
+ this.#driver = options.driver;
183
+ this.#now = options.now ?? Date.now;
184
+ this.#sleep = options.sleep ?? defaultSleep;
185
+ }
186
+ async submit(input) {
187
+ const requestHash = operationRequestHash(input);
188
+ const active = this.#submissions.get(requestHash);
189
+ if (active !== undefined)
190
+ return active;
191
+ const pending = this.#submit(input, requestHash);
192
+ this.#submissions.set(requestHash, pending);
193
+ try {
194
+ return await pending;
195
+ }
196
+ finally {
197
+ if (this.#submissions.get(requestHash) === pending) {
198
+ this.#submissions.delete(requestHash);
199
+ }
200
+ }
201
+ }
202
+ async #submit(input, requestHash) {
203
+ let effectiveInput = input;
204
+ const sourceLocatorHash = typeof input.sourceFacts.sourceHash === "string"
205
+ ? input.sourceFacts.sourceHash
206
+ : null;
207
+ let record = await this.#journal.loadByRequestId(input.clientRequestId);
208
+ if (record === null) {
209
+ let matching = await this.#journal.loadLatestByRequestHash(requestHash);
210
+ if (matching === null &&
211
+ input.sourceKind === "local" &&
212
+ input.sourceFacts.sourceFingerprint === undefined &&
213
+ sourceLocatorHash !== null) {
214
+ matching = await this.#journal.loadLatestBySourceLocatorHash(sourceLocatorHash);
215
+ }
216
+ if (matching !== null && this.#shouldReuseByRequestHash(matching)) {
217
+ record = matching;
218
+ effectiveInput = { ...input, clientRequestId: matching.clientRequestId };
219
+ }
220
+ else {
221
+ record = await this.#journal.beginIntent(input.clientRequestId, requestHash, input.sourceKind, sourceLocatorHash);
222
+ }
223
+ }
224
+ else {
225
+ record = await this.#journal.beginIntent(input.clientRequestId, requestHash, input.sourceKind, sourceLocatorHash);
226
+ }
227
+ const signal = effectiveInput.signal ?? new AbortController().signal;
228
+ if (TERMINAL_STATES.has(record.state))
229
+ return record;
230
+ if (record.state !== "CREATED") {
231
+ if (record.operationId !== null && this.#executions.has(record.operationId)) {
232
+ return record;
233
+ }
234
+ return this.#resume(record, effectiveInput, signal);
235
+ }
236
+ try {
237
+ record = await this.#journal.transition(effectiveInput.clientRequestId, "CREATED", "GRANT_PENDING", {});
238
+ }
239
+ catch (error) {
240
+ if (!(error instanceof OmniBridgeError) || error.code !== "JOURNAL_STATE_CONFLICT") {
241
+ throw error;
242
+ }
243
+ const raced = await this.#journal.loadByRequestId(effectiveInput.clientRequestId);
244
+ if (raced === null)
245
+ throw error;
246
+ return TERMINAL_STATES.has(raced.state)
247
+ ? raced
248
+ : this.#resume(raced, effectiveInput, signal);
249
+ }
250
+ try {
251
+ const start = await this.#driver.create(effectiveInput, requestHash, signal);
252
+ return this.#acceptStart(record, start);
253
+ }
254
+ catch (error) {
255
+ const structured = error instanceof OmniBridgeError
256
+ ? error
257
+ : managerError("BRIDGE_INTERNAL_ERROR", "The local operation could not be created safely.", { operationCreated: true });
258
+ await this.#recordCreationFailure(effectiveInput.clientRequestId, structured);
259
+ throw structured;
260
+ }
261
+ }
262
+ async submitResult(input, foregroundBudgetMs = FOREGROUND_BUDGET_MS) {
263
+ const startedAt = this.#now();
264
+ let record = await this.submit(input);
265
+ const existingResult = record.operationId === null
266
+ ? undefined
267
+ : this.#results.get(record.operationId);
268
+ if (existingResult !== undefined)
269
+ return existingResult;
270
+ const execution = record.operationId === null
271
+ ? undefined
272
+ : this.#executions.get(record.operationId);
273
+ if (execution === undefined) {
274
+ return TERMINAL_STATES.has(record.state) && record.operationId !== null
275
+ ? this.statusResult(record.operationId, 0, input.signal)
276
+ : resultFromRecord(record);
277
+ }
278
+ const remaining = Math.max(0, foregroundBudgetMs - (this.#now() - startedAt));
279
+ if (remaining === 0) {
280
+ record = await this.#applyUpdate(record, execution.handle.snapshot());
281
+ return resultFromRecord(record);
282
+ }
283
+ const winner = await Promise.race([
284
+ execution.settled.then((result) => ({ kind: "completed", result })),
285
+ this.#sleep(remaining).then(() => ({ kind: "timeout" })),
286
+ ]);
287
+ if (winner.kind === "completed") {
288
+ return this.#now() - startedAt < foregroundBudgetMs
289
+ ? winner.result
290
+ : resultFromRecord(record);
291
+ }
292
+ const current = await this.#journal.loadByOperationId(record.operationId);
293
+ if (current !== null && !TERMINAL_STATES.has(current.state)) {
294
+ record = await this.#applyUpdate(current, execution.handle.snapshot());
295
+ }
296
+ else if (current !== null) {
297
+ record = current;
298
+ }
299
+ return resultFromRecord(record);
300
+ }
301
+ async statusResult(operationId, waitMs = 0, signal = new AbortController().signal) {
302
+ const boundedWait = Math.min(STATUS_LONG_POLL_MAX_MS, Math.max(0, waitMs));
303
+ let record = await this.#existingOperation(operationId);
304
+ const cached = this.#results.get(operationId);
305
+ if (cached !== undefined && TERMINAL_STATES.has(record.state))
306
+ return cached;
307
+ if (TERMINAL_STATES.has(record.state)) {
308
+ const recovered = await this.#driver.result?.(record, signal);
309
+ if (recovered !== undefined) {
310
+ this.#results.set(operationId, recovered);
311
+ return recovered;
312
+ }
313
+ return resultFromRecord(record);
314
+ }
315
+ if (record.resultExpiresAt !== null &&
316
+ Date.parse(record.resultExpiresAt) <= this.#now()) {
317
+ record = await this.#journal.transition(record.clientRequestId, record.state, "EXPIRED", { errorCode: "RESULT_EXPIRED" });
318
+ return resultFromRecord(record);
319
+ }
320
+ let execution = this.#executions.get(operationId);
321
+ if (execution === undefined) {
322
+ record = await this.status(operationId, boundedWait, signal);
323
+ const statusResult = this.#results.get(operationId);
324
+ if (statusResult !== undefined && TERMINAL_STATES.has(record.state)) {
325
+ return statusResult;
326
+ }
327
+ execution = this.#executions.get(operationId);
328
+ if (execution === undefined)
329
+ return statusResult ?? resultFromRecord(record);
330
+ }
331
+ if (!TERMINAL_STATES.has(record.state)) {
332
+ record = await this.#applyUpdate(record, execution.handle.snapshot());
333
+ }
334
+ if (boundedWait === 0)
335
+ return this.#results.get(operationId) ?? resultFromRecord(record);
336
+ const winner = await Promise.race([
337
+ execution.settled.then((value) => ({ kind: "completed", value })),
338
+ this.#sleep(boundedWait).then(() => ({ kind: "timeout" })),
339
+ ]);
340
+ if (winner.kind === "completed")
341
+ return winner.value;
342
+ const current = await this.#existingOperation(operationId);
343
+ record = TERMINAL_STATES.has(current.state)
344
+ ? current
345
+ : await this.#applyUpdate(current, execution.handle.snapshot());
346
+ return this.#results.get(operationId) ?? resultFromRecord(record);
347
+ }
348
+ async cancelResult(operationId, signal = new AbortController().signal) {
349
+ const existing = this.#results.get(operationId);
350
+ if (existing !== undefined && existing.status !== "processing")
351
+ return existing;
352
+ const execution = this.#executions.get(operationId);
353
+ if (execution !== undefined) {
354
+ await execution.handle.cancel();
355
+ return execution.settled;
356
+ }
357
+ const record = await this.cancel(operationId, signal);
358
+ return this.#results.get(operationId) ?? resultFromRecord(record);
359
+ }
360
+ async status(operationId, waitMs, signal = new AbortController().signal) {
361
+ const record = await this.#existingOperation(operationId);
362
+ if (TERMINAL_STATES.has(record.state) || this.#driver.status === undefined)
363
+ return record;
364
+ const start = await this.#driver.status(record, waitMs, signal);
365
+ return start === undefined ? record : this.#acceptStart(record, start);
366
+ }
367
+ async cancel(operationId, signal = new AbortController().signal) {
368
+ const record = await this.#existingOperation(operationId);
369
+ if (TERMINAL_STATES.has(record.state) || this.#driver.cancel === undefined)
370
+ return record;
371
+ const update = await this.#driver.cancel(record, signal);
372
+ if (update === undefined)
373
+ return record;
374
+ const updated = await this.#applyUpdate(record, update);
375
+ if (update.result !== undefined && updated.operationId !== null) {
376
+ this.#results.set(updated.operationId, update.result);
377
+ }
378
+ return updated;
379
+ }
380
+ async #resume(record, input, signal) {
381
+ if (this.#driver.resume === undefined)
382
+ return record;
383
+ const start = await this.#driver.resume(record, input, signal);
384
+ return start === undefined ? record : this.#acceptStart(record, start);
385
+ }
386
+ async #acceptStart(record, start) {
387
+ if (!isExecution(start)) {
388
+ const updated = await this.#applyUpdate(record, start);
389
+ if (start.result !== undefined && updated.operationId !== null) {
390
+ this.#results.set(updated.operationId, start.result);
391
+ }
392
+ return updated;
393
+ }
394
+ const initial = await this.#applyUpdate(record, start.initial);
395
+ if (initial.operationId === null) {
396
+ throw managerError("OPERATION_ID_UNAVAILABLE", "The operation driver did not provide a recoverable operation identifier.", { retryable: true });
397
+ }
398
+ const operationId = initial.operationId;
399
+ let managed;
400
+ const settled = start.completed.then(async (completion) => {
401
+ const current = await this.#journal.loadByOperationId(operationId);
402
+ if (current !== null && !TERMINAL_STATES.has(current.state)) {
403
+ await this.#applyUpdate(current, completion.update);
404
+ }
405
+ if (completion.result.status !== "processing") {
406
+ this.#results.set(operationId, completion.result);
407
+ }
408
+ else {
409
+ this.#results.delete(operationId);
410
+ }
411
+ return completion.result;
412
+ }).catch(async (error) => {
413
+ const structured = error instanceof OmniBridgeError
414
+ ? error
415
+ : managerError("BRIDGE_INTERNAL_ERROR", "The local operation stopped unexpectedly.", { operationCreated: true });
416
+ const current = await this.#journal.loadByOperationId(operationId);
417
+ const updated = current !== null && !TERMINAL_STATES.has(current.state)
418
+ ? await this.#applyUpdate(current, {
419
+ state: "FAILED",
420
+ patch: failurePatch(structured),
421
+ })
422
+ : current;
423
+ const failed = { status: "failed", error: structured.toJSON() };
424
+ if (updated !== null && TERMINAL_STATES.has(updated.state)) {
425
+ this.#results.set(operationId, failed);
426
+ }
427
+ return failed;
428
+ }).finally(() => {
429
+ if (this.#executions.get(operationId) === managed) {
430
+ this.#executions.delete(operationId);
431
+ }
432
+ });
433
+ managed = { handle: start, settled };
434
+ this.#executions.set(operationId, managed);
435
+ return initial;
436
+ }
437
+ async #recordCreationFailure(clientRequestId, error) {
438
+ const current = await this.#journal.loadByRequestId(clientRequestId);
439
+ if (current === null || TERMINAL_STATES.has(current.state))
440
+ return;
441
+ try {
442
+ if (safeToRetryCreation(error) && current.state === "GRANT_PENDING") {
443
+ await this.#journal.transition(clientRequestId, current.state, "CREATED", { errorCode: null });
444
+ return;
445
+ }
446
+ if (safeToRecoverCreation(error) && current.state === "GRANT_PENDING") {
447
+ await this.#journal.transition(clientRequestId, current.state, current.state, failurePatch(error));
448
+ return;
449
+ }
450
+ if (current.state === "GRANT_PENDING" || current.operationId === null) {
451
+ await this.#journal.transition(clientRequestId, current.state, "FAILED", failurePatch(error));
452
+ return;
453
+ }
454
+ await this.#journal.transition(clientRequestId, current.state, current.state, failurePatch(error));
455
+ }
456
+ catch (journalError) {
457
+ if (!(journalError instanceof OmniBridgeError) ||
458
+ journalError.code !== "JOURNAL_STATE_CONFLICT") {
459
+ throw journalError;
460
+ }
461
+ }
462
+ }
463
+ #shouldReuseByRequestHash(record) {
464
+ if (!TERMINAL_STATES.has(record.state))
465
+ return true;
466
+ if (record.state !== "COMPLETED" || record.operationId === null)
467
+ return false;
468
+ if (this.#results.has(record.operationId))
469
+ return true;
470
+ const updatedAt = Date.parse(record.updatedAt);
471
+ return Number.isFinite(updatedAt) &&
472
+ updatedAt + DELIVERY_TTL_SECONDS * 1000 > this.#now();
473
+ }
474
+ async #existingOperation(operationId) {
475
+ const record = await this.#journal.loadByOperationId(operationId);
476
+ if (record === null) {
477
+ throw managerError("OPERATION_NOT_FOUND", "The requested parse operation is not available.");
478
+ }
479
+ return record;
480
+ }
481
+ async #applyUpdate(record, update) {
482
+ let current = record;
483
+ for (let attempt = 0; attempt < 4; attempt += 1) {
484
+ try {
485
+ return await this.#journal.transition(current.clientRequestId, current.state, update.state, update.patch ?? {});
486
+ }
487
+ catch (error) {
488
+ if (!(error instanceof OmniBridgeError) || error.code !== "JOURNAL_STATE_CONFLICT") {
489
+ throw error;
490
+ }
491
+ const latest = await this.#journal.loadByRequestId(current.clientRequestId);
492
+ if (latest === null)
493
+ throw error;
494
+ if (TERMINAL_STATES.has(latest.state))
495
+ return latest;
496
+ const latestOrder = STATE_ORDER.get(latest.state);
497
+ const targetOrder = STATE_ORDER.get(update.state);
498
+ if (latest.state !== update.state &&
499
+ !(targetOrder !== undefined && latestOrder !== undefined && latestOrder < targetOrder) &&
500
+ !TERMINAL_STATES.has(update.state)) {
501
+ return latest;
502
+ }
503
+ current = latest;
504
+ }
505
+ }
506
+ throw managerError("JOURNAL_STATE_CONFLICT", "The operation changed repeatedly while applying an update.", { operationCreated: record.operationId !== null, retryable: true });
507
+ }
508
+ }
509
+ function localContext(value) {
510
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
511
+ throw managerError("LOCAL_CONTEXT_INVALID", "The local parse context is unavailable.");
512
+ }
513
+ const context = value;
514
+ if (typeof context.source !== "string" ||
515
+ context.progress === undefined ||
516
+ typeof context.progress.report !== "function") {
517
+ throw managerError("LOCAL_CONTEXT_INVALID", "The local parse context is unavailable.");
518
+ }
519
+ return context;
520
+ }
521
+ function remoteContext(value) {
522
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
523
+ throw managerError("REMOTE_CONTEXT_INVALID", "The remote parse context is unavailable.");
524
+ }
525
+ const context = value;
526
+ if (typeof context.source !== "string") {
527
+ throw managerError("REMOTE_CONTEXT_INVALID", "The remote parse context is unavailable.");
528
+ }
529
+ return context;
530
+ }
531
+ function remoteFailure(result) {
532
+ const error = result.error;
533
+ return new OmniBridgeError({
534
+ code: error.code,
535
+ message: error.message,
536
+ ...(error.failure_scope === undefined ? {} : { failureScope: error.failure_scope }),
537
+ sourceKind: "url",
538
+ ...(error.user_action === undefined ? {} : { userAction: error.user_action }),
539
+ ...(error.request_id === undefined ? {} : { requestId: error.request_id }),
540
+ operationCreated: error.operation_created,
541
+ fileUploaded: error.file_uploaded,
542
+ parserStarted: error.parser_started,
543
+ billed: error.billed,
544
+ contentReleased: error.content_released,
545
+ retryable: error.retryable,
546
+ ...(error.retry_after === undefined ? {} : { retryAfter: error.retry_after }),
547
+ ...(error.constraints === undefined ? {} : { constraints: error.constraints }),
548
+ });
549
+ }
550
+ function remoteResultUpdate(result, expectedOperationId) {
551
+ if (result.status === "failed")
552
+ throw remoteFailure(result);
553
+ if (expectedOperationId !== undefined &&
554
+ result.operation_id !== expectedOperationId) {
555
+ throw managerError("REMOTE_PROTOCOL_ERROR", "The remote Omni response changed the operation identifier.", { operationCreated: true });
556
+ }
557
+ if (result.status === "processing") {
558
+ return {
559
+ state: "PROCESSING",
560
+ patch: {
561
+ operationId: result.operation_id,
562
+ errorCode: null,
563
+ stage: result.stage,
564
+ progressPercent: result.progress,
565
+ ...(result.progress_detail === undefined ? {} : { progress: result.progress_detail }),
566
+ processingCopy: result.data_handling.processing_copy,
567
+ temporaryData: result.data_handling.temporary_data,
568
+ deliveryResult: result.data_handling.delivery_result === "pending"
569
+ ? "pending"
570
+ : result.data_handling.delivery_result === "deleted_after_ack"
571
+ ? "deleted_after_ack"
572
+ : "not_created",
573
+ ...(typeof result.data_handling.delivery_result === "object"
574
+ ? { resultExpiresAt: result.data_handling.delivery_result.expires_at }
575
+ : {}),
576
+ },
577
+ result,
578
+ };
579
+ }
580
+ if (result.status === "completed") {
581
+ return {
582
+ state: "COMPLETED",
583
+ patch: {
584
+ operationId: result.operation_id,
585
+ errorCode: null,
586
+ parserStarted: true,
587
+ billed: true,
588
+ contentReleased: true,
589
+ stage: "completed",
590
+ progressPercent: 100,
591
+ processingCopy: "deleted",
592
+ temporaryData: "deleted",
593
+ deliveryResult: "deleted_after_ack",
594
+ },
595
+ result,
596
+ };
597
+ }
598
+ if (result.status === "cleanup_pending") {
599
+ return {
600
+ state: "CLEANUP_PENDING",
601
+ patch: {
602
+ operationId: result.operation_id,
603
+ errorCode: null,
604
+ parserStarted: true,
605
+ billed: true,
606
+ contentReleased: result.result !== undefined,
607
+ stage: "cleanup_pending",
608
+ progressPercent: result.result === undefined ? 99 : 100,
609
+ processingCopy: result.data_handling.processing_copy,
610
+ temporaryData: result.data_handling.temporary_data,
611
+ deliveryResult: "pending",
612
+ resultExpiresAt: result.cleanup_deadline,
613
+ },
614
+ result,
615
+ };
616
+ }
617
+ if (result.status === "canceled") {
618
+ return {
619
+ state: "CANCELED",
620
+ patch: {
621
+ operationId: result.operation_id,
622
+ errorCode: null,
623
+ stage: "canceled",
624
+ processingCopy: result.data_handling.processing_copy,
625
+ temporaryData: result.data_handling.temporary_data,
626
+ deliveryResult: result.data_handling.delivery_result === "pending"
627
+ ? "pending"
628
+ : result.data_handling.delivery_result === "deleted_after_ack"
629
+ ? "deleted_after_ack"
630
+ : "not_created",
631
+ ...(result.cleanup_deadline === undefined
632
+ ? {}
633
+ : { resultExpiresAt: result.cleanup_deadline }),
634
+ },
635
+ result,
636
+ };
637
+ }
638
+ return {
639
+ state: "EXPIRED",
640
+ patch: {
641
+ operationId: result.operation_id,
642
+ stage: "expired",
643
+ errorCode: "RESULT_EXPIRED",
644
+ },
645
+ result,
646
+ };
647
+ }
648
+ function localResultValue(local) {
649
+ if (local.kind === "inline")
650
+ return { kind: "inline", text: local.text };
651
+ if (local.nextCursor === undefined) {
652
+ throw managerError("LOCAL_RESULT_INTEGRITY_FAILED", "The local result artifact cursor is missing.", {
653
+ operationCreated: true,
654
+ fileUploaded: true,
655
+ parserStarted: true,
656
+ billed: true,
657
+ contentReleased: true,
658
+ });
659
+ }
660
+ return {
661
+ kind: "artifact",
662
+ result_id: local.resultId,
663
+ result_bytes: local.resultBytes,
664
+ expires_at: local.expiresAt,
665
+ preview: local.preview,
666
+ next_cursor: local.nextCursor,
667
+ };
668
+ }
669
+ function completedLocalParse(local) {
670
+ return {
671
+ status: "completed",
672
+ operation_id: local.operationId,
673
+ result: localResultValue(local),
674
+ data_handling: {
675
+ processing_copy: "deleted",
676
+ temporary_data: "deleted",
677
+ delivery_result: "deleted_after_ack",
678
+ original_source: "unchanged",
679
+ remote_content_retained: false,
680
+ },
681
+ ...(local.kind === "artifact" ? {
682
+ local_result_cache: {
683
+ expires_at: local.expiresAt,
684
+ discard_action: "discard_result",
685
+ },
686
+ } : {}),
687
+ };
688
+ }
689
+ function cleanupPendingParse(local, cleanupDeadline) {
690
+ return {
691
+ status: "cleanup_pending",
692
+ operation_id: local.operationId,
693
+ result: localResultValue(local),
694
+ cleanup_deadline: cleanupDeadline,
695
+ data_handling: {
696
+ processing_copy: "pending",
697
+ temporary_data: "pending",
698
+ delivery_result: "pending",
699
+ original_source: "unchanged",
700
+ },
701
+ };
702
+ }
703
+ function canceledParse(operationId, cleanupDeadline) {
704
+ return {
705
+ status: "canceled",
706
+ operation_id: operationId,
707
+ cleanup_deadline: cleanupDeadline,
708
+ data_handling: {
709
+ processing_copy: "pending",
710
+ temporary_data: "pending",
711
+ delivery_result: "not_created",
712
+ original_source: "unchanged",
713
+ },
714
+ };
715
+ }
716
+ function safeJournalStage(state) {
717
+ if (state === "UPLOADING")
718
+ return "uploading";
719
+ if (state === "PROCESSING")
720
+ return "parsing";
721
+ if (state === "RESULT_READY")
722
+ return "result_ready";
723
+ if (state === "ACK_PENDING")
724
+ return "ack_pending";
725
+ if (state === "CLEANUP_PENDING")
726
+ return "cleanup_pending";
727
+ if (state === "COMPLETED")
728
+ return "completed";
729
+ if (state === "FAILED")
730
+ return "failed";
731
+ if (state === "CANCELED")
732
+ return "canceled";
733
+ if (state === "EXPIRED")
734
+ return "expired";
735
+ return "preparing";
736
+ }
737
+ function safeOperationStage(status, fallback) {
738
+ if (status === "ISSUED" || status === "CLAIMED" || status === "UPLOADING") {
739
+ return "uploading";
740
+ }
741
+ if (status === "PROCESSING" || status === "SETTLING")
742
+ return "parsing";
743
+ if (status === "RELEASED")
744
+ return "result_ready";
745
+ if (status === "DELIVERED")
746
+ return "completed";
747
+ if (status === "CANCELED")
748
+ return "canceled";
749
+ if (status === "EXPIRED" || status === "DELIVERY_EXPIRED")
750
+ return "expired";
751
+ if (status === "FAILED" || status === "SETTLEMENT_DENIED")
752
+ return "failed";
753
+ return safeJournalStage(fallback);
754
+ }
755
+ function processingParse(operationId, stage, progress, contentReleased) {
756
+ return {
757
+ status: "processing",
758
+ operation_id: operationId,
759
+ stage,
760
+ progress: Math.min(99, Math.max(0, progress)),
761
+ next_action: "check_status",
762
+ poll_after_seconds: STATUS_POLL_AFTER_SECONDS,
763
+ can_cancel: true,
764
+ data_handling: {
765
+ processing_copy: contentReleased ? "pending" : "in_use",
766
+ temporary_data: contentReleased ? "pending" : "in_use",
767
+ delivery_result: contentReleased ? "pending" : "not_created",
768
+ original_source: "unchanged",
769
+ },
770
+ };
771
+ }
772
+ function localFailure(error) {
773
+ return error instanceof OmniBridgeError
774
+ ? error
775
+ : managerError("BRIDGE_INTERNAL_ERROR", "The local Omni operation could not complete.", { operationCreated: true });
776
+ }
777
+ export function createLocalParseOperationManager(options) {
778
+ const now = options.now ?? (() => new Date());
779
+ const openFile = options.openFile ?? openAllowedFile;
780
+ async function persistCheckpoint(clientRequestId, update) {
781
+ for (let attempt = 0; attempt < 4; attempt += 1) {
782
+ const current = await options.journal.loadByRequestId(clientRequestId);
783
+ if (current === null) {
784
+ throw managerError("JOURNAL_RECORD_NOT_FOUND", "The local operation journal record is missing.", { operationCreated: true, retryable: true });
785
+ }
786
+ if (TERMINAL_STATES.has(current.state))
787
+ return current;
788
+ try {
789
+ return await options.journal.transition(clientRequestId, current.state, update.state, update.patch ?? {});
790
+ }
791
+ catch (error) {
792
+ if (!(error instanceof OmniBridgeError) || error.code !== "JOURNAL_STATE_CONFLICT") {
793
+ throw error;
794
+ }
795
+ }
796
+ }
797
+ throw managerError("JOURNAL_STATE_CONFLICT", "The operation changed repeatedly while saving a delivery checkpoint.", { operationCreated: true, retryable: true });
798
+ }
799
+ async function recoverLocalArtifact(record) {
800
+ if (record.operationId === null || record.resultId === null) {
801
+ throw managerError("RESULT_NOT_AVAILABLE", "The retained local result artifact is unavailable.", {
802
+ operationCreated: record.operationId !== null,
803
+ fileUploaded: record.fileUploaded,
804
+ parserStarted: record.parserStarted,
805
+ billed: record.billed,
806
+ contentReleased: record.contentReleased,
807
+ });
808
+ }
809
+ const preview = await options.artifactStore.read(record.resultId, undefined, 2_048);
810
+ if (preview.resultBytes <= INLINE_RESULT_MAX_BYTES) {
811
+ const recovered = preview.nextCursor === undefined
812
+ ? preview
813
+ : await options.artifactStore.read(record.resultId, undefined, INLINE_RESULT_MAX_BYTES);
814
+ if (recovered.nextCursor !== undefined) {
815
+ throw managerError("LOCAL_RESULT_INTEGRITY_FAILED", "The retained inline result was not fully readable.", {
816
+ operationCreated: true,
817
+ fileUploaded: record.fileUploaded,
818
+ parserStarted: record.parserStarted,
819
+ billed: record.billed,
820
+ contentReleased: record.contentReleased,
821
+ });
822
+ }
823
+ return {
824
+ kind: "inline",
825
+ operationId: record.operationId,
826
+ resultId: recovered.resultId,
827
+ resultBytes: recovered.resultBytes,
828
+ expiresAt: recovered.expiresAt,
829
+ text: recovered.text,
830
+ };
831
+ }
832
+ return {
833
+ kind: "artifact",
834
+ operationId: record.operationId,
835
+ resultId: preview.resultId,
836
+ resultBytes: preview.resultBytes,
837
+ expiresAt: preview.expiresAt,
838
+ preview: preview.text,
839
+ ...(preview.nextCursor === undefined ? {} : { nextCursor: preview.nextCursor }),
840
+ };
841
+ }
842
+ async function startExecution(input, recovery) {
843
+ const recoveryOrder = recovery === undefined ? undefined : STATE_ORDER.get(recovery.state);
844
+ const needsUploadContext = recovery === undefined ||
845
+ recoveryOrder === undefined ||
846
+ recoveryOrder <= STATE_ORDER.get("GRANT_ISSUED");
847
+ const context = needsUploadContext
848
+ ? localContext(input.context)
849
+ : input.context === undefined
850
+ ? undefined
851
+ : localContext(input.context);
852
+ let opened;
853
+ let granted;
854
+ if (needsUploadContext) {
855
+ opened = await openFile(context.source, {
856
+ workspace: options.workspace,
857
+ extraRoots: options.extraRoots,
858
+ });
859
+ const expectedFingerprint = input.sourceFacts.sourceFingerprint;
860
+ if (typeof expectedFingerprint === "string" &&
861
+ opened.sourceFingerprint !== expectedFingerprint) {
862
+ await opened.close().catch(() => undefined);
863
+ opened = undefined;
864
+ throw managerError("SOURCE_CHANGED_DURING_SUBMISSION", "The local source changed before the upload operation was created.", { retryable: true });
865
+ }
866
+ try {
867
+ granted = await options.cubeClient.createGrant({
868
+ contentLength: opened.size,
869
+ contentType: opened.contentType,
870
+ fileExtension: opened.safeExtension,
871
+ noStore: true,
872
+ output: "markdown",
873
+ }, input.clientRequestId, input.signal, { journal: false });
874
+ }
875
+ catch (error) {
876
+ await opened.close().catch(() => undefined);
877
+ throw error;
878
+ }
879
+ }
880
+ const operationId = granted?.operationId ?? recovery?.operationId;
881
+ const operationToken = granted?.operationToken ?? recovery?.operationToken;
882
+ if (operationId === null || operationId === undefined || operationToken === null || operationToken === undefined) {
883
+ await opened?.close().catch(() => undefined);
884
+ throw managerError("OPERATION_ID_UNAVAILABLE", "The recoverable operation identity is unavailable.", { operationCreated: recovery?.operationId !== null, retryable: true });
885
+ }
886
+ const controller = new AbortController();
887
+ const retention = options.artifactStore.createRetention();
888
+ let latestProgress = recovery?.progress ?? null;
889
+ let latestPercent = recovery?.progressPercent ?? 0;
890
+ let snapshot = {
891
+ state: recovery !== undefined && STATE_ORDER.get(recovery.state) >= STATE_ORDER.get("PROCESSING")
892
+ ? recovery.state
893
+ : "UPLOADING",
894
+ patch: {
895
+ operationId,
896
+ operationToken,
897
+ errorCode: null,
898
+ ...(granted === undefined ? {} : { expiresAt: granted.expiresAt }),
899
+ stage: recovery?.stage ?? "uploading",
900
+ progressPercent: latestPercent,
901
+ ...(latestProgress === null ? {} : { progress: latestProgress }),
902
+ },
903
+ };
904
+ const progress = {
905
+ async report(value, total, message, detail) {
906
+ const percent = total <= 0 ? 0 : Math.min(100, Math.max(0, value * 100 / total));
907
+ latestPercent = Math.max(latestPercent, percent);
908
+ if (detail !== undefined) {
909
+ latestProgress = latestProgress === null
910
+ ? detail
911
+ : latestProgress.unit !== detail.unit
912
+ ? latestProgress
913
+ : {
914
+ unit: detail.unit,
915
+ completed: Math.max(latestProgress.completed, detail.completed),
916
+ total: Math.max(latestProgress.total, detail.total),
917
+ };
918
+ }
919
+ const state = recovery !== undefined && STATE_ORDER.get(recovery.state) > STATE_ORDER.get("PROCESSING")
920
+ ? recovery.state
921
+ : latestPercent >= 40
922
+ ? "PROCESSING"
923
+ : "UPLOADING";
924
+ snapshot = {
925
+ state,
926
+ patch: {
927
+ stage: state === "UPLOADING" ? "uploading" : "parsing",
928
+ progressPercent: latestPercent,
929
+ ...(latestProgress === null ? {} : { progress: latestProgress }),
930
+ },
931
+ };
932
+ await (context?.progress ?? NOOP_PROGRESS).report(value, total, message, latestProgress ?? undefined);
933
+ },
934
+ };
935
+ const operation = {
936
+ operationId,
937
+ operationToken,
938
+ ...(granted === undefined || opened === undefined
939
+ ? {}
940
+ : {
941
+ parseGrant: granted.parseGrant,
942
+ uploadUrl: granted.uploadUrl,
943
+ expiresAt: granted.expiresAt,
944
+ openedFile: opened,
945
+ }),
946
+ retention,
947
+ progress,
948
+ signal: controller.signal,
949
+ };
950
+ let cancellationSnapshot;
951
+ let cancellationPromise;
952
+ const cancel = () => {
953
+ controller.abort();
954
+ if (cancellationPromise !== undefined)
955
+ return cancellationPromise;
956
+ cancellationPromise = (async () => {
957
+ if (options.iiisClient.cancelOperation === undefined)
958
+ return;
959
+ try {
960
+ cancellationSnapshot = await options.iiisClient.cancelOperation({
961
+ ...operation,
962
+ signal: undefined,
963
+ });
964
+ }
965
+ catch {
966
+ // Inspection below determines the factual state when cancellation is uncertain.
967
+ }
968
+ })();
969
+ return cancellationPromise;
970
+ };
971
+ const abort = () => { void cancel(); };
972
+ if (input.signal?.aborted)
973
+ abort();
974
+ else
975
+ input.signal?.addEventListener("abort", abort, { once: true });
976
+ const completed = (async () => {
977
+ try {
978
+ const deliveryRecovery = recovery?.state === "RESULT_READY" ||
979
+ recovery?.state === "ACK_PENDING" ||
980
+ recovery?.state === "CLEANUP_PENDING";
981
+ let local;
982
+ if (deliveryRecovery && recovery.resultId !== null) {
983
+ local = await recoverLocalArtifact(recovery);
984
+ }
985
+ else {
986
+ if (deliveryRecovery) {
987
+ await options.iiisClient.downloadResult(operation, progress);
988
+ }
989
+ else if (recovery?.state === "UPLOADING" || recovery?.state === "PROCESSING") {
990
+ await options.iiisClient.recoverAndWait(operation);
991
+ }
992
+ else {
993
+ await options.iiisClient.uploadAndWait(operation);
994
+ }
995
+ local = retention.result();
996
+ }
997
+ const cleanupDeadline = recovery?.resultExpiresAt ?? new Date(now().getTime() + DELIVERY_TTL_SECONDS * 1000).toISOString();
998
+ const resultPatch = {
999
+ fileUploaded: true,
1000
+ parserStarted: true,
1001
+ billed: true,
1002
+ contentReleased: true,
1003
+ stage: "result_ready",
1004
+ progressPercent: 100,
1005
+ resultId: local.resultId,
1006
+ resultExpiresAt: cleanupDeadline,
1007
+ deliveryResult: "pending",
1008
+ };
1009
+ snapshot = { state: "ACK_PENDING", patch: resultPatch };
1010
+ const checkpoint = await persistCheckpoint(input.clientRequestId, snapshot);
1011
+ if (checkpoint.state !== "ACK_PENDING") {
1012
+ throw managerError("OPERATION_ALREADY_TERMINAL", "The parse operation became terminal before result acknowledgement.", {
1013
+ operationCreated: true,
1014
+ fileUploaded: checkpoint.fileUploaded,
1015
+ parserStarted: checkpoint.parserStarted,
1016
+ billed: checkpoint.billed,
1017
+ contentReleased: checkpoint.contentReleased,
1018
+ });
1019
+ }
1020
+ try {
1021
+ await options.iiisClient.ack(operation);
1022
+ }
1023
+ catch {
1024
+ return {
1025
+ update: {
1026
+ state: "CLEANUP_PENDING",
1027
+ patch: {
1028
+ ...resultPatch,
1029
+ processingCopy: "pending",
1030
+ temporaryData: "pending",
1031
+ deliveryResult: "pending",
1032
+ },
1033
+ },
1034
+ result: cleanupPendingParse(local, cleanupDeadline),
1035
+ };
1036
+ }
1037
+ return {
1038
+ update: {
1039
+ state: "COMPLETED",
1040
+ patch: {
1041
+ ...resultPatch,
1042
+ processingCopy: "deleted",
1043
+ temporaryData: "deleted",
1044
+ deliveryResult: "deleted_after_ack",
1045
+ },
1046
+ },
1047
+ result: completedLocalParse(local),
1048
+ };
1049
+ }
1050
+ catch (error) {
1051
+ const stable = localFailure(error);
1052
+ if (controller.signal.aborted || stable.code === "CANCELED") {
1053
+ const confirmed = failurePatch(stable);
1054
+ let inspected;
1055
+ try {
1056
+ await cancellationPromise;
1057
+ inspected = cancellationSnapshot ??
1058
+ await options.iiisClient.inspectOperation(operation);
1059
+ }
1060
+ catch {
1061
+ const state = snapshot.state;
1062
+ const stage = safeJournalStage(state);
1063
+ return {
1064
+ update: {
1065
+ state,
1066
+ patch: { ...confirmed, errorCode: null, stage },
1067
+ },
1068
+ result: processingParse(operationId, stage, latestPercent, confirmed.contentReleased === true),
1069
+ };
1070
+ }
1071
+ const patch = {
1072
+ ...confirmed,
1073
+ fileUploaded: confirmed.fileUploaded === true || inspected.fileUploaded,
1074
+ parserStarted: confirmed.parserStarted === true || inspected.parserStarted,
1075
+ billed: confirmed.billed === true || inspected.billed,
1076
+ contentReleased: confirmed.contentReleased === true || inspected.contentReleased,
1077
+ errorCode: null,
1078
+ ...(inspected.expiresAt === null ? {} : { expiresAt: inspected.expiresAt }),
1079
+ };
1080
+ if (inspected.status === "CANCELED") {
1081
+ const cleanupDeadline = inspected.expiresAt ?? new Date(now().getTime() + DELIVERY_TTL_SECONDS * 1000).toISOString();
1082
+ return {
1083
+ update: {
1084
+ state: "CANCELED",
1085
+ patch: {
1086
+ ...patch,
1087
+ stage: "canceled",
1088
+ processingCopy: "pending",
1089
+ temporaryData: "pending",
1090
+ deliveryResult: "not_created",
1091
+ resultExpiresAt: cleanupDeadline,
1092
+ },
1093
+ },
1094
+ result: canceledParse(operationId, cleanupDeadline),
1095
+ };
1096
+ }
1097
+ if (inspected.status === "EXPIRED" || inspected.status === "DELIVERY_EXPIRED") {
1098
+ return {
1099
+ update: {
1100
+ state: "EXPIRED",
1101
+ patch: { ...patch, stage: "expired", errorCode: "RESULT_EXPIRED" },
1102
+ },
1103
+ result: {
1104
+ status: "expired",
1105
+ operation_id: operationId,
1106
+ requires_user_confirmation: true,
1107
+ },
1108
+ };
1109
+ }
1110
+ if (inspected.status === "FAILED" || inspected.status === "SETTLEMENT_DENIED") {
1111
+ const failed = managerError(inspected.status, `The Omni operation ended with status ${inspected.status}.`, {
1112
+ operationCreated: true,
1113
+ fileUploaded: patch.fileUploaded,
1114
+ parserStarted: patch.parserStarted,
1115
+ billed: patch.billed,
1116
+ contentReleased: patch.contentReleased,
1117
+ });
1118
+ return {
1119
+ update: {
1120
+ state: "FAILED",
1121
+ patch: { ...patch, stage: "failed", errorCode: inspected.status },
1122
+ },
1123
+ result: { status: "failed", error: failed.toJSON() },
1124
+ };
1125
+ }
1126
+ if (inspected.status === "DELIVERED") {
1127
+ const unavailable = managerError("RESULT_NOT_AVAILABLE", "The delivered result is no longer available locally.", {
1128
+ operationCreated: true,
1129
+ fileUploaded: patch.fileUploaded,
1130
+ parserStarted: patch.parserStarted,
1131
+ billed: patch.billed,
1132
+ contentReleased: patch.contentReleased,
1133
+ });
1134
+ return {
1135
+ update: {
1136
+ state: "FAILED",
1137
+ patch: {
1138
+ ...patch,
1139
+ stage: "failed",
1140
+ errorCode: unavailable.code,
1141
+ processingCopy: "deleted",
1142
+ temporaryData: "deleted",
1143
+ deliveryResult: "deleted_after_ack",
1144
+ },
1145
+ },
1146
+ result: { status: "failed", error: unavailable.toJSON() },
1147
+ };
1148
+ }
1149
+ const state = inspected.status === "RELEASED"
1150
+ ? "RESULT_READY"
1151
+ : inspected.status === "PROCESSING" || inspected.status === "SETTLING"
1152
+ ? "PROCESSING"
1153
+ : snapshot.state;
1154
+ const stage = safeJournalStage(state);
1155
+ return {
1156
+ update: { state, patch: { ...patch, stage } },
1157
+ result: processingParse(operationId, stage, latestPercent, patch.contentReleased === true),
1158
+ };
1159
+ }
1160
+ return {
1161
+ update: {
1162
+ state: "FAILED",
1163
+ patch: failurePatch(stable),
1164
+ },
1165
+ result: { status: "failed", error: stable.toJSON() },
1166
+ };
1167
+ }
1168
+ finally {
1169
+ input.signal?.removeEventListener("abort", abort);
1170
+ await opened?.close().catch(() => undefined);
1171
+ }
1172
+ })();
1173
+ return {
1174
+ initial: snapshot,
1175
+ completed,
1176
+ snapshot: () => snapshot,
1177
+ cancel,
1178
+ };
1179
+ }
1180
+ async function startRemote(input, recovery) {
1181
+ if (options.remoteClient === undefined) {
1182
+ throw managerError("REMOTE_PARSE_UNAVAILABLE", "Remote URL parsing is not available in this Bridge build.", { retryable: true });
1183
+ }
1184
+ const context = remoteContext(input.context);
1185
+ const result = await options.remoteClient.parse(context.source, input.clientRequestId, input.signal ?? new AbortController().signal);
1186
+ return remoteResultUpdate(result, recovery?.operationId ?? undefined);
1187
+ }
1188
+ const driver = {
1189
+ create: (input) => input.sourceKind === "url"
1190
+ ? startRemote(input)
1191
+ : startExecution(input),
1192
+ resume: (record, input) => input.sourceKind === "url"
1193
+ ? startRemote(input, record)
1194
+ : startExecution(input, record),
1195
+ status: async (record, waitMs, signal) => {
1196
+ if (record.operationId === null)
1197
+ return undefined;
1198
+ if (record.sourceKind === "url") {
1199
+ if (options.remoteClient === undefined)
1200
+ return undefined;
1201
+ return remoteResultUpdate(await options.remoteClient.status(record.operationId, waitMs, signal), record.operationId);
1202
+ }
1203
+ const order = STATE_ORDER.get(record.state);
1204
+ if (order === undefined || order < STATE_ORDER.get("UPLOADING"))
1205
+ return undefined;
1206
+ return startExecution({
1207
+ sourceKind: "local",
1208
+ sourceFacts: {},
1209
+ clientRequestId: record.clientRequestId,
1210
+ signal,
1211
+ }, record);
1212
+ },
1213
+ cancel: async (record, signal) => {
1214
+ if (record.operationId === null)
1215
+ return undefined;
1216
+ if (record.sourceKind === "url") {
1217
+ if (options.remoteClient === undefined)
1218
+ return undefined;
1219
+ return remoteResultUpdate(await options.remoteClient.cancel(record.operationId, signal), record.operationId);
1220
+ }
1221
+ if (record.operationToken === null)
1222
+ return undefined;
1223
+ const operation = {
1224
+ operationId: record.operationId,
1225
+ operationToken: record.operationToken,
1226
+ retention: {
1227
+ async reset() { },
1228
+ async begin() { },
1229
+ async write() { },
1230
+ async complete() { },
1231
+ async abort() { },
1232
+ },
1233
+ signal,
1234
+ };
1235
+ const inspected = options.iiisClient.cancelOperation === undefined
1236
+ ? await options.iiisClient.inspectOperation(operation)
1237
+ : await options.iiisClient.cancelOperation(operation);
1238
+ const patch = {
1239
+ fileUploaded: inspected.fileUploaded,
1240
+ parserStarted: inspected.parserStarted,
1241
+ billed: inspected.billed,
1242
+ contentReleased: inspected.contentReleased,
1243
+ stage: safeOperationStage(inspected.status, record.state),
1244
+ ...(inspected.expiresAt === null ? {} : { expiresAt: inspected.expiresAt }),
1245
+ };
1246
+ if (inspected.status === "CANCELED") {
1247
+ const cleanupDeadline = inspected.expiresAt ?? new Date(now().getTime() + DELIVERY_TTL_SECONDS * 1000).toISOString();
1248
+ return {
1249
+ state: "CANCELED",
1250
+ patch: {
1251
+ ...patch,
1252
+ processingCopy: "pending",
1253
+ temporaryData: "pending",
1254
+ deliveryResult: "not_created",
1255
+ resultExpiresAt: cleanupDeadline,
1256
+ },
1257
+ result: canceledParse(record.operationId, cleanupDeadline),
1258
+ };
1259
+ }
1260
+ if (inspected.status === "EXPIRED" || inspected.status === "DELIVERY_EXPIRED") {
1261
+ return {
1262
+ state: "EXPIRED",
1263
+ patch: { ...patch, errorCode: "RESULT_EXPIRED" },
1264
+ result: {
1265
+ status: "expired",
1266
+ operation_id: record.operationId,
1267
+ requires_user_confirmation: true,
1268
+ },
1269
+ };
1270
+ }
1271
+ if (inspected.status === "FAILED" || inspected.status === "SETTLEMENT_DENIED") {
1272
+ return {
1273
+ state: "FAILED",
1274
+ patch: { ...patch, errorCode: inspected.status },
1275
+ };
1276
+ }
1277
+ if (inspected.status === "DELIVERED") {
1278
+ return {
1279
+ state: "COMPLETED",
1280
+ patch: {
1281
+ ...patch,
1282
+ processingCopy: "deleted",
1283
+ temporaryData: "deleted",
1284
+ deliveryResult: "deleted_after_ack",
1285
+ },
1286
+ };
1287
+ }
1288
+ return {
1289
+ state: inspected.status === "RELEASED" ? "RESULT_READY" : record.state,
1290
+ patch,
1291
+ };
1292
+ },
1293
+ result: async (record, signal) => {
1294
+ if (record.operationId === null || record.state !== "COMPLETED")
1295
+ return undefined;
1296
+ if (record.sourceKind === "local" && record.resultId !== null) {
1297
+ return completedLocalParse(await recoverLocalArtifact(record));
1298
+ }
1299
+ if (record.sourceKind === "url" && options.remoteClient !== undefined) {
1300
+ return await options.remoteClient.status(record.operationId, 0, signal);
1301
+ }
1302
+ return undefined;
1303
+ },
1304
+ };
1305
+ return new OperationManager({
1306
+ journal: options.journal,
1307
+ driver,
1308
+ now: () => now().getTime(),
1309
+ sleep: options.sleep,
1310
+ });
1311
+ }