@cueai/omni-reader-mcp 1.5.5 → 1.6.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.
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
5
5
  import path from "node:path";
6
6
  import { MAX_FILE_BYTES } from "./constants.js";
7
7
  import { OmniBridgeError } from "./errors.js";
8
+ import { PathNormalizationError, normalizePlatformPath, } from "./path-normalization.js";
8
9
  const CONTENT_TYPES = {
9
10
  ".pdf": "application/pdf",
10
11
  ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -92,29 +93,44 @@ function bridgeError(code, message, retryable, constraints) {
92
93
  ...(constraints === undefined ? {} : { constraints }),
93
94
  });
94
95
  }
96
+ function pathsFor(platform) {
97
+ return platform === "win32" ? path.win32 : path.posix;
98
+ }
95
99
  function validatePathText(value) {
96
100
  if (value.length === 0 || value.includes("\0")) {
97
101
  throw bridgeError("INVALID_LOCAL_PATH", "Provide one valid local file path.", false);
98
102
  }
99
103
  }
100
- function expandHomeOnly(value, homeDirectory) {
104
+ function normalizedPath(value, platform) {
105
+ validatePathText(value);
106
+ try {
107
+ return normalizePlatformPath(value, platform);
108
+ }
109
+ catch (error) {
110
+ if (error instanceof PathNormalizationError) {
111
+ throw bridgeError(error.code, error.message, false);
112
+ }
113
+ throw error;
114
+ }
115
+ }
116
+ function expandHomeOnly(value, homeDirectory, paths) {
101
117
  if (value === "~") {
102
118
  return homeDirectory;
103
119
  }
104
120
  if (/^~[\\/]/u.test(value)) {
105
- return path.join(homeDirectory, value.slice(2));
121
+ return paths.join(homeDirectory, value.slice(2));
106
122
  }
107
123
  return value;
108
124
  }
109
- function isInsideRoot(candidate, root) {
110
- const relative = path.relative(root, candidate);
125
+ function isInsideRoot(candidate, root, paths) {
126
+ const relative = paths.relative(root, candidate);
111
127
  return (relative === "" ||
112
128
  (relative !== ".." &&
113
- !relative.startsWith(`..${path.sep}`) &&
114
- !path.isAbsolute(relative)));
129
+ !relative.startsWith(`..${paths.sep}`) &&
130
+ !paths.isAbsolute(relative)));
115
131
  }
