u1s1-cli 1.7.0 → 1.8.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/dist/agent-setup.d.ts +7 -0
- package/dist/agent-setup.js +35 -2
- package/dist/api.d.ts +5 -0
- package/dist/api.js +5 -0
- package/dist/brand.js +1 -1
- package/dist/error-humanize.js +21 -1
- package/dist/index.js +47 -4
- package/dist/login.js +2 -0
- package/dist/mcp/client.d.ts +53 -0
- package/dist/mcp/client.js +452 -0
- package/dist/mcp/command.d.ts +25 -0
- package/dist/mcp/command.js +349 -0
- package/dist/mcp/config.d.ts +59 -0
- package/dist/mcp/config.js +0 -0
- package/dist/mcp/extension.d.ts +14 -0
- package/dist/mcp/extension.js +128 -0
- package/dist/mcp/tools.d.ts +22 -0
- package/dist/mcp/tools.js +96 -0
- package/dist/nudges.d.ts +1 -1
- package/dist/nudges.js +1 -1
- package/dist/update.js +1 -0
- package/dist/usage.d.ts +6 -2
- package/dist/usage.js +12 -5
- package/dist/web.js +3 -1
- package/package.json +1 -1
- package/scripts/patch-pi.js +151 -0
package/dist/agent-setup.d.ts
CHANGED
|
@@ -49,6 +49,13 @@ export declare function writeErrorHumanizeExtension(): void;
|
|
|
49
49
|
* print/json 一次性模式不起轮询;import 失败静默跳过(同 error-humanize)。
|
|
50
50
|
*/
|
|
51
51
|
export declare function writeAnnouncementsExtension(): void;
|
|
52
|
+
/**
|
|
53
|
+
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js。
|
|
54
|
+
* 读 ~/.u1s1/mcp.json,把每个服务的工具注册成 mcp_<服务>_<工具>;逻辑在 mcp/extension.ts,
|
|
55
|
+
* 这里只负责投影(Desktop App 共享)。没配置任何服务时扩展直接返回,零开销。
|
|
56
|
+
* 子会话(spawn_subagent)里不挂 MCP 工具:避免每个子进程都拉起一份服务。
|
|
57
|
+
*/
|
|
58
|
+
export declare function writeMcpExtension(): void;
|
|
52
59
|
/**
|
|
53
60
|
* 生成「OpenRouter 应用归因」扩展到 <agentDir>/extensions/u1s1-attribution.js。
|
|
54
61
|
* 用户自己配 OPENROUTER_API_KEY 直连时,pi 内置的归因头归到 pi.dev 名下(且受遥测
|
package/dist/agent-setup.js
CHANGED
|
@@ -332,6 +332,35 @@ export function writeAnnouncementsExtension() {
|
|
|
332
332
|
` });\n` +
|
|
333
333
|
`}\n`);
|
|
334
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js。
|
|
337
|
+
* 读 ~/.u1s1/mcp.json,把每个服务的工具注册成 mcp_<服务>_<工具>;逻辑在 mcp/extension.ts,
|
|
338
|
+
* 这里只负责投影(Desktop App 共享)。没配置任何服务时扩展直接返回,零开销。
|
|
339
|
+
* 子会话(spawn_subagent)里不挂 MCP 工具:避免每个子进程都拉起一份服务。
|
|
340
|
+
*/
|
|
341
|
+
export function writeMcpExtension() {
|
|
342
|
+
const dir = join(agentDir, "extensions");
|
|
343
|
+
mkdirSync(dir, { recursive: true });
|
|
344
|
+
const url = new URL("./mcp/extension.js", import.meta.url).href;
|
|
345
|
+
writeFileSync(join(dir, "u1s1-mcp.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
346
|
+
`export default async function (pi) {\n` +
|
|
347
|
+
` if (process.env.U1S1_IN_SUBAGENT === "1") return;\n` +
|
|
348
|
+
` let registerMcpServers;\n` +
|
|
349
|
+
` try {\n` +
|
|
350
|
+
` registerMcpServers = (await import(${JSON.stringify(url)})).registerMcpServers;\n` +
|
|
351
|
+
` } catch {\n` +
|
|
352
|
+
` return;\n` +
|
|
353
|
+
` }\n` +
|
|
354
|
+
` try {\n` +
|
|
355
|
+
` await registerMcpServers(pi);\n` +
|
|
356
|
+
` } catch (err) {\n` +
|
|
357
|
+
` const msg = err instanceof Error ? err.message : String(err);\n` +
|
|
358
|
+
` pi.on("session_start", (_event, ctx) => {\n` +
|
|
359
|
+
` if (ctx.hasUI) ctx.ui.notify("MCP 服务加载失败:" + msg, "warning");\n` +
|
|
360
|
+
` });\n` +
|
|
361
|
+
` }\n` +
|
|
362
|
+
`}\n`);
|
|
363
|
+
}
|
|
335
364
|
/**
|
|
336
365
|
* 生成「OpenRouter 应用归因」扩展到 <agentDir>/extensions/u1s1-attribution.js。
|
|
337
366
|
* 用户自己配 OPENROUTER_API_KEY 直连时,pi 内置的归因头归到 pi.dev 名下(且受遥测
|
|
@@ -377,6 +406,10 @@ export default async function (pi) {
|
|
|
377
406
|
// ---- 汇总统计 ----
|
|
378
407
|
let counts = {};
|
|
379
408
|
let errors = 0;
|
|
409
|
+
// 工具调用被折叠后,这一行摘要是新手了解「AI 刚才做了什么」的唯一入口,内置工具名给人话
|
|
410
|
+
const TOOL_SUMMARY_LABELS: Record<string, string> = {
|
|
411
|
+
read: "读文件", write: "写文件", edit: "改文件", bash: "跑命令", grep: "搜内容", find: "找文件", ls: "列目录",
|
|
412
|
+
};
|
|
380
413
|
pi.on("agent_start", async () => {
|
|
381
414
|
counts = {};
|
|
382
415
|
errors = 0;
|
|
@@ -392,14 +425,14 @@ export default async function (pi) {
|
|
|
392
425
|
if (total === 0) return;
|
|
393
426
|
const parts = Object.entries(counts)
|
|
394
427
|
.sort((a, b) => b[1] - a[1])
|
|
395
|
-
.map(([name, n]) => name + "×" + n);
|
|
428
|
+
.map(([name, n]) => (TOOL_SUMMARY_LABELS[name] ?? name) + "×" + n);
|
|
396
429
|
if (errors > 0) parts.push("✗ " + errors);
|
|
397
430
|
pi.appendEntry("tool-summary", { total, summary: parts.join(" · ") });
|
|
398
431
|
});
|
|
399
432
|
pi.registerEntryRenderer("tool-summary", (entry, _opts, theme) => {
|
|
400
433
|
const d = entry.data;
|
|
401
434
|
let text = theme.fg("muted", "⚙ ");
|
|
402
|
-
text += theme.fg("toolTitle", theme.bold(d.total + "
|
|
435
|
+
text += theme.fg("toolTitle", theme.bold(d.total + " 次工具调用"));
|
|
403
436
|
text += theme.fg("dim", " · " + d.summary);
|
|
404
437
|
return new Text(text, 0, 0);
|
|
405
438
|
});
|
package/dist/api.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface MeResponse {
|
|
|
24
24
|
packages?: MePackage[];
|
|
25
25
|
/** 可领取的免费包:'first' 首月包 / 'renew' 年度包 / null 已有生效中的 */
|
|
26
26
|
free_claim?: "first" | "renew" | null;
|
|
27
|
+
/** 支付通道是否开放;老网关没有该字段,按开放处理(保留充值入口) */
|
|
28
|
+
pay_enabled?: boolean;
|
|
27
29
|
}
|
|
28
30
|
export interface MePackage {
|
|
29
31
|
id: number;
|
|
@@ -63,6 +65,9 @@ export interface ModelsResponse {
|
|
|
63
65
|
/** 401:凭证失效(设备被移除/换过钥匙),调用方应引导重新登录而不是继续硬跑。 */
|
|
64
66
|
export declare class AuthError extends Error {
|
|
65
67
|
}
|
|
68
|
+
/** 403:登录着但被服务端拒绝(封禁/停用/设备不受信任),重新登录也没用,进 TUI 每条消息都会失败。 */
|
|
69
|
+
export declare class AccessDeniedError extends Error {
|
|
70
|
+
}
|
|
66
71
|
export declare function fetchModels(cfg: CliConfig): Promise<ModelsResponse>;
|
|
67
72
|
/**
|
|
68
73
|
* 会话内公告轮询用的轻量接口:免鉴权、服务端有缓存,回的就是 /v1/models 里的 announcement
|
package/dist/api.js
CHANGED
|
@@ -57,6 +57,9 @@ export async function readJsonResponseCapped(resp, maxBytes = MAX_API_RESPONSE_B
|
|
|
57
57
|
/** 401:凭证失效(设备被移除/换过钥匙),调用方应引导重新登录而不是继续硬跑。 */
|
|
58
58
|
export class AuthError extends Error {
|
|
59
59
|
}
|
|
60
|
+
/** 403:登录着但被服务端拒绝(封禁/停用/设备不受信任),重新登录也没用,进 TUI 每条消息都会失败。 */
|
|
61
|
+
export class AccessDeniedError extends Error {
|
|
62
|
+
}
|
|
60
63
|
/** 网关错误壳统一是 { error: { message } };解析不出来时回退到状态码提示。 */
|
|
61
64
|
async function errorMessageFromResponse(resp, fallback) {
|
|
62
65
|
const body = jsonRecord(await readJsonResponseCapped(resp, MAX_API_ERROR_BYTES).catch(() => null));
|
|
@@ -76,6 +79,8 @@ export async function fetchModels(cfg) {
|
|
|
76
79
|
}
|
|
77
80
|
if (resp.status === 401)
|
|
78
81
|
throw new AuthError("登录已失效,请重新运行 u1s1 login");
|
|
82
|
+
if (resp.status === 403)
|
|
83
|
+
throw new AccessDeniedError(await errorMessageFromResponse(resp, "账号当前无法使用 u1s1"));
|
|
79
84
|
if (!resp.ok)
|
|
80
85
|
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
|
|
81
86
|
const body = await readJsonResponseCapped(resp);
|
package/dist/brand.js
CHANGED
|
@@ -54,7 +54,7 @@ function starterLines(theme) {
|
|
|
54
54
|
` ${theme.fg("text", "「做一个自我介绍网页,做完帮我发布出去」")}`,
|
|
55
55
|
` ${theme.fg("text", "「做一个给朋友的生日祝福页面,要有点小动画」")}`,
|
|
56
56
|
` ${theme.fg("text", "「写一个把文件夹里照片按日期重命名的小工具」")}`,
|
|
57
|
-
` ${theme.fg("muted", "
|
|
57
|
+
` ${theme.fg("muted", "网页做好后,输入 /exit 回到终端,运行 u1s1 deploy --public 一键上线,拿到可分享的链接发给朋友")}`,
|
|
58
58
|
];
|
|
59
59
|
}
|
|
60
60
|
/**
|
package/dist/error-humanize.js
CHANGED
|
@@ -55,10 +55,30 @@ export function extractRequestId(raw) {
|
|
|
55
55
|
const id = payload?.err["request_id"];
|
|
56
56
|
return typeof id === "string" && id.length > 0 && id.length <= 128 ? id : undefined;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 非 JSON 的失败体(Cloudflare 5xx HTML 页、纯文本网关错误)原样直出就是
|
|
60
|
+
* `Error: 503 <!DOCTYPE html>…` 一大坨。只认得出状态码就给一句人话,
|
|
61
|
+
* 状态码数字保留在尾巴里,pi 的重试分类(5xx/429 可重试)不受影响。
|
|
62
|
+
*/
|
|
63
|
+
function humanizeOpaqueError(raw) {
|
|
64
|
+
const status = /^\s*(\d{3})\b/.exec(raw)?.[1];
|
|
65
|
+
if (!status)
|
|
66
|
+
return undefined;
|
|
67
|
+
const opaque = /<\s*(?:!doctype|html)/i.test(raw) || raw.length > 400;
|
|
68
|
+
if (!opaque)
|
|
69
|
+
return undefined;
|
|
70
|
+
const n = Number(status);
|
|
71
|
+
const body = n >= 500
|
|
72
|
+
? "服务暂时不可用,稍等再试;持续出现可看状态页 https://u1s1.io/status"
|
|
73
|
+
: n === 429
|
|
74
|
+
? "请求太频繁,稍等再试"
|
|
75
|
+
: "请求被拒绝,重试仍失败可运行 u1s1 feedback 反馈";
|
|
76
|
+
return `${body} (HTTP ${status})`;
|
|
77
|
+
}
|
|
58
78
|
export function humanizeModelError(raw, now = Date.now()) {
|
|
59
79
|
const payload = parseErrorPayload(raw);
|
|
60
80
|
if (!payload)
|
|
61
|
-
return
|
|
81
|
+
return humanizeOpaqueError(raw);
|
|
62
82
|
const { err, jsonStart } = payload;
|
|
63
83
|
const message = typeof err["message"] === "string" ? err["message"].trim() : "";
|
|
64
84
|
if (!message)
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync, spawnSync } from "node:child_process";
|
|
3
3
|
import { writeFileSync } from "node:fs";
|
|
4
|
-
import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, ensureWorkflowPromptTemplate, scrubForeignProviderEnv, toProviderModels, writeAnnouncementsExtension, writeAttributionExtension, writeCompactUiExtension, writeErrorHumanizeExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
4
|
+
import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, ensureWorkflowPromptTemplate, scrubForeignProviderEnv, toProviderModels, writeAnnouncementsExtension, writeAttributionExtension, writeMcpExtension, writeCompactUiExtension, writeErrorHumanizeExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import { printConsoleBanner } from "./brand.js";
|
|
7
7
|
import { agentDir, apiModelToDef, CUSTOM_ENDPOINTS, isPortableInstall, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readSettings, refValid, resolvePreferredModel, setModelsFromApi, VERSION, } from "./config.js";
|
|
@@ -9,7 +9,7 @@ import { registerLoopCommand } from "./loop.js";
|
|
|
9
9
|
import { ensureSearchTools } from "./search-tools.js";
|
|
10
10
|
import { ensureUsableShell } from "./shell-doctor.js";
|
|
11
11
|
import { applyBrandUi, setAnnouncement, setUpdateNotice } from "./style.js";
|
|
12
|
-
import { AuthError, fetchModels, loadCustomEndpoints, readJsonResponseCapped } from "./api.js";
|
|
12
|
+
import { AccessDeniedError, AuthError, fetchModels, loadCustomEndpoints, readJsonResponseCapped } from "./api.js";
|
|
13
13
|
import { ensureSigningProxy } from "./device-auth.js";
|
|
14
14
|
import { installChildEnvGuard } from "./secret-env.js";
|
|
15
15
|
import { DEPLOY_HINT_TEXT, markNudge, shouldShowDeployHint } from "./nudges.js";
|
|
@@ -93,6 +93,7 @@ function installPendingUpdate() {
|
|
|
93
93
|
return;
|
|
94
94
|
const { latest, installCmd } = pendingUpdate;
|
|
95
95
|
console.log(`\n⬆ 正在更新到 v${latest}…`);
|
|
96
|
+
console.log(" (可能需要几分钟,期间请不要关闭窗口或按 Ctrl+C,中途打断会装出半截)");
|
|
96
97
|
try {
|
|
97
98
|
execSync(installCmd, { stdio: "inherit" });
|
|
98
99
|
console.log(`✅ 已更新到 v${latest},下次运行 u1s1 生效。`);
|
|
@@ -200,6 +201,12 @@ async function runAgent(cfg, args) {
|
|
|
200
201
|
return undefined;
|
|
201
202
|
});
|
|
202
203
|
}
|
|
204
|
+
else if (e instanceof AccessDeniedError) {
|
|
205
|
+
// 封禁/停用/设备不受信任:进 TUI 后每条消息都会失败,不如现在就把原因说清楚并退出
|
|
206
|
+
console.error(` ${e.message}`);
|
|
207
|
+
console.error(" 这个账号当前无法使用 u1s1。如有疑问可发邮件到 contact@u1s1.io,或在 https://u1s1.io/dashboard 提交工单。");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
203
210
|
else {
|
|
204
211
|
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
205
212
|
}
|
|
@@ -248,6 +255,8 @@ async function runAgent(cfg, args) {
|
|
|
248
255
|
writeAnnouncementsExtension();
|
|
249
256
|
// OpenRouter 应用归因:直连流量计入 u1s1 的公开排行
|
|
250
257
|
writeAttributionExtension();
|
|
258
|
+
// 自定义 MCP 服务的工具(~/.u1s1/mcp.json,同样投影,Desktop 共享)
|
|
259
|
+
writeMcpExtension();
|
|
251
260
|
ensureTmuxKeyboardProtocol();
|
|
252
261
|
// must be set before pi reads them (getAgentDir() reads at call time);
|
|
253
262
|
// 常量部分已在 runAgent 开头、发起 import 之前设好
|
|
@@ -530,6 +539,7 @@ async function run() {
|
|
|
530
539
|
console.log(" u1s1 deploy remove 删除已发布的站点");
|
|
531
540
|
console.log(" u1s1 feedback \"一句话\" 反馈问题或建议(自动建工单,--bug/--question…)");
|
|
532
541
|
console.log(" u1s1 import 导入历史会话(import skills 导入技能)");
|
|
542
|
+
console.log(" u1s1 mcp 配置第三方 MCP 服务(add/list/test/remove/import)");
|
|
533
543
|
console.log(" u1s1 bench 模型编码能力评测");
|
|
534
544
|
console.log(" u1s1 --version 查看版本");
|
|
535
545
|
console.log("");
|
|
@@ -543,6 +553,12 @@ async function run() {
|
|
|
543
553
|
process.exitCode = 1;
|
|
544
554
|
return;
|
|
545
555
|
}
|
|
556
|
+
// `u1s1 login --help` 不该真的去登录、`logout --help` 不该真的退出:先看是不是在问用法
|
|
557
|
+
if (cmd && SIMPLE_COMMAND_HELP[cmd] && (args[1] === "--help" || args[1] === "-h")) {
|
|
558
|
+
for (const line of SIMPLE_COMMAND_HELP[cmd])
|
|
559
|
+
console.log(line);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
546
562
|
if (cmd === "deploy") {
|
|
547
563
|
const { deployCommand, printDeployHelp } = await import("./deploy.js");
|
|
548
564
|
// --help 不该先被拽去登录
|
|
@@ -570,6 +586,11 @@ async function run() {
|
|
|
570
586
|
await feedbackCommand(args.slice(1));
|
|
571
587
|
return;
|
|
572
588
|
}
|
|
589
|
+
if (cmd === "mcp") {
|
|
590
|
+
const { mcpCommand } = await import("./mcp/command.js");
|
|
591
|
+
await mcpCommand(args.slice(1));
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
573
594
|
if (cmd === "model") {
|
|
574
595
|
const { modelCommand } = await import("./model.js");
|
|
575
596
|
await modelCommand(args[1]);
|
|
@@ -622,8 +643,9 @@ async function run() {
|
|
|
622
643
|
}
|
|
623
644
|
// 拼错的子命令(如 `u1s1 depoly`)不该被静默当成第一条消息发给模型白烧 token。
|
|
624
645
|
// 只拦「单个短英文词 + 与已知命令编辑距离很近」的情况,正常 prompt 不受影响。
|
|
646
|
+
// `u1s1 delpoy list` 这类带参数的拼错也拦;长句(≥4 个词)才当 prompt 放行
|
|
625
647
|
if (cmd
|
|
626
|
-
&& args.length
|
|
648
|
+
&& args.length <= 3
|
|
627
649
|
&& /^[a-z][a-z0-9-]{1,15}$/.test(cmd)
|
|
628
650
|
&& !KNOWN_COMMANDS.includes(cmd)) {
|
|
629
651
|
const near = KNOWN_COMMANDS.find((k) => editDistance(k, cmd) <= (k.length <= 4 ? 1 : 2));
|
|
@@ -642,7 +664,28 @@ async function run() {
|
|
|
642
664
|
// autoUpdate 开着且启动时发现了新版:现在装(TUI 已退出,原地重铺安全)
|
|
643
665
|
installPendingUpdate();
|
|
644
666
|
}
|
|
645
|
-
const KNOWN_COMMANDS = ["deploy", "login", "logout", "usage", "model", "update", "import", "
|
|
667
|
+
const KNOWN_COMMANDS = ["deploy", "login", "logout", "usage", "model", "update", "import", "mcp", "bench", "feedback", "help"];
|
|
668
|
+
/** 没有自己 --help 处理的简单子命令:问用法时只打印说明,不执行任何动作。 */
|
|
669
|
+
const SIMPLE_COMMAND_HELP = {
|
|
670
|
+
login: [
|
|
671
|
+
"用法: u1s1 login",
|
|
672
|
+
" 在浏览器里批准这台设备登录 u1s1;没有账号会引导注册。",
|
|
673
|
+
" 登录信息保存在 ~/.u1s1/config.json,退出登录用 u1s1 logout。",
|
|
674
|
+
],
|
|
675
|
+
logout: [
|
|
676
|
+
"用法: u1s1 logout",
|
|
677
|
+
" 退出本机登录,并注销这台设备在服务端的授权(连不上服务器时只清本机)。",
|
|
678
|
+
],
|
|
679
|
+
usage: [
|
|
680
|
+
"用法: u1s1 usage",
|
|
681
|
+
" 查看剩余额度:免费包、已购用量包与余额。会话内也可输入 /usage。",
|
|
682
|
+
],
|
|
683
|
+
update: [
|
|
684
|
+
"用法: u1s1 update",
|
|
685
|
+
" 检查并升级到最新版;npm 安装的走 npm,便携版原地自更新。",
|
|
686
|
+
" 不想每次退出自动更新:在 ~/.u1s1/agent/settings.json 设 \"autoUpdate\": false",
|
|
687
|
+
],
|
|
688
|
+
};
|
|
646
689
|
/** 经典 Levenshtein,命令名都很短,O(nm) 足够。 */
|
|
647
690
|
function editDistance(a, b) {
|
|
648
691
|
const dp = Array.from({ length: a.length + 1 }, (_, i) => i);
|
package/dist/login.js
CHANGED
|
@@ -149,6 +149,8 @@ export async function login(keyArg) {
|
|
|
149
149
|
printConsoleBanner(VERSION);
|
|
150
150
|
const origin = apiOrigin(cfg);
|
|
151
151
|
const failure = {};
|
|
152
|
+
// 慢网下这一步最长 15 秒,横幅之后一片安静像是卡死;先说一声在连
|
|
153
|
+
console.log(" 正在连接 u1s1 服务器…");
|
|
152
154
|
const start = await startDeviceLogin(origin, failure);
|
|
153
155
|
if (!start) {
|
|
154
156
|
if (failure.message) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { McpServerConfig, McpToolInfo } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* 极简 MCP 客户端:只做 initialize / tools/list / tools/call 三件事,
|
|
4
|
+
* 支持 stdio(本机进程,按行 JSON-RPC)和 Streamable HTTP(POST JSON,
|
|
5
|
+
* 响应可能是 JSON 也可能是 SSE)两种传输。不引 SDK:pi 本身没有 MCP client,
|
|
6
|
+
* 我们只需要把远端工具挂成 pi 工具,协议面很窄,自己写反而少一坨依赖。
|
|
7
|
+
*/
|
|
8
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
9
|
+
export declare const DEFAULT_CONNECT_TIMEOUT_MS = 20000;
|
|
10
|
+
export declare const DEFAULT_CALL_TIMEOUT_MS = 120000;
|
|
11
|
+
export interface McpClientOptions {
|
|
12
|
+
connectTimeoutMs?: number;
|
|
13
|
+
callTimeoutMs?: number;
|
|
14
|
+
/** 服务端 stderr / 传输层告警;默认丢弃 */
|
|
15
|
+
onLog?: (line: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface McpServerInfo {
|
|
18
|
+
serverName?: string;
|
|
19
|
+
serverVersion?: string;
|
|
20
|
+
protocolVersion: string;
|
|
21
|
+
}
|
|
22
|
+
export interface McpCallResult {
|
|
23
|
+
text: string;
|
|
24
|
+
isError: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare class McpError extends Error {
|
|
27
|
+
readonly code?: number | undefined;
|
|
28
|
+
constructor(message: string, code?: number | undefined);
|
|
29
|
+
}
|
|
30
|
+
/** 解析 SSE 正文,返回每个 event 的 data 拼接结果 */
|
|
31
|
+
export declare function parseSseData(body: string): string[];
|
|
32
|
+
/** 把 tools/call 返回的 content 数组压成模型可读的文本 */
|
|
33
|
+
export declare function contentToText(result: unknown): McpCallResult;
|
|
34
|
+
export declare function normalizeToolInfo(raw: unknown): McpToolInfo | undefined;
|
|
35
|
+
export declare class McpClient {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
private readonly config;
|
|
38
|
+
private readonly opts;
|
|
39
|
+
private transport;
|
|
40
|
+
private nextId;
|
|
41
|
+
private readonly connectTimeoutMs;
|
|
42
|
+
private readonly callTimeoutMs;
|
|
43
|
+
private connected;
|
|
44
|
+
info: McpServerInfo | undefined;
|
|
45
|
+
constructor(name: string, config: McpServerConfig, opts?: McpClientOptions);
|
|
46
|
+
/** 建连并完成 initialize 握手;重复调用返回同一个 promise,失败后可重试 */
|
|
47
|
+
connect(): Promise<McpServerInfo>;
|
|
48
|
+
private doConnect;
|
|
49
|
+
private rpc;
|
|
50
|
+
listTools(): Promise<McpToolInfo[]>;
|
|
51
|
+
callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpCallResult>;
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
}
|