@cueai/omni-reader-mcp 1.2.2 → 1.3.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.
- package/README.md +19 -12
- package/dist/artifact-store.d.ts +3 -0
- package/dist/artifact-store.js +36 -0
- package/dist/cli/agent-config.js +2 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/iiis-client.d.ts +1 -1
- package/dist/operation-manager.d.ts +5 -4
- package/dist/operation-manager.js +56 -4
- package/dist/outline.d.ts +15 -0
- package/dist/outline.js +79 -0
- package/dist/protocol.d.ts +11 -0
- package/dist/protocol.js +10 -0
- package/dist/result-contract.d.ts +15 -1
- package/dist/result-contract.js +73 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +74 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ differ from the numbers above, report the live values.
|
|
|
60
60
|
Always use an audited exact version, never an implicit `latest`:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
63
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 setup
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
|
|
@@ -68,9 +68,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
|
|
|
68
68
|
logic:
|
|
69
69
|
|
|
70
70
|
```sh
|
|
71
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
72
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
73
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
71
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
|
|
72
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 setup --client cursor --add-root /absolute/minimum/root --yes --json
|
|
73
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
|
|
@@ -78,7 +78,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
|
|
|
78
78
|
stdin is never read:
|
|
79
79
|
|
|
80
80
|
```sh
|
|
81
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
81
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
## Cache and journal isolation
|
|
@@ -127,6 +127,7 @@ The public tools are fixed:
|
|
|
127
127
|
- `get_parse_status`
|
|
128
128
|
- `cancel_parse`
|
|
129
129
|
- `read_result`
|
|
130
|
+
- `read_outline`
|
|
130
131
|
- `discard_result`
|
|
131
132
|
|
|
132
133
|
Every tool returns `structuredContent` with a strict `outputSchema`, plus an equivalent
|
|
@@ -138,6 +139,12 @@ fallback for clients that only read legacy MCP `content[].text`:
|
|
|
138
139
|
compact JSON equivalent to the `structuredContent` fields;
|
|
139
140
|
- the `read_result` text JSON contains the current `result.text` and an optional
|
|
140
141
|
`next_cursor`; clients must exhaust all cursors before concatenating the body;
|
|
142
|
+
- `read_outline(result_id)` returns the result's heading tree (from Markdown ATX headings
|
|
143
|
+
or, for a PDF source, its font-size-calibrated heading structure) without returning the
|
|
144
|
+
full body to the caller; `read_outline(result_id, node_id)` mints a `read_result`-compatible cursor
|
|
145
|
+
anchored at that heading, so a long result can be jumped into directly instead of only
|
|
146
|
+
advancing sequentially through `next_cursor`. An empty or absent outline is reported
|
|
147
|
+
explicitly, never silently — it never blocks reading the result itself with `read_result`;
|
|
141
148
|
- the `discard_result` text JSON explicitly returns `discarded`; never claim deletion on
|
|
142
149
|
call success alone.
|
|
143
150
|
|
|
@@ -198,16 +205,16 @@ new source that satisfies the constraints.
|
|
|
198
205
|
## Commands
|
|
199
206
|
|
|
200
207
|
```sh
|
|
201
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
202
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
203
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
204
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
208
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 doctor
|
|
209
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 doctor --json
|
|
210
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 clean
|
|
211
|
+
npx -y @cueai/omni-reader-mcp@1.3.0 uninstall --yes --json
|
|
205
212
|
```
|
|
206
213
|
|
|
207
214
|
Running the pinned version without a command starts the stdio MCP server:
|
|
208
215
|
|
|
209
216
|
```sh
|
|
210
|
-
npx -y @cueai/omni-reader-mcp@1.
|
|
217
|
+
npx -y @cueai/omni-reader-mcp@1.3.0
|
|
211
218
|
```
|
|
212
219
|
|
|
213
220
|
`doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
|
|
@@ -216,12 +223,12 @@ status; it never prints the Key, private source paths, or content.
|
|
|
216
223
|
|
|
217
224
|
## Uninstall and rollback
|
|
218
225
|
|
|
219
|
-
`uninstall --yes --json` removes only a trusted 1.2.
|
|
226
|
+
`uninstall --yes --json` removes only a trusted 1.2.2 or 1.3.0 Bridge entry; when a
|
|
220
227
|
matching trusted backup exists, it restores the original URL-only `omni-reader` entry.
|
|
221
228
|
Uninstall never deletes user source files and never silently removes unexpired local
|
|
222
229
|
results.
|
|
223
230
|
|
|
224
|
-
To roll back from 1.
|
|
231
|
+
To roll back from 1.3.0:
|
|
225
232
|
|
|
226
233
|
1. stop recommending or installing that version;
|
|
227
234
|
2. run `uninstall --yes --json` to restore the trusted URL-only entry;
|
package/dist/artifact-store.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
2
|
import type { ReleasedMetadata, ResultRetentionSink, ResultRetentionStart } from "./iiis-client.js";
|
|
3
|
+
import { type OutlineResult } from "./outline.js";
|
|
3
4
|
import { GROUNDING_SCHEMA_VERSION, RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
|
|
4
5
|
import { type VerifiedBundle } from "./result-bundle.js";
|
|
5
6
|
export interface ArtifactStoreOptions {
|
|
@@ -109,6 +110,8 @@ export declare class ArtifactStore {
|
|
|
109
110
|
get rootDirectory(): string;
|
|
110
111
|
createRetention(): LocalResultRetention;
|
|
111
112
|
read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
|
|
113
|
+
readOutline(resultId: string): Promise<OutlineResult>;
|
|
114
|
+
mintOutlineCursor(resultId: string, byteOffset: number): Promise<string>;
|
|
112
115
|
readBundlePart(resultId: string, cursor: string, maxBytes?: number): Promise<BundlePartReadChunk>;
|
|
113
116
|
discard(resultId: string): Promise<boolean>;
|
|
114
117
|
cleanupExpired(): Promise<number>;
|
package/dist/artifact-store.js
CHANGED
|
@@ -7,6 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
import { ARTIFACT_TTL_MS, INLINE_RESULT_MAX_BYTES, RESULT_CHUNK_MAX_BYTES } from "./constants.js";
|
|
8
8
|
import { CursorCodec } from "./cursor.js";
|
|
9
9
|
import { OmniBridgeError } from "./errors.js";
|
|
10
|
+
import { extractOutline } from "./outline.js";
|
|
10
11
|
import { GROUNDING_SCHEMA_VERSION, RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
|
|
11
12
|
// The D2-A source scan (test/protocol.test.ts) rejects any import specifier
|
|
12
13
|
// mentioning the bundle module: it was written when nothing imported it. D2-D
|
|
@@ -535,6 +536,41 @@ export class ArtifactStore {
|
|
|
535
536
|
: {}),
|
|
536
537
|
};
|
|
537
538
|
}
|
|
539
|
+
async readOutline(resultId) {
|
|
540
|
+
this.#requireOpen();
|
|
541
|
+
const metadata = await this.#loadMetadata(resultId);
|
|
542
|
+
if (Date.parse(metadata.expiresAt) <= this.#now().getTime()) {
|
|
543
|
+
await this.discard(resultId);
|
|
544
|
+
throw artifactError("RESULT_EXPIRED", "The local result artifact has expired.");
|
|
545
|
+
}
|
|
546
|
+
const artifactPath = path.join(this.#resultsDirectory, metadata.artifactName);
|
|
547
|
+
const handle = await openArtifactForRead(artifactPath);
|
|
548
|
+
let fullText;
|
|
549
|
+
try {
|
|
550
|
+
const artifactStat = await handle.stat();
|
|
551
|
+
if (!artifactStat.isFile() || artifactStat.size !== metadata.resultBytes) {
|
|
552
|
+
throw artifactError("LOCAL_RESULT_INTEGRITY_FAILED", "The local result artifact is invalid.");
|
|
553
|
+
}
|
|
554
|
+
const slice = await readUtf8Slice(handle, 0, metadata.resultBytes, metadata.resultBytes);
|
|
555
|
+
fullText = slice.text;
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
await handle.close();
|
|
559
|
+
}
|
|
560
|
+
return extractOutline(fullText);
|
|
561
|
+
}
|
|
562
|
+
async mintOutlineCursor(resultId, byteOffset) {
|
|
563
|
+
this.#requireOpen();
|
|
564
|
+
const metadata = await this.#loadMetadata(resultId);
|
|
565
|
+
if (Date.parse(metadata.expiresAt) <= this.#now().getTime()) {
|
|
566
|
+
await this.discard(resultId);
|
|
567
|
+
throw artifactError("RESULT_EXPIRED", "The local result artifact has expired.");
|
|
568
|
+
}
|
|
569
|
+
if (!Number.isSafeInteger(byteOffset) || byteOffset < 0 || byteOffset > metadata.resultBytes) {
|
|
570
|
+
throw artifactError("INVALID_RESULT_CURSOR", "The outline node offset is invalid.");
|
|
571
|
+
}
|
|
572
|
+
return this.#cursor.encode({ resultId, offset: byteOffset, expiresAt: metadata.expiresAt });
|
|
573
|
+
}
|
|
538
574
|
// Read one UTF-8-safe chunk of one named part of a retained bundle. The
|
|
539
575
|
// opaque v2 cursor binds resultId/part/detail/schema/bundle protocol/
|
|
540
576
|
// offset/expiry; every bound field is re-verified against the closed
|
package/dist/cli/agent-config.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, lstat, mkdir, open, realpath, rename, unlink, } from "node:fs/pr
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { BRIDGE_RELEASE_VERSION, REMOTE_OMNI_MCP_URL, } from "../constants.js";
|
|
6
6
|
const PACKAGE_SPEC = `@cueai/omni-reader-mcp@${BRIDGE_RELEASE_VERSION}`;
|
|
7
|
-
const PREVIOUS_PACKAGE_SPEC = "@cueai/omni-reader-mcp@1.2.
|
|
7
|
+
const PREVIOUS_PACKAGE_SPEC = "@cueai/omni-reader-mcp@1.2.2";
|
|
8
8
|
const LEGACY_PACKAGE_SPEC = "@cueai/omni-reader-mcp";
|
|
9
9
|
const TRUSTED_EXACT_PACKAGE_SPECS = new Set([PREVIOUS_PACKAGE_SPEC, PACKAGE_SPEC]);
|
|
10
10
|
function isRecord(value) {
|
|
@@ -586,7 +586,7 @@ function expectedBridgeVersion(target, value) {
|
|
|
586
586
|
if (!isExpectedOmniEntry(target, value))
|
|
587
587
|
return undefined;
|
|
588
588
|
const packageSpec = value.args[1];
|
|
589
|
-
return packageSpec === PREVIOUS_PACKAGE_SPEC ? "1.2.
|
|
589
|
+
return packageSpec === PREVIOUS_PACKAGE_SPEC ? "1.2.2" : BRIDGE_RELEASE_VERSION;
|
|
590
590
|
}
|
|
591
591
|
function isLegacyBridgeEntry(target, value) {
|
|
592
592
|
if (!isRecord(value) || value.command !== "npx")
|
package/dist/constants.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
|
|
|
6
6
|
export declare const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
|
|
7
7
|
export declare const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
|
|
8
8
|
export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
|
|
9
|
-
export declare const BRIDGE_RELEASE_VERSION = "1.
|
|
9
|
+
export declare const BRIDGE_RELEASE_VERSION = "1.3.0";
|
|
10
10
|
export declare const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
|
|
11
11
|
export declare const FOREGROUND_BUDGET_MS = 15000;
|
|
12
12
|
export declare const STATUS_LONG_POLL_MAX_MS = 20000;
|
package/dist/constants.js
CHANGED
|
@@ -6,7 +6,7 @@ export const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
|
|
|
6
6
|
export const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
|
|
7
7
|
export const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
|
|
8
8
|
export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
|
|
9
|
-
export const BRIDGE_RELEASE_VERSION = "1.
|
|
9
|
+
export const BRIDGE_RELEASE_VERSION = "1.3.0";
|
|
10
10
|
export const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
|
|
11
11
|
export const FOREGROUND_BUDGET_MS = 15_000;
|
|
12
12
|
export const STATUS_LONG_POLL_MAX_MS = 20_000;
|
package/dist/iiis-client.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface ResultRetentionStart {
|
|
|
4
4
|
readonly operationId: string;
|
|
5
5
|
readonly resultBytes: number;
|
|
6
6
|
readonly mediaType: string;
|
|
7
|
-
readonly source: "sse" | "recovery";
|
|
7
|
+
readonly source: "sse" | "recovery" | "remote_hydration";
|
|
8
8
|
}
|
|
9
9
|
export interface ReleasedMetadata extends ResultRetentionStart {
|
|
10
10
|
readonly resultDigest: string;
|
|
@@ -5,7 +5,7 @@ import type { JournalPatch, JournalRecord, JournalState, OperationJournal } from
|
|
|
5
5
|
import { type OpenAllowedFileOptions, type OpenedAllowedFile } from "./path-security.js";
|
|
6
6
|
import { type RepresentationIntent } from "./protocol.js";
|
|
7
7
|
import type { RemoteOmniClient } from "./remote-client.js";
|
|
8
|
-
import type
|
|
8
|
+
import { type ParseResult } from "./result-contract.js";
|
|
9
9
|
export interface SubmitOperationInput {
|
|
10
10
|
readonly sourceKind: "local" | "url";
|
|
11
11
|
readonly sourceFacts: Readonly<Record<string, unknown>>;
|
|
@@ -55,11 +55,11 @@ export declare class OperationManager {
|
|
|
55
55
|
status(operationId: string, waitMs?: number, signal?: AbortSignal): Promise<JournalRecord>;
|
|
56
56
|
cancel(operationId: string, signal?: AbortSignal): Promise<JournalRecord>;
|
|
57
57
|
}
|
|
58
|
-
interface LocalRetention extends ResultRetentionSink {
|
|
58
|
+
export interface LocalRetention extends ResultRetentionSink {
|
|
59
59
|
result(): LocalResult;
|
|
60
60
|
bundleResult?(): BundleLocalResult;
|
|
61
61
|
}
|
|
62
|
-
interface LocalArtifactStore {
|
|
62
|
+
export interface LocalArtifactStore {
|
|
63
63
|
createRetention(): LocalRetention;
|
|
64
64
|
read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
|
|
65
65
|
readBundleDescriptor?(resultId: string): Promise<BundleLocalResult | null>;
|
|
@@ -76,5 +76,6 @@ export interface LocalParseOperationManagerOptions {
|
|
|
76
76
|
readonly now?: () => Date;
|
|
77
77
|
readonly sleep?: (milliseconds: number) => Promise<void>;
|
|
78
78
|
}
|
|
79
|
+
export declare function hydrateInlineUrlResult(result: ParseResult, artifactStore: Pick<LocalArtifactStore, "createRetention">): Promise<ParseResult>;
|
|
80
|
+
export declare function hydrateInlineUrlResultSafely(result: ParseResult, artifactStore: Pick<LocalArtifactStore, "createRetention">): Promise<ParseResult>;
|
|
79
81
|
export declare function createLocalParseOperationManager(options: LocalParseOperationManagerOptions): OperationManager;
|
|
80
|
-
export {};
|
|
@@ -5,6 +5,7 @@ import { LEGACY_RECOVERY_FAILURE_CODE } from "./operation-journal.js";
|
|
|
5
5
|
import { openAllowedFile, } from "./path-security.js";
|
|
6
6
|
import { NOOP_PROGRESS } from "./progress.js";
|
|
7
7
|
import { normalizeRepresentation } from "./protocol.js";
|
|
8
|
+
import { localResultToResultField } from "./result-contract.js";
|
|
8
9
|
const TERMINAL_STATES = new Set([
|
|
9
10
|
"COMPLETED",
|
|
10
11
|
"FAILED",
|
|
@@ -633,6 +634,50 @@ function remoteContext(value) {
|
|
|
633
634
|
}
|
|
634
635
|
return context;
|
|
635
636
|
}
|
|
637
|
+
// A completed (or cleanup_pending -- also directly caller-visible with usable
|
|
638
|
+
// result content, per the local-file path's own existing handling of that
|
|
639
|
+
// status) URL parse's `kind:"inline"` result already carries its full text --
|
|
640
|
+
// hydrating it into Bridge's own local ArtifactStore is a pure local
|
|
641
|
+
// operation (zero extra network I/O) that makes it a completely ordinary
|
|
642
|
+
// local result afterward, so read_result/read_outline work on it exactly as
|
|
643
|
+
// they do for a local-file parse. `kind:"artifact"`/bundle-shaped results
|
|
644
|
+
// are deliberately left untouched -- see this plan's Non-goals.
|
|
645
|
+
export async function hydrateInlineUrlResult(result, artifactStore) {
|
|
646
|
+
if (result.status !== "completed" && result.status !== "cleanup_pending") {
|
|
647
|
+
return result;
|
|
648
|
+
}
|
|
649
|
+
if (result.result === undefined || result.result.kind !== "inline") {
|
|
650
|
+
return result;
|
|
651
|
+
}
|
|
652
|
+
const bytes = new TextEncoder().encode(result.result.text);
|
|
653
|
+
const start = {
|
|
654
|
+
operationId: result.operation_id,
|
|
655
|
+
resultBytes: bytes.byteLength,
|
|
656
|
+
mediaType: "text/markdown; charset=utf-8",
|
|
657
|
+
source: "remote_hydration",
|
|
658
|
+
};
|
|
659
|
+
const retention = artifactStore.createRetention();
|
|
660
|
+
await retention.begin(start);
|
|
661
|
+
await retention.write(bytes);
|
|
662
|
+
await retention.complete({
|
|
663
|
+
...start,
|
|
664
|
+
resultDigest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
|
|
665
|
+
});
|
|
666
|
+
return { ...result, result: localResultToResultField(retention.result()) };
|
|
667
|
+
}
|
|
668
|
+
// hydrateInlineUrlResult is a pure local-storage optimization on top of content the caller
|
|
669
|
+
// already has in full -- a local-disk failure here (e.g. disk full, permission error) must never
|
|
670
|
+
// turn an otherwise-successful URL parse into a failure. This wrapper swallows any hydration
|
|
671
|
+
// error and falls back to the original, un-hydrated result: the caller still gets their content;
|
|
672
|
+
// they just lose local read_result/read_outline re-read capability for that particular result.
|
|
673
|
+
export async function hydrateInlineUrlResultSafely(result, artifactStore) {
|
|
674
|
+
try {
|
|
675
|
+
return await hydrateInlineUrlResult(result, artifactStore);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
636
681
|
function _userFacingMessage(code, result) {
|
|
637
682
|
const error = result.error;
|
|
638
683
|
if (!error.file_uploaded && !error.parser_started) {
|
|
@@ -1419,7 +1464,8 @@ export function createLocalParseOperationManager(options) {
|
|
|
1419
1464
|
}
|
|
1420
1465
|
const context = remoteContext(input.context);
|
|
1421
1466
|
const result = await options.remoteClient.parse(context.source, input.clientRequestId, input.signal ?? new AbortController().signal, representation.detail);
|
|
1422
|
-
|
|
1467
|
+
const hydrated = await hydrateInlineUrlResultSafely(result, options.artifactStore);
|
|
1468
|
+
return remoteResultUpdate(hydrated, recovery?.operationId ?? undefined);
|
|
1423
1469
|
}
|
|
1424
1470
|
const driver = {
|
|
1425
1471
|
create: (input) => input.sourceKind === "url"
|
|
@@ -1434,7 +1480,9 @@ export function createLocalParseOperationManager(options) {
|
|
|
1434
1480
|
if (record.sourceKind === "url") {
|
|
1435
1481
|
if (options.remoteClient === undefined)
|
|
1436
1482
|
return undefined;
|
|
1437
|
-
|
|
1483
|
+
const result = await options.remoteClient.status(record.operationId, waitMs, signal);
|
|
1484
|
+
const hydrated = await hydrateInlineUrlResultSafely(result, options.artifactStore);
|
|
1485
|
+
return remoteResultUpdate(hydrated, record.operationId);
|
|
1438
1486
|
}
|
|
1439
1487
|
const order = STATE_ORDER.get(record.state);
|
|
1440
1488
|
if (order === undefined || order < STATE_ORDER.get("UPLOADING"))
|
|
@@ -1454,7 +1502,9 @@ export function createLocalParseOperationManager(options) {
|
|
|
1454
1502
|
if (record.sourceKind === "url") {
|
|
1455
1503
|
if (options.remoteClient === undefined)
|
|
1456
1504
|
return undefined;
|
|
1457
|
-
|
|
1505
|
+
const result = await options.remoteClient.cancel(record.operationId, signal);
|
|
1506
|
+
const hydrated = await hydrateInlineUrlResultSafely(result, options.artifactStore);
|
|
1507
|
+
return remoteResultUpdate(hydrated, record.operationId);
|
|
1458
1508
|
}
|
|
1459
1509
|
if (record.operationToken === null)
|
|
1460
1510
|
return undefined;
|
|
@@ -1535,7 +1585,9 @@ export function createLocalParseOperationManager(options) {
|
|
|
1535
1585
|
return completedLocalParse(await recoverLocalArtifact(record));
|
|
1536
1586
|
}
|
|
1537
1587
|
if (record.sourceKind === "url" && options.remoteClient !== undefined) {
|
|
1538
|
-
|
|
1588
|
+
const result = await options.remoteClient.status(record.operationId, 0, signal);
|
|
1589
|
+
const hydrated = await hydrateInlineUrlResultSafely(result, options.artifactStore);
|
|
1590
|
+
return hydrated;
|
|
1539
1591
|
}
|
|
1540
1592
|
return undefined;
|
|
1541
1593
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OutlineNode {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly level: number;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly preview: string;
|
|
6
|
+
readonly byteOffset: number;
|
|
7
|
+
}
|
|
8
|
+
export interface OutlineResult {
|
|
9
|
+
readonly coverage: "complete" | "partial" | "none";
|
|
10
|
+
readonly nodes: readonly OutlineNode[];
|
|
11
|
+
}
|
|
12
|
+
export interface ExtractOutlineOptions {
|
|
13
|
+
readonly coverage?: "complete" | "partial";
|
|
14
|
+
}
|
|
15
|
+
export declare function extractOutline(content: string, options?: ExtractOutlineOptions): OutlineResult;
|
package/dist/outline.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// 0-3 leading spaces (CommonMark ATX rule: 4+ leading spaces is a code block, not a heading),
|
|
2
|
+
// 1-6 '#' characters, required whitespace, required non-empty title.
|
|
3
|
+
const ATX_HEADING_PATTERN = /^ {0,3}(#{1,6})\s+(\S.*)$/;
|
|
4
|
+
// Any run of 3+ backticks or tildes is a fence-looking line. Matches CommonMark's exact
|
|
5
|
+
// fence-matching rule: a fence only closes when the closing marker uses the SAME character
|
|
6
|
+
// (backtick vs tilde) as the opening marker, its length is >= the opening marker's length, AND
|
|
7
|
+
// the line -- after trimming BOTH leading and trailing whitespace -- is exactly the marker with
|
|
8
|
+
// nothing else (a closing fence line may still carry the same 0-3 leading spaces and any amount
|
|
9
|
+
// of trailing whitespace that fence lines are always allowed, but may not carry an info string).
|
|
10
|
+
// The character/length/exact-match comparisons are done in plain JS after this regex match (see
|
|
11
|
+
// below); a fence-looking line that does not satisfy all three conditions while already inside a
|
|
12
|
+
// fence is just fence-looking content inside the fence, not a real close. An OPENING fence line
|
|
13
|
+
// has no such restriction -- trailing content there is a legitimate info string (e.g. ```python)
|
|
14
|
+
// and is allowed and ignored.
|
|
15
|
+
const FENCE_PATTERN = /^ {0,3}(`{3,}|~{3,})/;
|
|
16
|
+
const TRAILING_CLOSING_HASHES = /\s+#+\s*$/;
|
|
17
|
+
export function extractOutline(content, options = {}) {
|
|
18
|
+
const lines = content.split("\n");
|
|
19
|
+
const nodes = [];
|
|
20
|
+
let fenceMarker = null;
|
|
21
|
+
let byteOffset = 0;
|
|
22
|
+
let nodeCounter = 0;
|
|
23
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
24
|
+
const line = lines[i];
|
|
25
|
+
const isLastLine = i === lines.length - 1;
|
|
26
|
+
// split("\n") consumes the newline itself; re-add its one byte for every line except a
|
|
27
|
+
// trailing line with no final newline (matches how `content.split("\n")` behaves).
|
|
28
|
+
const lineByteLength = Buffer.byteLength(line, "utf8") + (isLastLine ? 0 : 1);
|
|
29
|
+
const fenceMatch = FENCE_PATTERN.exec(line);
|
|
30
|
+
if (fenceMatch) {
|
|
31
|
+
const marker = fenceMatch[1];
|
|
32
|
+
const char = marker[0];
|
|
33
|
+
const length = marker.length;
|
|
34
|
+
if (fenceMarker === null) {
|
|
35
|
+
fenceMarker = { char, length };
|
|
36
|
+
}
|
|
37
|
+
else if (char === fenceMarker.char && length >= fenceMarker.length && line.trim() === marker) {
|
|
38
|
+
fenceMarker = null;
|
|
39
|
+
}
|
|
40
|
+
// Otherwise: fence-looking line while already inside a fence that doesn't satisfy the
|
|
41
|
+
// closing condition -- just fence-looking content inside the fence, state stays "in fence".
|
|
42
|
+
byteOffset += lineByteLength;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (fenceMarker === null) {
|
|
46
|
+
const match = ATX_HEADING_PATTERN.exec(line);
|
|
47
|
+
if (match) {
|
|
48
|
+
const hashes = match[1];
|
|
49
|
+
const rawTitle = match[2].replace(TRAILING_CLOSING_HASHES, "").trim();
|
|
50
|
+
const title = /^#+$/.test(rawTitle) ? "" : rawTitle;
|
|
51
|
+
if (title) {
|
|
52
|
+
nodeCounter += 1;
|
|
53
|
+
nodes.push({
|
|
54
|
+
id: `node_${String(nodeCounter).padStart(6, "0")}`,
|
|
55
|
+
level: hashes.length,
|
|
56
|
+
title,
|
|
57
|
+
preview: firstLinePreview(lines, i + 1),
|
|
58
|
+
byteOffset,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
byteOffset += lineByteLength;
|
|
64
|
+
}
|
|
65
|
+
const coverage = options.coverage ?? (nodes.length > 0 ? "complete" : "none");
|
|
66
|
+
return { coverage, nodes };
|
|
67
|
+
}
|
|
68
|
+
function firstLinePreview(lines, startIndex, maxChars = 120) {
|
|
69
|
+
for (let i = startIndex; i < lines.length; i += 1) {
|
|
70
|
+
const raw = lines[i];
|
|
71
|
+
const trimmed = raw.trim();
|
|
72
|
+
if (!trimmed)
|
|
73
|
+
continue;
|
|
74
|
+
if (ATX_HEADING_PATTERN.test(raw))
|
|
75
|
+
return ""; // next content is another heading -> no preview
|
|
76
|
+
return trimmed.length > maxChars ? `${trimmed.slice(0, maxChars)}…` : trimmed;
|
|
77
|
+
}
|
|
78
|
+
return "";
|
|
79
|
+
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -51,6 +51,16 @@ export declare const readResultSchema: z.ZodObject<{
|
|
|
51
51
|
cursor?: string | undefined;
|
|
52
52
|
max_bytes?: number | undefined;
|
|
53
53
|
}>;
|
|
54
|
+
export declare const readOutlineSchema: z.ZodObject<{
|
|
55
|
+
result_id: z.ZodString;
|
|
56
|
+
node_id: z.ZodOptional<z.ZodString>;
|
|
57
|
+
}, "strict", z.ZodTypeAny, {
|
|
58
|
+
result_id: string;
|
|
59
|
+
node_id?: string | undefined;
|
|
60
|
+
}, {
|
|
61
|
+
result_id: string;
|
|
62
|
+
node_id?: string | undefined;
|
|
63
|
+
}>;
|
|
54
64
|
export declare const discardResultSchema: z.ZodObject<{
|
|
55
65
|
result_id: z.ZodString;
|
|
56
66
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -63,3 +73,4 @@ export type GetParseStatusArguments = z.infer<typeof getParseStatusSchema>;
|
|
|
63
73
|
export type CancelParseArguments = z.infer<typeof cancelParseSchema>;
|
|
64
74
|
export type ReadResultArguments = z.infer<typeof readResultSchema>;
|
|
65
75
|
export type DiscardResultArguments = z.infer<typeof discardResultSchema>;
|
|
76
|
+
export type ReadOutlineArguments = z.infer<typeof readOutlineSchema>;
|
package/dist/protocol.js
CHANGED
|
@@ -73,6 +73,16 @@ export const readResultSchema = z
|
|
|
73
73
|
max_bytes: z.number().int().min(1).max(RESULT_CHUNK_MAX_BYTES).optional(),
|
|
74
74
|
})
|
|
75
75
|
.strict();
|
|
76
|
+
export const readOutlineSchema = z
|
|
77
|
+
.object({
|
|
78
|
+
result_id: resultIdSchema,
|
|
79
|
+
node_id: z
|
|
80
|
+
.string()
|
|
81
|
+
.regex(/^node_[0-9]{6}$/u)
|
|
82
|
+
.describe("From a prior read_outline call's nodes[].id. Mints a read_result-compatible cursor for that section.")
|
|
83
|
+
.optional(),
|
|
84
|
+
})
|
|
85
|
+
.strict();
|
|
76
86
|
export const discardResultSchema = z
|
|
77
87
|
.object({
|
|
78
88
|
result_id: resultIdSchema,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import type {
|
|
3
|
+
import type { LocalResult } from "./artifact-store.js";
|
|
4
|
+
import { type OmniBridgeErrorPayload } from "./errors.js";
|
|
4
5
|
export interface DataHandling {
|
|
5
6
|
processing_copy: "in_use" | "pending" | "deleted";
|
|
6
7
|
temporary_data: "in_use" | "pending" | "deleted";
|
|
@@ -84,6 +85,17 @@ export type ParseResult = {
|
|
|
84
85
|
credits_remaining: number;
|
|
85
86
|
};
|
|
86
87
|
};
|
|
88
|
+
export declare function localResultToResultField(local: LocalResult): {
|
|
89
|
+
kind: "inline";
|
|
90
|
+
text: string;
|
|
91
|
+
} | {
|
|
92
|
+
kind: "artifact";
|
|
93
|
+
result_id: string;
|
|
94
|
+
result_bytes: number;
|
|
95
|
+
expires_at: string;
|
|
96
|
+
preview: string;
|
|
97
|
+
next_cursor: string;
|
|
98
|
+
};
|
|
87
99
|
export declare const stableErrorSchema: z.ZodObject<{
|
|
88
100
|
ok: z.ZodLiteral<false>;
|
|
89
101
|
code: z.ZodString;
|
|
@@ -594,6 +606,8 @@ export declare const bundlePartReadSchema: z.ZodObject<{
|
|
|
594
606
|
};
|
|
595
607
|
}>;
|
|
596
608
|
export declare const readResultOutputSchema: z.ZodTypeAny;
|
|
609
|
+
export declare const readOutlineOutputSchema: z.ZodTypeAny;
|
|
610
|
+
export declare const readOutlineToolOutputSchema: z.ZodTypeAny;
|
|
597
611
|
export declare const discardResultOutputSchema: z.ZodTypeAny;
|
|
598
612
|
export declare const parseToolOutputSchema: z.ZodTypeAny;
|
|
599
613
|
export declare const readResultToolOutputSchema: z.ZodTypeAny;
|
package/dist/result-contract.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { OmniBridgeError } from "./errors.js";
|
|
2
3
|
import { RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
|
|
4
|
+
// Maps a retained LocalResult (ArtifactStore's own representation) into the
|
|
5
|
+
// wire ParseResult["result"] shape. Used both for local-file parses
|
|
6
|
+
// (tools.ts's completedLocalResult, which additionally sets its own
|
|
7
|
+
// local-upload-specific operation_id/data_handling) and for hydrated URL
|
|
8
|
+
// parses (operation-manager.ts's hydrateInlineUrlResult, which preserves
|
|
9
|
+
// Cube's own original operation_id/data_handling instead).
|
|
10
|
+
export function localResultToResultField(local) {
|
|
11
|
+
if (local.kind === "inline") {
|
|
12
|
+
return { kind: "inline", text: local.text };
|
|
13
|
+
}
|
|
14
|
+
if (local.nextCursor === undefined) {
|
|
15
|
+
throw new OmniBridgeError({
|
|
16
|
+
code: "LOCAL_RESULT_INTEGRITY_FAILED",
|
|
17
|
+
failureScope: "bridge",
|
|
18
|
+
message: "The local result artifact cursor is missing.",
|
|
19
|
+
operationCreated: true,
|
|
20
|
+
fileUploaded: true,
|
|
21
|
+
parserStarted: true,
|
|
22
|
+
billed: false,
|
|
23
|
+
contentReleased: true,
|
|
24
|
+
retryable: false,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
kind: "artifact",
|
|
29
|
+
result_id: local.resultId,
|
|
30
|
+
result_bytes: local.resultBytes,
|
|
31
|
+
expires_at: local.expiresAt,
|
|
32
|
+
preview: local.preview,
|
|
33
|
+
next_cursor: local.nextCursor,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
3
36
|
const operationIdSchema = z.string().min(1).max(128);
|
|
4
37
|
const resultIdSchema = z.string().regex(/^result_[A-Za-z0-9_-]{16,64}$/u);
|
|
5
38
|
const nextCursorSchema = z
|
|
@@ -294,6 +327,46 @@ export const readResultOutputSchema = z.discriminatedUnion("status", [
|
|
|
294
327
|
.strict(),
|
|
295
328
|
failedResultSchema,
|
|
296
329
|
]);
|
|
330
|
+
const outlineNodeSchema = z
|
|
331
|
+
.object({
|
|
332
|
+
id: z.string().regex(/^node_[0-9]{6}$/u),
|
|
333
|
+
level: z.number().int().min(1).max(6),
|
|
334
|
+
title: z.string().min(1).max(512),
|
|
335
|
+
preview: z.string().max(512),
|
|
336
|
+
})
|
|
337
|
+
.strict();
|
|
338
|
+
export const readOutlineOutputSchema = z.discriminatedUnion("status", [
|
|
339
|
+
z
|
|
340
|
+
.object({
|
|
341
|
+
status: z.literal("outline"),
|
|
342
|
+
coverage: z.enum(["complete", "partial", "none"]),
|
|
343
|
+
nodes: z.array(outlineNodeSchema),
|
|
344
|
+
})
|
|
345
|
+
.strict(),
|
|
346
|
+
z
|
|
347
|
+
.object({
|
|
348
|
+
status: z.literal("cursor"),
|
|
349
|
+
cursor: z.string().min(1).max(2048),
|
|
350
|
+
})
|
|
351
|
+
.strict(),
|
|
352
|
+
z
|
|
353
|
+
.object({
|
|
354
|
+
status: z.literal("unavailable"),
|
|
355
|
+
reason: z.literal("OUTLINE_NOT_SUPPORTED"),
|
|
356
|
+
})
|
|
357
|
+
.strict(),
|
|
358
|
+
failedResultSchema,
|
|
359
|
+
]);
|
|
360
|
+
export const readOutlineToolOutputSchema = z
|
|
361
|
+
.object({
|
|
362
|
+
status: z.enum(["outline", "cursor", "unavailable", "failed"]),
|
|
363
|
+
coverage: z.enum(["complete", "partial", "none"]).optional(),
|
|
364
|
+
nodes: z.array(outlineNodeSchema).optional(),
|
|
365
|
+
cursor: z.string().min(1).max(2048).optional(),
|
|
366
|
+
reason: z.literal("OUTLINE_NOT_SUPPORTED").optional(),
|
|
367
|
+
error: stableErrorSchema.optional(),
|
|
368
|
+
})
|
|
369
|
+
.strict();
|
|
297
370
|
export const discardResultOutputSchema = z.discriminatedUnion("status", [
|
|
298
371
|
z
|
|
299
372
|
.object({
|
package/dist/tools.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ReaderCapabilitiesV1 } from "./capabilities.js";
|
|
|
5
5
|
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
|
+
import type { OutlineResult } from "./outline.js";
|
|
8
9
|
import type { RemoteOmniClient } from "./remote-client.js";
|
|
9
10
|
import { type ParseResult } from "./result-contract.js";
|
|
10
11
|
interface ToolRetention extends ResultRetentionSink {
|
|
@@ -14,6 +15,8 @@ interface ToolArtifactStore {
|
|
|
14
15
|
createRetention(): ToolRetention;
|
|
15
16
|
read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
|
|
16
17
|
readBundlePart?(resultId: string, cursor: string, maxBytes?: number): Promise<BundlePartReadChunk>;
|
|
18
|
+
readOutline?(resultId: string): Promise<OutlineResult>;
|
|
19
|
+
mintOutlineCursor?(resultId: string, byteOffset: number): Promise<string>;
|
|
17
20
|
discard(resultId: string): Promise<boolean>;
|
|
18
21
|
}
|
|
19
22
|
export interface ParseOperationController {
|
package/dist/tools.js
CHANGED
|
@@ -3,10 +3,11 @@ import { CallToolRequestSchema, CancelTaskRequestSchema, ErrorCode, McpError, }
|
|
|
3
3
|
import { selectDirectProfile, selectUrlProfile, } from "./capabilities.js";
|
|
4
4
|
import { createClientRequestId as newClientRequestId, } from "./cube-client.js";
|
|
5
5
|
import { OmniBridgeError } from "./errors.js";
|
|
6
|
+
import { hydrateInlineUrlResultSafely } from "./operation-manager.js";
|
|
6
7
|
import { openAllowedFile, } from "./path-security.js";
|
|
7
8
|
import { NOOP_PROGRESS } from "./progress.js";
|
|
8
|
-
import { MACHINE_INSTRUCTIONS, cancelParseSchema, discardResultSchema, getParseStatusSchema, normalizeRepresentation, parseSchema, readResultSchema, } from "./protocol.js";
|
|
9
|
-
import { discardResultOutputSchema, discardResultToolOutputSchema, parseResultSchema, parseToolOutputSchema, readResultOutputSchema, readResultToolOutputSchema, structuredResult, } from "./result-contract.js";
|
|
9
|
+
import { MACHINE_INSTRUCTIONS, cancelParseSchema, discardResultSchema, getParseStatusSchema, normalizeRepresentation, parseSchema, readOutlineSchema, readResultSchema, } from "./protocol.js";
|
|
10
|
+
import { discardResultOutputSchema, discardResultToolOutputSchema, localResultToResultField, parseResultSchema, parseToolOutputSchema, readOutlineOutputSchema, readOutlineToolOutputSchema, readResultOutputSchema, readResultToolOutputSchema, structuredResult, } from "./result-contract.js";
|
|
10
11
|
import { classifySource } from "./source.js";
|
|
11
12
|
import { TaskRuntime } from "./task-runtime.js";
|
|
12
13
|
function bridgeError(code, message, facts = {}) {
|
|
@@ -51,38 +52,15 @@ function completedLocalResult(local) {
|
|
|
51
52
|
original_source: "unchanged",
|
|
52
53
|
remote_content_retained: false,
|
|
53
54
|
};
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
status: "completed",
|
|
57
|
-
operation_id: local.operationId,
|
|
58
|
-
result: { kind: "inline", text: local.text },
|
|
59
|
-
data_handling: dataHandling,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
if (local.nextCursor === undefined) {
|
|
63
|
-
throw bridgeError("LOCAL_RESULT_INTEGRITY_FAILED", "The local result artifact cursor is missing.", {
|
|
64
|
-
operationCreated: true,
|
|
65
|
-
fileUploaded: true,
|
|
66
|
-
parserStarted: true,
|
|
67
|
-
contentReleased: true,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
55
|
+
const result = localResultToResultField(local);
|
|
70
56
|
return {
|
|
71
57
|
status: "completed",
|
|
72
58
|
operation_id: local.operationId,
|
|
73
|
-
result
|
|
74
|
-
kind: "artifact",
|
|
75
|
-
result_id: local.resultId,
|
|
76
|
-
result_bytes: local.resultBytes,
|
|
77
|
-
expires_at: local.expiresAt,
|
|
78
|
-
preview: local.preview,
|
|
79
|
-
next_cursor: local.nextCursor,
|
|
80
|
-
},
|
|
59
|
+
result,
|
|
81
60
|
data_handling: dataHandling,
|
|
82
|
-
|
|
83
|
-
expires_at: local.expiresAt,
|
|
84
|
-
|
|
85
|
-
},
|
|
61
|
+
...(result.kind === "artifact"
|
|
62
|
+
? { local_result_cache: { expires_at: local.expiresAt, discard_action: "discard_result" } }
|
|
63
|
+
: {}),
|
|
86
64
|
};
|
|
87
65
|
}
|
|
88
66
|
async function parseLocal(args, clientRequestId, signal, progress, dependencies) {
|
|
@@ -229,7 +207,8 @@ async function parseValue(args, signal, progress, dependencies) {
|
|
|
229
207
|
if (dependencies.remoteClient === undefined) {
|
|
230
208
|
throw bridgeError("REMOTE_PARSE_UNAVAILABLE", "Remote URL parsing is not available in this Bridge build.", { retryable: true });
|
|
231
209
|
}
|
|
232
|
-
|
|
210
|
+
const remoteResult = await dependencies.remoteClient.parse(source.source, clientRequestId, signal);
|
|
211
|
+
return await hydrateInlineUrlResultSafely(remoteResult, dependencies.artifactStore);
|
|
233
212
|
}
|
|
234
213
|
return await parseLocal(args, clientRequestId, signal, progress, dependencies);
|
|
235
214
|
}
|
|
@@ -256,7 +235,8 @@ async function statusValue(operationId, waitMs, signal, dependencies) {
|
|
|
256
235
|
}
|
|
257
236
|
}
|
|
258
237
|
if (dependencies.remoteClient !== undefined) {
|
|
259
|
-
|
|
238
|
+
const remoteResult = await dependencies.remoteClient.status(operationId, waitMs, signal);
|
|
239
|
+
return await hydrateInlineUrlResultSafely(remoteResult, dependencies.artifactStore);
|
|
260
240
|
}
|
|
261
241
|
throw bridgeError("OPERATION_NOT_FOUND", "The requested parse operation is not available.", { operationCreated: true });
|
|
262
242
|
}
|
|
@@ -283,7 +263,8 @@ async function cancelValue(operationId, signal, dependencies) {
|
|
|
283
263
|
}
|
|
284
264
|
}
|
|
285
265
|
if (dependencies.remoteClient !== undefined) {
|
|
286
|
-
|
|
266
|
+
const remoteResult = await dependencies.remoteClient.cancel(operationId, signal);
|
|
267
|
+
return await hydrateInlineUrlResultSafely(remoteResult, dependencies.artifactStore);
|
|
287
268
|
}
|
|
288
269
|
throw bridgeError("OPERATION_NOT_FOUND", "The requested parse operation is not available.", { operationCreated: true });
|
|
289
270
|
}
|
|
@@ -354,12 +335,55 @@ async function callDiscardResult(resultId, dependencies) {
|
|
|
354
335
|
return structuredResult(discardResultOutputSchema, failed(error));
|
|
355
336
|
}
|
|
356
337
|
}
|
|
338
|
+
async function callReadOutline(resultId, nodeId, dependencies) {
|
|
339
|
+
if (dependencies.artifactStore.readOutline === undefined
|
|
340
|
+
|| dependencies.artifactStore.mintOutlineCursor === undefined) {
|
|
341
|
+
return structuredResult(readOutlineOutputSchema, {
|
|
342
|
+
status: "unavailable",
|
|
343
|
+
reason: "OUTLINE_NOT_SUPPORTED",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const outline = await dependencies.artifactStore.readOutline(resultId);
|
|
348
|
+
if (nodeId === undefined) {
|
|
349
|
+
return structuredResult(readOutlineOutputSchema, {
|
|
350
|
+
status: "outline",
|
|
351
|
+
coverage: outline.coverage,
|
|
352
|
+
// Strip byteOffset: it is an internal detail the caller mints a cursor for,
|
|
353
|
+
// never a value it reads or supplies back directly.
|
|
354
|
+
nodes: outline.nodes.map((node) => ({
|
|
355
|
+
id: node.id,
|
|
356
|
+
level: node.level,
|
|
357
|
+
title: node.title,
|
|
358
|
+
preview: node.preview,
|
|
359
|
+
})),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
const node = outline.nodes.find((candidate) => candidate.id === nodeId);
|
|
363
|
+
if (node === undefined) {
|
|
364
|
+
throw bridgeError("OUTLINE_NODE_NOT_FOUND", "The requested outline node does not exist.", {
|
|
365
|
+
operationCreated: true,
|
|
366
|
+
fileUploaded: true,
|
|
367
|
+
parserStarted: true,
|
|
368
|
+
billed: true,
|
|
369
|
+
contentReleased: true,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const cursor = await dependencies.artifactStore.mintOutlineCursor(resultId, node.byteOffset);
|
|
373
|
+
return structuredResult(readOutlineOutputSchema, { status: "cursor", cursor });
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return structuredResult(readOutlineOutputSchema, failed(error));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
357
379
|
function invalidArgumentsResult(name) {
|
|
358
380
|
const error = failed(bridgeError("INVALID_TOOL_ARGUMENTS", "The Omni tool arguments are invalid."));
|
|
359
381
|
if (name === "read_result")
|
|
360
382
|
return structuredResult(readResultOutputSchema, error);
|
|
361
383
|
if (name === "discard_result")
|
|
362
384
|
return structuredResult(discardResultOutputSchema, error);
|
|
385
|
+
if (name === "read_outline")
|
|
386
|
+
return structuredResult(readOutlineOutputSchema, error);
|
|
363
387
|
return structuredResult(parseResultSchema, error);
|
|
364
388
|
}
|
|
365
389
|
async function dispatchTool(name, rawArguments, extra, dependencies) {
|
|
@@ -393,6 +417,12 @@ async function dispatchTool(name, rawArguments, extra, dependencies) {
|
|
|
393
417
|
? callDiscardResult(parsed.data.result_id, dependencies)
|
|
394
418
|
: invalidArgumentsResult(name);
|
|
395
419
|
}
|
|
420
|
+
if (name === "read_outline") {
|
|
421
|
+
const parsed = readOutlineSchema.safeParse(rawArguments ?? {});
|
|
422
|
+
return parsed.success
|
|
423
|
+
? callReadOutline(parsed.data.result_id, parsed.data.node_id, dependencies)
|
|
424
|
+
: invalidArgumentsResult(name);
|
|
425
|
+
}
|
|
396
426
|
return structuredResult(parseResultSchema, failed(bridgeError("TOOL_NOT_FOUND", "The requested Omni tool is not available.")));
|
|
397
427
|
}
|
|
398
428
|
export function registerOmniTools(server, dependencies, taskStore) {
|
|
@@ -473,6 +503,17 @@ export function registerOmniTools(server, dependencies, taskStore) {
|
|
|
473
503
|
openWorldHint: false,
|
|
474
504
|
},
|
|
475
505
|
}, (args) => callDiscardResult(args.result_id, dependencies));
|
|
506
|
+
server.registerTool("read_outline", {
|
|
507
|
+
description: "Return a local result's heading outline. Pass node_id from a returned node's id to mint a read_result-compatible cursor that jumps straight to that section, instead of reading sequentially from the start.",
|
|
508
|
+
inputSchema: readOutlineSchema,
|
|
509
|
+
outputSchema: readOutlineToolOutputSchema,
|
|
510
|
+
annotations: {
|
|
511
|
+
readOnlyHint: true,
|
|
512
|
+
destructiveHint: false,
|
|
513
|
+
idempotentHint: true,
|
|
514
|
+
openWorldHint: false,
|
|
515
|
+
},
|
|
516
|
+
}, (args) => callReadOutline(args.result_id, args.node_id, dependencies));
|
|
476
517
|
server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
477
518
|
if (request.params.task !== undefined) {
|
|
478
519
|
if (request.params.name !== "parse") {
|