116
- function requireInsideAnyRoot(candidate, roots) {
117
- if (!roots.some((root) => isInsideRoot(candidate, root))) {
132
+ function requireInsideAnyRoot(candidate, roots, paths) {
133
+ if (!roots.some((root) => isInsideRoot(candidate, root, paths))) {
118
134
  throw bridgeError("PATH_NOT_ALLOWED", "Move the file into the current workspace or add its parent directory to OMNI_ALLOWED_ROOTS.", false);
119
135
  }
120
136
  }
@@ -148,8 +164,8 @@ function redactedSourceFingerprint(fileStat) {
148
164
  ].map((value) => value.toString()).join(":");
149
165
  return `sha256:${createHash("sha256").update(identity, "utf8").digest("hex")}`;
150
166
  }
151
- function safeFileType(resolvedPath) {
152
- const safeExtension = path.extname(resolvedPath).toLowerCase();
167
+ function safeFileType(resolvedPath, paths) {
168
+ const safeExtension = paths.extname(resolvedPath).toLowerCase();
153
169
  if (!/^\.[a-z0-9]{1,10}$/u.test(safeExtension)) {
154
170
  throw bridgeError("UNSUPPORTED_MEDIA_TYPE", "The source media type is not supported.", false, { supported_extensions: SUPPORTED_EXTENSIONS });
155
171
  }
@@ -175,19 +191,20 @@ function fileAccessError(error) {
175
191
  }
176
192
  return bridgeError("FILE_NOT_READABLE", "Omni cannot read the local file. Check its permissions and try again.", false);
177
193
  }
178
- async function resolveAllowedRoots(options, fileSystem, homeDirectory) {
179
- validatePathText(options.workspace);
180
- const workspaceInput = path.resolve(expandHomeOnly(options.workspace, homeDirectory));
194
+ async function resolveAllowedRoots(options, fileSystem, homeDirectory, platform, paths) {
195
+ const workspace = normalizedPath(options.workspace, platform);
196
+ const expandedWorkspace = expandHomeOnly(workspace, homeDirectory, paths);
197
+ const workspaceInput = paths.resolve(normalizedPath(expandedWorkspace, platform));
181
198
  const requestedRoots = [
182
199
  workspaceInput,
183
200
  ...(options.extraRoots ?? [])
184
201
  .filter((root) => root.length > 0)
185
202
  .map((root) => {
186
- validatePathText(root);
187
- const expanded = expandHomeOnly(root, homeDirectory);
188
- return path.isAbsolute(expanded)
203
+ const normalizedRoot = normalizedPath(root, platform);
204
+ const expanded = normalizedPath(expandHomeOnly(normalizedRoot, homeDirectory, paths), platform);
205
+ return paths.isAbsolute(expanded)
189
206
  ? expanded
190
- : path.resolve(workspaceInput, expanded);
207
+ : paths.resolve(workspaceInput, expanded);
191
208
  }),
192
209
  ];
193
210
  try {
@@ -199,7 +216,7 @@ async function resolveAllowedRoots(options, fileSystem, homeDirectory) {
199
216
  }
200
217
  return resolved;
201
218
  }));
202
- return [...new Set(roots)];
219
+ return { roots: [...new Set(roots)], workspaceInput };
203
220
  }
204
221
  catch (error) {
205
222
  if (error instanceof OmniBridgeError) {
@@ -208,21 +225,25 @@ async function resolveAllowedRoots(options, fileSystem, homeDirectory) {
208
225
  throw bridgeError("INVALID_ALLOWED_ROOT", "The workspace or an OMNI_ALLOWED_ROOTS entry is unavailable.", false);
209
226
  }
210
227
  }
211
- export function splitAllowedRoots(value) {
228
+ export function splitAllowedRoots(value, platform = process.platform) {
212
229
  if (value === undefined || value.length === 0) {
213
230
  return [];
214
231
  }
215
- return value.split(path.delimiter).filter((root) => root.length > 0);
232
+ return value
233
+ .split(pathsFor(platform).delimiter)
234
+ .filter((root) => root.length > 0);
216
235
  }
217
236
  export async function openAllowedFile(input, options) {
218
- validatePathText(input);
237
+ const platform = options.platform ?? process.platform;
238
+ const paths = pathsFor(platform);
219
239
  const fileSystem = options.fileSystem ?? NODE_FILE_SYSTEM;
220
240
  const homeDirectory = options.homeDirectory ?? homedir();
221
- const roots = await resolveAllowedRoots(options, fileSystem, homeDirectory);
222
- const expanded = expandHomeOnly(input, homeDirectory);
223
- const candidate = path.isAbsolute(expanded)
241
+ const normalizedInput = normalizedPath(input, platform);
242
+ const expanded = normalizedPath(expandHomeOnly(normalizedInput, homeDirectory, paths), platform);
243
+ const { roots, workspaceInput } = await resolveAllowedRoots(options, fileSystem, homeDirectory, platform, paths);
244
+ const candidate = paths.isAbsolute(expanded)
224
245
  ? expanded
225
- : path.resolve(options.workspace, expanded);
246
+ : paths.resolve(workspaceInput, expanded);
226
247
  let resolved;
227
248
  try {
228
249
  resolved = await fileSystem.realpath(candidate);
@@ -230,7 +251,7 @@ export async function openAllowedFile(input, options) {
230
251
  catch (error) {
231
252
  throw fileAccessError(error);
232
253
  }
233
- requireInsideAnyRoot(resolved, roots);
254
+ requireInsideAnyRoot(resolved, roots, paths);
234
255
  let handle;
235
256
  try {
236
257
  const pathStat = await fileSystem.stat(resolved);
@@ -244,11 +265,11 @@ export async function openAllowedFile(input, options) {
244
265
  requireSameFile(pathStat, before);
245
266
  const size = requireAllowedSize(before);
246
267
  const afterPath = await fileSystem.realpath(resolved);
247
- requireInsideAnyRoot(afterPath, roots);
268
+ requireInsideAnyRoot(afterPath, roots, paths);
248
269
  const after = await fileSystem.stat(afterPath);
249
270
  requireRegularFile(after);
250
271
  requireSameFile(before, after);
251
- const { safeExtension, contentType } = safeFileType(afterPath);
272
+ const { safeExtension, contentType } = safeFileType(afterPath, paths);
252
273
  const opened = new DescriptorBackedFile(handle, size, safeExtension, contentType, redactedSourceFingerprint(before));
253
274
  handle = undefined;
254
275
  return opened;
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
- export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
3
- export declare const OPERATION_JOURNAL_VERSION = 3;
2
+ export declare const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v4";
3
+ export declare const OPERATION_JOURNAL_VERSION = 4;
4
4
  export declare const BUNDLE_CURSOR_VERSION = 2;
5
5
  export declare const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
6
6
  export declare const GROUNDING_SCHEMA_VERSION = "omni.grounding.v1";
7
+ export type ResultDelivery = "auto" | "artifact";
7
8
  export interface RepresentationIntent {
8
9
  readonly detail: "text" | "grounded" | "layout";
9
10
  readonly groundingSchemaVersion: "none" | "omni.grounding.v1";
@@ -15,31 +16,38 @@ export declare const parseSchemaShape: {
15
16
  source: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
16
17
  url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
17
18
  detail: z.ZodOptional<z.ZodEnum<["text", "grounded", "layout"]>>;
19
+ result_delivery: z.ZodOptional<z.ZodEnum<["auto", "artifact"]>>;
18
20
  };
19
21
  export declare const parseSchema: z.ZodEffects<z.ZodObject<{
20
22
  source: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
21
23
  url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
22
24
  detail: z.ZodOptional<z.ZodEnum<["text", "grounded", "layout"]>>;
25
+ result_delivery: z.ZodOptional<z.ZodEnum<["auto", "artifact"]>>;
23
26
  }, "strict", z.ZodTypeAny, {
24
27
  source?: string | undefined;
25
28
  url?: string | undefined;
26
29
  detail?: "grounded" | "layout" | "text" | undefined;
30
+ result_delivery?: "auto" | "artifact" | undefined;
27
31
  }, {
28
32
  source?: string | undefined;
29
33
  url?: string | undefined;
30
34
  detail?: "grounded" | "layout" | "text" | undefined;
35
+ result_delivery?: "auto" | "artifact" | undefined;
31
36
  }>, {
32
37
  source?: string | undefined;
33
38
  url?: string | undefined;
34
39
  detail?: "grounded" | "layout" | "text" | undefined;
40
+ result_delivery?: "auto" | "artifact" | undefined;
35
41
  }, {
36
42
  source?: string | undefined;
37
43
  url?: string | undefined;
38
44
  detail?: "grounded" | "layout" | "text" | undefined;
45
+ result_delivery?: "auto" | "artifact" | undefined;
39
46
  }>;
40
47
  export interface NormalizedParseArguments {
41
48
  readonly source: string;
42
49
  readonly detail?: "text" | "grounded" | "layout";
50
+ readonly resultDelivery: ResultDelivery;
43
51
  }
44
52
  export declare function normalizeParseArguments(args: ParseArguments): NormalizedParseArguments;
45
53
  export declare const getParseStatusSchema: z.ZodObject<{
package/dist/protocol.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { RESULT_CHUNK_MAX_BYTES, STATUS_LONG_POLL_MAX_MS } from "./constants.js";
3
- export const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
4
- // D2-D item 1: versioned persistence literals frozen with the v3 contract.
5
- // The journal record version and the bundle cursor version are bumped
6
- // together with the tool contract; legacy v2 journal records still recover
7
- // text operations unchanged and are never auto-upgraded.
8
- export const OPERATION_JOURNAL_VERSION = 3;
3
+ export const LOCAL_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v4";
4
+ export const OPERATION_JOURNAL_VERSION = 4;
9
5
  export const BUNDLE_CURSOR_VERSION = 2;
10
6
  // Canonical bundle/grounding literals carried by non-text representations.
11
7
  export const RESULT_BUNDLE_PROTOCOL_VERSION = "omni.result_bundle.v1";
@@ -36,6 +32,17 @@ export const MACHINE_INSTRUCTIONS = [
36
32
  "Do not promise background notification when the client lacks task support.",
37
33
  "Do not claim deletion before cleanup is confirmed.",
38
34
  "Report authoritative unit progress when present; never treat partial output as final.",
35
+ "Answer directly → use inline text when present; otherwise read_result",
36
+ "Find one section → read_outline, then pass its cursor to read_result",
37
+ "Read all content → read_result until next_cursor is absent",
38
+ "Deliver a file → save_result",
39
+ "Outline navigation does not require save_result.",
40
+ "Use result_delivery=artifact for saving, section navigation, multiple documents, or strict context control.",
41
+ "Use bounded concurrent independent parse calls for multiple sources; there is no batch_parse tool.",
42
+ "Discover MCP Tasks, MCP Roots, host tool timeout, and process cwd versus active workspace; do not guess client capabilities.",
43
+ "Tasks unknown → use ordinary parse and get_parse_status polling.",
44
+ "Roots unknown → use process cwd and explicitly configured roots only.",
45
+ "Host timeout unknown → retain the bounded 20-second status wait.",
39
46
  "After parsing, continue the user's original task.",
40
47
  ].join("\n");
41
48
  const operationIdSchema = z.string().regex(/^op_[A-Za-z0-9_-]{16,64}$/u);
@@ -60,6 +67,7 @@ const parseSchemaObject = z
60
67
  .refine((value) => !value.includes("\0"))
61
68
  .optional(),
62
69
  detail: z.enum(["text", "grounded", "layout"]).optional(),
70
+ result_delivery: z.enum(["auto", "artifact"]).optional(),
63
71
  })
64
72
  .strict();
65
73
  export const parseSchemaShape = parseSchemaObject.shape;
@@ -71,7 +79,11 @@ export function normalizeParseArguments(args) {
71
79
  // schema-validated input and guards misuse of the exported helper.
72
80
  throw new Error("parse arguments require exactly one of source or url");
73
81
  }
74
- return { source, detail: args.detail };
82
+ return {
83
+ source,
84
+ detail: args.detail,
85
+ resultDelivery: args.result_delivery ?? "auto",
86
+ };
75
87
  }
76
88
  export const getParseStatusSchema = z
77
89
  .object({
@@ -2,6 +2,7 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { z } from "zod";
3
3
  import type { LocalResult } from "./artifact-store.js";
4
4
  import { type OmniBridgeErrorPayload } from "./errors.js";
5
+ import { type ResultDelivery } from "./protocol.js";
5
6
  export interface DataHandling {
6
7
  processing_copy: "in_use" | "pending" | "deleted";
7
8
  temporary_data: "in_use" | "pending" | "deleted";
@@ -11,6 +12,25 @@ export interface DataHandling {
11
12
  original_source: "unchanged";
12
13
  remote_content_retained?: false;
13
14
  }
15
+ export declare const FLAT_RESULT_ACTIONS: readonly ["read_result", "read_outline", "save_result", "discard_result"];
16
+ export declare const LOCAL_ARTIFACT_RETENTION_WARNING: "LOCAL_ARTIFACT_RETENTION_FAILED";
17
+ export interface FlatLocalResultCache {
18
+ readonly result_id: string;
19
+ readonly result_bytes: number;
20
+ readonly expires_at: string;
21
+ readonly available_actions: typeof FLAT_RESULT_ACTIONS;
22
+ readonly discard_action: "discard_result";
23
+ }
24
+ export interface LegacyBundleLocalResultCache {
25
+ readonly expires_at: string;
26
+ readonly discard_action: "discard_result";
27
+ }
28
+ export type LocalResultCache = FlatLocalResultCache | LegacyBundleLocalResultCache;
29
+ export declare function createFlatLocalResultCache(input: {
30
+ readonly resultId: string;
31
+ readonly resultBytes: number;
32
+ readonly expiresAt: string;
33
+ }): FlatLocalResultCache;
14
34
  export type ParseResult = {
15
35
  status: "processing";
16
36
  operation_id: string;
@@ -44,10 +64,8 @@ export type ParseResult = {
44
64
  credits_remaining: number;
45
65
  };
46
66
  data_handling: DataHandling;
47
- local_result_cache?: {
48
- expires_at: string;
49
- discard_action: "discard_result";
50
- };
67
+ local_result_cache?: LocalResultCache;
68
+ delivery_warning?: typeof LOCAL_ARTIFACT_RETENTION_WARNING;
51
69
  } | {
52
70
  status: "cleanup_pending";
53
71
  operation_id: string;
@@ -64,6 +82,8 @@ export type ParseResult = {
64
82
  };
65
83
  cleanup_deadline: string;
66
84
  data_handling: DataHandling;
85
+ local_result_cache?: LocalResultCache;
86
+ delivery_warning?: typeof LOCAL_ARTIFACT_RETENTION_WARNING;
67
87
  } | {
68
88
  status: "failed";
69
89
  error: OmniBridgeErrorPayload;
@@ -132,7 +152,7 @@ export declare const stableErrorSchema: z.ZodObject<{
132
152
  ok: false;
133
153
  operation_created: boolean;
134
154
  failure_scope?: "source" | "local_capability" | "authentication" | "billing" | "service" | "parser" | "operation" | "cleanup" | "bridge" | undefined;
135
- source_kind?: "local" | "url" | undefined;
155
+ source_kind?: "url" | "local" | undefined;
136
156
  user_action?: string | undefined;
137
157
  request_id?: string | undefined;
138
158
  retry_after?: number | undefined;
@@ -151,7 +171,7 @@ export declare const stableErrorSchema: z.ZodObject<{
151
171
  ok: false;
152
172
  operation_created: boolean;
153
173
  failure_scope?: "source" | "local_capability" | "authentication" | "billing" | "service" | "parser" | "operation" | "cleanup" | "bridge" | undefined;
154
- source_kind?: "local" | "url" | undefined;
174
+ source_kind?: "url" | "local" | undefined;
155
175
  user_action?: string | undefined;
156
176
  request_id?: string | undefined;
157
177
  retry_after?: number | undefined;
@@ -454,6 +474,25 @@ export declare const bundleDescriptorSchema: z.ZodObject<{
454
474
  };
455
475
  };
456
476
  }>;
477
+ export declare const flatLocalResultCacheSchema: z.ZodObject<{
478
+ result_id: z.ZodString;
479
+ result_bytes: z.ZodNumber;
480
+ expires_at: z.ZodString;
481
+ available_actions: z.ZodTuple<[z.ZodLiteral<"read_result">, z.ZodLiteral<"read_outline">, z.ZodLiteral<"save_result">, z.ZodLiteral<"discard_result">], null>;
482
+ discard_action: z.ZodLiteral<"discard_result">;
483
+ }, "strict", z.ZodTypeAny, {
484
+ result_id: string;
485
+ expires_at: string;
486
+ result_bytes: number;
487
+ available_actions: ["read_result", "read_outline", "save_result", "discard_result"];
488
+ discard_action: "discard_result";
489
+ }, {
490
+ result_id: string;
491
+ expires_at: string;
492
+ result_bytes: number;
493
+ available_actions: ["read_result", "read_outline", "save_result", "discard_result"];
494
+ discard_action: "discard_result";
495
+ }>;
457
496
  export declare const parseResultSchema: z.ZodTypeAny;
458
497
  export declare const bundlePartReadSchema: z.ZodObject<{
459
498
  status: z.ZodLiteral<"completed">;
@@ -614,4 +653,5 @@ export declare const parseToolOutputSchema: z.ZodTypeAny;
614
653
  export declare const readResultToolOutputSchema: z.ZodTypeAny;
615
654
  export declare const discardResultToolOutputSchema: z.ZodTypeAny;
616
655
  export declare const saveResultToolOutputSchema: z.ZodTypeAny;
656
+ export declare function presentRetainedResult(result: ParseResult, resultDelivery: ResultDelivery, mintReadCursor: (resultId: string, byteOffset: number) => Promise<string>): Promise<ParseResult>;
617
657
  export declare function structuredResult(schema: z.ZodTypeAny, value: unknown): CallToolResult;
@@ -1,6 +1,22 @@
1
1
  import { z } from "zod";
2
2
  import { OmniBridgeError } from "./errors.js";
3
- import { RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
3
+ import { RESULT_BUNDLE_PROTOCOL_VERSION, } from "./protocol.js";
4
+ export const FLAT_RESULT_ACTIONS = [
5
+ "read_result",
6
+ "read_outline",
7
+ "save_result",
8
+ "discard_result",
9
+ ];
10
+ export const LOCAL_ARTIFACT_RETENTION_WARNING = "LOCAL_ARTIFACT_RETENTION_FAILED";
11
+ export function createFlatLocalResultCache(input) {
12
+ return {
13
+ result_id: input.resultId,
14
+ result_bytes: input.resultBytes,
15
+ expires_at: input.expiresAt,
16
+ available_actions: FLAT_RESULT_ACTIONS,
17
+ discard_action: "discard_result",
18
+ };
19
+ }
4
20
  // Maps a retained LocalResult (ArtifactStore's own representation) into the
5
21
  // wire ParseResult["result"] shape. Used both for local-file parses
6
22
  // (tools.ts's completedLocalResult, which additionally sets its own
@@ -197,19 +213,38 @@ export const bundleDescriptorSchema = z
197
213
  .strict(),
198
214
  })
199
215
  .strict();
200
- const localResultCacheSchema = z
216
+ export const flatLocalResultCacheSchema = z
201
217
  .object({
218
+ result_id: resultIdSchema,
219
+ result_bytes: z.number().int().nonnegative(),
202
220
  expires_at: z.string().datetime({ offset: true }),
221
+ available_actions: z.tuple([
222
+ z.literal("read_result"),
223
+ z.literal("read_outline"),
224
+ z.literal("save_result"),
225
+ z.literal("discard_result"),
226
+ ]),
203
227
  discard_action: z.literal("discard_result"),
204
228
  })
205
229
  .strict();
230
+ const legacyBundleLocalResultCacheSchema = z
231
+ .object({
232
+ expires_at: z.string().datetime({ offset: true }),
233
+ discard_action: z.literal("discard_result"),
234
+ })
235
+ .strict();
236
+ const localResultCacheSchema = z.union([
237
+ flatLocalResultCacheSchema,
238
+ legacyBundleLocalResultCacheSchema,
239
+ ]);
240
+ const deliveryWarningSchema = z.literal(LOCAL_ARTIFACT_RETENTION_WARNING);
206
241
  const failedResultSchema = z
207
242
  .object({
208
243
  status: z.literal("failed"),
209
244
  error: stableErrorSchema,
210
245
  })
211
246
  .strict();
212
- export const parseResultSchema = z.discriminatedUnion("status", [
247
+ const parseResultDiscriminatedSchema = z.discriminatedUnion("status", [
213
248
  z
214
249
  .object({
215
250
  status: z.literal("processing"),
@@ -235,6 +270,7 @@ export const parseResultSchema = z.discriminatedUnion("status", [
235
270
  billing: billingSchema.optional(),
236
271
  data_handling: dataHandlingSchema,
237
272
  local_result_cache: localResultCacheSchema.optional(),
273
+ delivery_warning: deliveryWarningSchema.optional(),
238
274
  })
239
275
  .strict(),
240
276
  z
@@ -248,6 +284,8 @@ export const parseResultSchema = z.discriminatedUnion("status", [
248
284
  ]).optional(),
249
285
  cleanup_deadline: z.string().datetime({ offset: true }),
250
286
  data_handling: dataHandlingSchema,
287
+ local_result_cache: localResultCacheSchema.optional(),
288
+ delivery_warning: deliveryWarningSchema.optional(),
251
289
  })
252
290
  .strict(),
253
291
  failedResultSchema,
@@ -269,6 +307,41 @@ export const parseResultSchema = z.discriminatedUnion("status", [
269
307
  })
270
308
  .strict(),
271
309
  ]);
310
+ export const parseResultSchema = parseResultDiscriminatedSchema
311
+ .superRefine((value, context) => {
312
+ if (value.status !== "completed" && value.status !== "cleanup_pending")
313
+ return;
314
+ const result = value.result;
315
+ const cache = value.local_result_cache;
316
+ if (cache !== undefined) {
317
+ const cacheMatches = result !== undefined && (result.kind === "bundle"
318
+ ? legacyBundleLocalResultCacheSchema.safeParse(cache).success
319
+ : flatLocalResultCacheSchema.safeParse(cache).success);
320
+ if (!cacheMatches) {
321
+ context.addIssue({
322
+ code: z.ZodIssueCode.custom,
323
+ path: ["local_result_cache"],
324
+ message: "the local result cache does not match the result shape",
325
+ });
326
+ }
327
+ }
328
+ if (value.delivery_warning !== undefined) {
329
+ if (cache !== undefined) {
330
+ context.addIssue({
331
+ code: z.ZodIssueCode.custom,
332
+ path: ["local_result_cache"],
333
+ message: "a retention warning cannot advertise a local result cache",
334
+ });
335
+ }
336
+ if (result === undefined || result.kind !== "inline") {
337
+ context.addIssue({
338
+ code: z.ZodIssueCode.custom,
339
+ path: ["delivery_warning"],
340
+ message: "a retention warning requires delivered inline content",
341
+ });
342
+ }
343
+ }
344
+ });
272
345
  const resultChunkSchema = z
273
346
  .object({
274
347
  result_id: resultIdSchema,
@@ -419,6 +492,7 @@ export const parseToolOutputSchema = z
419
492
  billing: billingSchema.optional(),
420
493
  data_handling: dataHandlingSchema.optional(),
421
494
  local_result_cache: localResultCacheSchema.optional(),
495
+ delivery_warning: deliveryWarningSchema.optional(),
422
496
  cleanup_deadline: z.string().datetime({ offset: true }).optional(),
423
497
  error: stableErrorSchema.optional(),
424
498
  requires_user_confirmation: z.literal(true).optional(),
@@ -449,6 +523,47 @@ export const saveResultToolOutputSchema = z
449
523
  error: stableErrorSchema.optional(),
450
524
  })
451
525
  .strict();
526
+ export async function presentRetainedResult(result, resultDelivery, mintReadCursor) {
527
+ if (resultDelivery === "auto" ||
528
+ (result.status !== "completed" && result.status !== "cleanup_pending") ||
529
+ result.result === undefined) {
530
+ return result;
531
+ }
532
+ const current = result.result;
533
+ if (current.kind === "bundle" || current.kind === "artifact")
534
+ return result;
535
+ if (current.kind !== "inline")
536
+ return result;
537
+ const parsedCache = flatLocalResultCacheSchema.safeParse(result.local_result_cache);
538
+ if (!parsedCache.success) {
539
+ if (result.delivery_warning === LOCAL_ARTIFACT_RETENTION_WARNING)
540
+ return result;
541
+ throw new OmniBridgeError({
542
+ code: "LOCAL_RESULT_INTEGRITY_FAILED",
543
+ failureScope: "bridge",
544
+ message: "The retained inline result descriptor is unavailable.",
545
+ operationCreated: false,
546
+ fileUploaded: false,
547
+ parserStarted: false,
548
+ billed: false,
549
+ contentReleased: false,
550
+ retryable: false,
551
+ });
552
+ }
553
+ const cache = parsedCache.data;
554
+ const nextCursor = await mintReadCursor(cache.result_id, 0);
555
+ return {
556
+ ...result,
557
+ result: {
558
+ kind: "artifact",
559
+ result_id: cache.result_id,
560
+ result_bytes: cache.result_bytes,
561
+ expires_at: cache.expires_at,
562
+ preview: "",
563
+ next_cursor: nextCursor,
564
+ },
565
+ };
566
+ }
452
567
  function storageText(result) {
453
568
  const parts = result.parts;
454
569
  if (parts === null || typeof parts !== "object" || Array.isArray(parts)) {
@@ -1,13 +1,14 @@
1
1
  import type { RequestTaskStore } from "@modelcontextprotocol/sdk/shared/protocol.js";
2
+ import type { NormalizedParseArguments } from "./protocol.js";
2
3
  import { type ParseResult } from "./result-contract.js";
3
4
  export interface TaskRuntimeOperations {
4
- parse(source: string, signal: AbortSignal): Promise<ParseResult>;
5
+ parse(args: NormalizedParseArguments, signal: AbortSignal): Promise<ParseResult>;
5
6
  status(operationId: string, waitMs: number, signal: AbortSignal): Promise<ParseResult>;
6
7
  cancel(operationId: string, signal: AbortSignal): Promise<ParseResult>;
7
8
  }
8
9
  export declare class TaskRuntime {
9
10
  #private;
10
11
  constructor(operations: TaskRuntimeOperations);
11
- start(taskId: string, source: string, store: RequestTaskStore): Promise<void>;
12
+ start(taskId: string, args: NormalizedParseArguments, store: RequestTaskStore): Promise<void>;
12
13
  cancel(taskId: string): Promise<void>;
13
14
  }
@@ -39,7 +39,7 @@ export class TaskRuntime {
39
39
  constructor(operations) {
40
40
  this.#operations = operations;
41
41
  }
42
- async start(taskId, source, store) {
42
+ async start(taskId, args, store) {
43
43
  if (this.#running.has(taskId)) {
44
44
  throw new Error(`Task ${taskId} is already running.`);
45
45
  }
@@ -48,7 +48,7 @@ export class TaskRuntime {
48
48
  };
49
49
  this.#running.set(taskId, running);
50
50
  try {
51
- let result = await this.#operations.parse(source, running.controller.signal);
51
+ let result = await this.#operations.parse(args, running.controller.signal);
52
52
  while (result.status === "processing") {
53
53
  running.operationId = result.operation_id;
54
54
  if (await isCancelled(store, taskId)) {
package/dist/tools.d.ts CHANGED
@@ -6,6 +6,7 @@ import { type CubeGrantClient } from "./cube-client.js";
6
6
  import type { IiisClient, ResultRetentionSink } from "./iiis-client.js";
7
7
  import { type OpenAllowedFileOptions, type OpenedAllowedFile } from "./path-security.js";
8
8
  import type { OutlineResult } from "./outline.js";
9
+ import { type NormalizedParseArguments } from "./protocol.js";
9
10
  import type { RemoteOmniClient } from "./remote-client.js";
10
11
  import { type ParseResult } from "./result-contract.js";
11
12
  interface ToolRetention extends ResultRetentionSink {
@@ -13,7 +14,9 @@ interface ToolRetention extends ResultRetentionSink {
13
14
  }
14
15
  interface ToolArtifactStore {
15
16
  createRetention(): ToolRetention;
17
+ preflightWrite?(): Promise<void>;
16
18
  read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
19
+ mintReadCursor?(resultId: string, byteOffset: number): Promise<string>;
17
20
  readBundlePart?(resultId: string, cursor: string, maxBytes?: number): Promise<BundlePartReadChunk>;
18
21
  readOutline?(resultId: string): Promise<OutlineResult>;
19
22
  mintOutlineCursor?(resultId: string, byteOffset: number): Promise<string>;
@@ -29,6 +32,7 @@ export interface ParseOperationController {
29
32
  sourceFacts: Readonly<Record<string, unknown>>;
30
33
  clientRequestId: string;
31
34
  signal: AbortSignal;
35
+ resultDelivery: NormalizedParseArguments["resultDelivery"];
32
36
  context: unknown;
33
37
  }): Promise<ParseResult>;
34
38
  statusResult(operationId: string, waitMs: number | undefined, signal: AbortSignal): Promise<ParseResult>;