@cueai/omni-reader-mcp 1.1.1 → 1.1.3
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 +34 -11
- package/dist/artifact-store.d.ts +1 -0
- package/dist/artifact-store.js +20 -3
- package/dist/cli/arguments.d.ts +1 -0
- package/dist/cli/arguments.js +7 -1
- package/dist/cli/setup.js +10 -4
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/operation-journal.d.ts +8 -0
- package/dist/operation-journal.js +26 -1
- package/dist/operation-manager.js +28 -2
- package/dist/result-contract.js +12 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,17 +33,33 @@ Bridge 运行时只使用以下产品配置:
|
|
|
33
33
|
始终使用已审核的精确版本,不使用隐式 `latest`:
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
36
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 setup
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
交互 setup 支持 Hermes、Cursor、Claude Desktop 和通用 stdio 配置。非交互安装使用同一参数解析和写入逻辑:
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
43
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
44
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
42
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
|
|
43
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 setup --client cursor --add-root /absolute/minimum/root --yes --json
|
|
44
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
Agent/脚本在 pty 下运行(stdin 仍是 TTY)时,用 `--headless`(别名 `--non-interactive`)显式声明非交互,无需 `--yes` 且绝不读 stdin:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 缓存与 journal 隔离
|
|
54
|
+
|
|
55
|
+
本地解析缓存与操作 journal 默认位于用户级缓存目录(macOS `~/Library/Caches/cue/omni-reader-mcp`、Windows `%LOCALAPPDATA%\Cue\omni-reader-mcp\Cache`、Linux `~/.cache/cue/omni-reader-mcp`)。若 Agent 的文件监视器(如 WorkBuddy)锁定该目录下的文件导致写入失败,设置 `OMNI_CACHE_DIR` 将缓存与 journal 一起隔离到不被监视的目录:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
export OMNI_CACHE_DIR=/var/cache/omni-reader
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
显式 `OMNI_CACHE_DIR` 不会被项目重叠检查改写到临时目录;仍禁止与项目目录互相包含。
|
|
62
|
+
|
|
47
63
|
setup 会:
|
|
48
64
|
|
|
49
65
|
1. 展示 user-scope 配置目标和变更;
|
|
@@ -73,7 +89,14 @@ Agent 将用户提供的来源字符串直接传给 `parse(source)`:
|
|
|
73
89
|
- `read_result`
|
|
74
90
|
- `discard_result`
|
|
75
91
|
|
|
76
|
-
每个工具都返回 `structuredContent` 和严格 `outputSchema
|
|
92
|
+
每个工具都返回 `structuredContent` 和严格 `outputSchema`,同时为只读取传统 MCP `content[].text` 的客户端提供等价回退:
|
|
93
|
+
|
|
94
|
+
- `parse` 完成且结果可 inline 时,text 直接返回完整 Markdown;
|
|
95
|
+
- processing、artifact、cleanup、cancel、expired 和 failure 状态,text 返回与 `structuredContent` 字段等价的紧凑 JSON;
|
|
96
|
+
- `read_result` 的 text JSON 包含本次 `result.text` 和可选 `next_cursor`,客户端必须追完所有 cursor 后再拼接正文;
|
|
97
|
+
- `discard_result` 的 text JSON 明确返回 `discarded`,不得仅凭调用成功宣称结果已删除。
|
|
98
|
+
|
|
99
|
+
这使不展示 `structuredContent` 的客户端也能取得 operation/result 句柄和完整正文,同时不改变支持结构化结果的客户端契约。
|
|
77
100
|
|
|
78
101
|
## 前台、后台和进度
|
|
79
102
|
|
|
@@ -112,23 +135,23 @@ Agent 不应自动拆分、转码或上传公共站点;应请用户提供符
|
|
|
112
135
|
## 命令
|
|
113
136
|
|
|
114
137
|
```sh
|
|
115
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
116
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
117
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
118
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
138
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 doctor
|
|
139
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 doctor --json
|
|
140
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 clean
|
|
141
|
+
npx -y @cueai/omni-reader-mcp@1.1.3 uninstall --yes --json
|
|
119
142
|
```
|
|
120
143
|
|
|
121
144
|
不带命令运行固定版本会启动 stdio MCP server:
|
|
122
145
|
|
|
123
146
|
```sh
|
|
124
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
147
|
+
npx -y @cueai/omni-reader-mcp@1.1.3
|
|
125
148
|
```
|
|
126
149
|
|
|
127
150
|
`doctor --json` 返回 package/npm/client adapter、Key present/absent、allowed-root 安全状态、endpoint compatibility、artifact、cache mode、onboarding 和 reload 状态;不会输出 Key、私有来源路径或内容。
|
|
128
151
|
|
|
129
152
|
## 卸载与回滚
|
|
130
153
|
|
|
131
|
-
`uninstall --yes --json` 只删除当前受信的 1.1.
|
|
154
|
+
`uninstall --yes --json` 只删除当前受信的 1.1.3 Bridge entry;存在匹配的可信备份时恢复原 URL-only `omni-reader` entry。卸载不会删除用户源文件,也不会静默删除未过期的本地结果。
|
|
132
155
|
|
|
133
156
|
需要回滚 1.1.1 时:
|
|
134
157
|
|
package/dist/artifact-store.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface ArtifactReadResult {
|
|
|
44
44
|
interface ArtifactFinalizeInput extends ReleasedMetadata {
|
|
45
45
|
readonly resultId: string;
|
|
46
46
|
}
|
|
47
|
+
export declare function syncDirectory(directory: string, platform?: NodeJS.Platform): Promise<void>;
|
|
47
48
|
export declare function defaultArtifactRoot(options?: DefaultArtifactRootOptions): string;
|
|
48
49
|
export declare function selectArtifactRoot(options: SelectArtifactRootOptions): Promise<SelectedArtifactRoot>;
|
|
49
50
|
export declare class ArtifactStore {
|
package/dist/artifact-store.js
CHANGED
|
@@ -53,14 +53,21 @@ async function removeIfPresent(filePath) {
|
|
|
53
53
|
throw error;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
async function syncDirectory(directory) {
|
|
56
|
+
export async function syncDirectory(directory, platform = process.platform) {
|
|
57
|
+
// Windows cannot fsync a directory handle: opening a directory with
|
|
58
|
+
// O_RDONLY raises EPERM. File fsync still provides primary durability.
|
|
59
|
+
if (platform === "win32")
|
|
60
|
+
return;
|
|
57
61
|
let handle;
|
|
58
62
|
try {
|
|
59
63
|
handle = await open(directory, "r");
|
|
60
64
|
await handle.sync();
|
|
61
65
|
}
|
|
62
66
|
catch (error) {
|
|
63
|
-
if (!errno(error, "EINVAL") &&
|
|
67
|
+
if (!errno(error, "EINVAL") &&
|
|
68
|
+
!errno(error, "ENOTSUP") &&
|
|
69
|
+
!errno(error, "EISDIR") &&
|
|
70
|
+
!errno(error, "EPERM")) {
|
|
64
71
|
throw error;
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -232,6 +239,14 @@ export function defaultArtifactRoot(options = {}) {
|
|
|
232
239
|
const platform = options.platform ?? process.platform;
|
|
233
240
|
const homeDirectory = options.homeDirectory ?? os.homedir();
|
|
234
241
|
const env = options.env ?? process.env;
|
|
242
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
243
|
+
const configured = env.OMNI_CACHE_DIR;
|
|
244
|
+
if (configured !== undefined && configured.length > 0) {
|
|
245
|
+
// Explicit isolation: Agents' file watchers lock files under the default
|
|
246
|
+
// cache (WorkBuddy dogfood). An explicit root skips the overlap fallback;
|
|
247
|
+
// ArtifactStore.open still rejects a project-inside-cache layout.
|
|
248
|
+
return paths.resolve(configured);
|
|
249
|
+
}
|
|
235
250
|
if (platform === "darwin") {
|
|
236
251
|
return path.posix.join(homeDirectory, "Library", "Caches", "cue", "omni-reader-mcp");
|
|
237
252
|
}
|
|
@@ -287,12 +302,14 @@ function selectionPathsOverlap(left, right, platform) {
|
|
|
287
302
|
export async function selectArtifactRoot(options) {
|
|
288
303
|
const platform = options.platform ?? process.platform;
|
|
289
304
|
const rootDirectory = defaultArtifactRoot(options);
|
|
305
|
+
const env = options.env ?? process.env;
|
|
306
|
+
const explicitRoot = env.OMNI_CACHE_DIR !== undefined && env.OMNI_CACHE_DIR.length > 0;
|
|
290
307
|
const [rootIdentity, projectIdentity, homeIdentity] = await Promise.all([
|
|
291
308
|
selectionPathIdentity(rootDirectory, platform),
|
|
292
309
|
selectionPathIdentity(options.projectDirectory, platform),
|
|
293
310
|
selectionPathIdentity(options.homeDirectory ?? os.homedir(), platform),
|
|
294
311
|
]);
|
|
295
|
-
if (!selectionPathsOverlap(rootIdentity, projectIdentity, platform)) {
|
|
312
|
+
if (explicitRoot || !selectionPathsOverlap(rootIdentity, projectIdentity, platform)) {
|
|
296
313
|
return { mode: "default", rootDirectory };
|
|
297
314
|
}
|
|
298
315
|
const paths = platform === "win32" ? path.win32 : path.posix;
|
package/dist/cli/arguments.d.ts
CHANGED
package/dist/cli/arguments.js
CHANGED
|
@@ -32,12 +32,13 @@ function parseSetupArguments(values, platform) {
|
|
|
32
32
|
let allowedRoot;
|
|
33
33
|
let addRoot;
|
|
34
34
|
let yes = false;
|
|
35
|
+
let headless = false;
|
|
35
36
|
let json = false;
|
|
36
37
|
const seen = new Set();
|
|
37
38
|
const paths = pathsFor(platform);
|
|
38
39
|
for (let index = 0; index < values.length; index += 1) {
|
|
39
40
|
const flag = values[index];
|
|
40
|
-
if (!["--client", "--allowed-root", "--add-root", "--yes", "--json"].includes(flag)) {
|
|
41
|
+
if (!["--client", "--allowed-root", "--add-root", "--yes", "--headless", "--non-interactive", "--json"].includes(flag)) {
|
|
41
42
|
throw new CliUsageError(`Unknown setup flag: ${flag}`);
|
|
42
43
|
}
|
|
43
44
|
if (seen.has(flag))
|
|
@@ -47,6 +48,10 @@ function parseSetupArguments(values, platform) {
|
|
|
47
48
|
yes = true;
|
|
48
49
|
continue;
|
|
49
50
|
}
|
|
51
|
+
if (flag === "--headless" || flag === "--non-interactive") {
|
|
52
|
+
headless = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
50
55
|
if (flag === "--json") {
|
|
51
56
|
json = true;
|
|
52
57
|
continue;
|
|
@@ -83,6 +88,7 @@ function parseSetupArguments(values, platform) {
|
|
|
83
88
|
allowedRoots: allowedRoot === undefined ? [] : [allowedRoot],
|
|
84
89
|
addRoots: addRoot === undefined ? [] : [addRoot],
|
|
85
90
|
yes,
|
|
91
|
+
headless,
|
|
86
92
|
json,
|
|
87
93
|
};
|
|
88
94
|
}
|
package/dist/cli/setup.js
CHANGED
|
@@ -39,14 +39,20 @@ async function interactiveArguments(options) {
|
|
|
39
39
|
}
|
|
40
40
|
async function selectedTargetAndRoots(options) {
|
|
41
41
|
const requested = options.arguments;
|
|
42
|
+
// --headless (alias: --non-interactive) declares a non-interactive run
|
|
43
|
+
// explicitly, so setup works even when the caller is attached to a TTY
|
|
44
|
+
// (WorkBuddy runs under a pty). Without the flag, non-TTY is inferred.
|
|
45
|
+
const headless = requested.headless || !options.stdinIsTTY;
|
|
42
46
|
if (requested.client === undefined) {
|
|
43
|
-
if (
|
|
44
|
-
throw new CliUsageError("
|
|
47
|
+
if (headless) {
|
|
48
|
+
throw new CliUsageError("Headless setup requires: setup --client hermes --allowed-root <absolute-path> --headless --json");
|
|
45
49
|
}
|
|
46
50
|
const selected = await interactiveArguments(options);
|
|
47
51
|
return { ...selected, interactive: true };
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
// Only an explicit --headless bypasses the consent gate; a non-TTY
|
|
54
|
+
// without --yes still hard-errors so scripts cannot silently write.
|
|
55
|
+
if (!requested.headless && !options.stdinIsTTY && !requested.yes) {
|
|
50
56
|
throw new CliUsageError("Non-interactive setup requires --yes.");
|
|
51
57
|
}
|
|
52
58
|
const existing = await configuredAllowedRoots(requested.client, options);
|
|
@@ -55,7 +61,7 @@ async function selectedTargetAndRoots(options) {
|
|
|
55
61
|
: requested.addRoots.length > 0
|
|
56
62
|
? [...new Set([...existing, ...requested.addRoots])]
|
|
57
63
|
: existing;
|
|
58
|
-
return { target: requested.client, roots, interactive: !requested.yes };
|
|
64
|
+
return { target: requested.client, roots, interactive: headless ? false : !requested.yes };
|
|
59
65
|
}
|
|
60
66
|
function writePreview(options, prepared) {
|
|
61
67
|
options.write(`Target: ${prepared.displayPath}\n`);
|
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.1.
|
|
9
|
+
export declare const BRIDGE_RELEASE_VERSION = "1.1.3";
|
|
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.1.
|
|
9
|
+
export const BRIDGE_RELEASE_VERSION = "1.1.3";
|
|
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;
|
|
@@ -61,6 +61,14 @@ export interface OperationJournalOptions {
|
|
|
61
61
|
readonly rootDirectory?: string;
|
|
62
62
|
readonly now?: () => Date;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Rename with a Windows fallback. Node's rename() rejects replacing an
|
|
66
|
+
* existing destination on Windows with EPERM instead of overwriting
|
|
67
|
+
* atomically; WorkBuddy dogfood hit this on every journal update because
|
|
68
|
+
* the record file already exists. The record lock makes the journal
|
|
69
|
+
* single-writer, so remove-then-rename cannot race a concurrent writer.
|
|
70
|
+
*/
|
|
71
|
+
export declare function renameWithFallback(sourcePath: string, destinationPath: string, platform?: NodeJS.Platform): Promise<void>;
|
|
64
72
|
export declare class OperationJournal {
|
|
65
73
|
#private;
|
|
66
74
|
constructor(options?: OperationJournalOptions);
|
|
@@ -56,6 +56,12 @@ function bridgeError(code, message, retryable) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
function defaultRootDirectory() {
|
|
59
|
+
const configured = process.env.OMNI_CACHE_DIR;
|
|
60
|
+
if (configured !== undefined && configured.length > 0) {
|
|
61
|
+
return process.platform === "win32"
|
|
62
|
+
? path.win32.resolve(configured)
|
|
63
|
+
: path.posix.resolve(configured);
|
|
64
|
+
}
|
|
59
65
|
if (process.platform === "darwin") {
|
|
60
66
|
return path.join(homedir(), "Library", "Caches", "cue", "omni-reader-mcp");
|
|
61
67
|
}
|
|
@@ -199,6 +205,25 @@ async function closeQuietly(handle) {
|
|
|
199
205
|
function sleep(milliseconds) {
|
|
200
206
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
201
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Rename with a Windows fallback. Node's rename() rejects replacing an
|
|
210
|
+
* existing destination on Windows with EPERM instead of overwriting
|
|
211
|
+
* atomically; WorkBuddy dogfood hit this on every journal update because
|
|
212
|
+
* the record file already exists. The record lock makes the journal
|
|
213
|
+
* single-writer, so remove-then-rename cannot race a concurrent writer.
|
|
214
|
+
*/
|
|
215
|
+
export async function renameWithFallback(sourcePath, destinationPath, platform = process.platform) {
|
|
216
|
+
try {
|
|
217
|
+
await rename(sourcePath, destinationPath);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (platform !== "win32" || error.code !== "EPERM") {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
await unlink(destinationPath);
|
|
224
|
+
await rename(sourcePath, destinationPath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
202
227
|
function assertStableTransition(current, updated) {
|
|
203
228
|
for (const [before, after] of [
|
|
204
229
|
[current.operationId, updated.operationId],
|
|
@@ -553,7 +578,7 @@ export class OperationJournal {
|
|
|
553
578
|
await handle.sync();
|
|
554
579
|
await handle.close();
|
|
555
580
|
handle = undefined;
|
|
556
|
-
await
|
|
581
|
+
await renameWithFallback(temporaryPath, recordPath);
|
|
557
582
|
if (process.platform !== "win32")
|
|
558
583
|
await chmod(recordPath, 0o600);
|
|
559
584
|
await this.#syncDirectory();
|
|
@@ -541,11 +541,37 @@ function remoteContext(value) {
|
|
|
541
541
|
}
|
|
542
542
|
return context;
|
|
543
543
|
}
|
|
544
|
+
function _userFacingMessage(code, result) {
|
|
545
|
+
const error = result.error;
|
|
546
|
+
if (!error.file_uploaded && !error.parser_started) {
|
|
547
|
+
if (code === "antibot_blocked" || code === "empty_content") {
|
|
548
|
+
return "目标网站返回了验证码或空白页面,解析器无法提取内容。可尝试用本地 Bridge 下载页面后上传。";
|
|
549
|
+
}
|
|
550
|
+
if (code === "SERVICE_TEMPORARILY_UNAVAILABLE") {
|
|
551
|
+
return "解析服务暂时不可用,请稍后重试。";
|
|
552
|
+
}
|
|
553
|
+
if (code === "ssrf_blocked") {
|
|
554
|
+
return "该链接指向内部/私有地址,已被安全策略拦截。";
|
|
555
|
+
}
|
|
556
|
+
return `文件上传失败(${code})。请检查网络连接,或尝试用本地 Bridge 下载后上传。`;
|
|
557
|
+
}
|
|
558
|
+
if (!error.parser_started) {
|
|
559
|
+
return `文件已上传但解析未能启动(${code})。可能是文件格式不支持或文件损坏。`;
|
|
560
|
+
}
|
|
561
|
+
return `解析失败(${code})。可尝试更换链接或文件格式。`;
|
|
562
|
+
}
|
|
544
563
|
function remoteFailure(result) {
|
|
545
564
|
const error = result.error;
|
|
565
|
+
// Map internal error codes to user-facing messages so the caller (and the
|
|
566
|
+
// user) can understand what happened without reading protocol internals.
|
|
567
|
+
const code = error.code || "UNKNOWN_ERROR";
|
|
568
|
+
let message = error.message || "";
|
|
569
|
+
if (!message || message === code) {
|
|
570
|
+
message = _userFacingMessage(code, result);
|
|
571
|
+
}
|
|
546
572
|
return new OmniBridgeError({
|
|
547
|
-
code
|
|
548
|
-
message
|
|
573
|
+
code,
|
|
574
|
+
message,
|
|
549
575
|
...(error.failure_scope === undefined ? {} : { failureScope: error.failure_scope }),
|
|
550
576
|
sourceKind: "url",
|
|
551
577
|
...(error.user_action === undefined ? {} : { userAction: error.user_action }),
|
package/dist/result-contract.js
CHANGED
|
@@ -210,26 +210,24 @@ export const discardResultToolOutputSchema = z
|
|
|
210
210
|
error: stableErrorSchema.optional(),
|
|
211
211
|
})
|
|
212
212
|
.strict();
|
|
213
|
-
function
|
|
214
|
-
if (value.status === "
|
|
215
|
-
const
|
|
216
|
-
|
|
213
|
+
function textFallback(value) {
|
|
214
|
+
if (value.status === "completed") {
|
|
215
|
+
const result = value.result;
|
|
216
|
+
if (result !== null
|
|
217
|
+
&& typeof result === "object"
|
|
218
|
+
&& !Array.isArray(result)
|
|
219
|
+
&& result.kind === "inline"
|
|
220
|
+
&& typeof result.text === "string") {
|
|
221
|
+
return result.text;
|
|
222
|
+
}
|
|
217
223
|
}
|
|
218
|
-
|
|
219
|
-
return "Omni processing.";
|
|
220
|
-
if (value.status === "cleanup_pending")
|
|
221
|
-
return "Omni cleanup pending.";
|
|
222
|
-
if (value.status === "canceled")
|
|
223
|
-
return "Omni canceled.";
|
|
224
|
-
if (value.status === "expired")
|
|
225
|
-
return "Omni result expired.";
|
|
226
|
-
return "Omni completed.";
|
|
224
|
+
return JSON.stringify(value);
|
|
227
225
|
}
|
|
228
226
|
export function structuredResult(schema, value) {
|
|
229
227
|
const structuredContent = schema.parse(value);
|
|
230
228
|
return {
|
|
231
229
|
...(structuredContent.status === "failed" ? { isError: true } : {}),
|
|
232
230
|
structuredContent,
|
|
233
|
-
content: [{ type: "text", text:
|
|
231
|
+
content: [{ type: "text", text: textFallback(structuredContent) }],
|
|
234
232
|
};
|
|
235
233
|
}
|