ms-vite-plugin 1.1.20 → 1.1.22

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * HTTP API 请求方法
3
+ */
4
+ export type HttpApiMethod = "GET" | "POST";
5
+ /**
6
+ * HTTP API 文档条目
7
+ */
8
+ export type HttpApiDocEntry = {
9
+ /** 稳定 slug,可用于读取单个接口文档 */
10
+ slug: string;
11
+ /** 接口请求方法 */
12
+ method: HttpApiMethod;
13
+ /** 接口路径 */
14
+ path: string;
15
+ /** 接口标题 */
16
+ title: string;
17
+ /** 所属一级章节 */
18
+ section: string;
19
+ /** 文档片段起始行号(1-based) */
20
+ startLine: number;
21
+ /** 文档片段结束行号(1-based) */
22
+ endLine: number;
23
+ /** 接口完整 markdown 片段 */
24
+ content: string;
25
+ };
26
+ /**
27
+ * 读取 HTTP API markdown 文档
28
+ * @returns 返回完整 markdown 文本
29
+ * @example
30
+ * const markdown = await readHttpApiMarkdown()
31
+ */
32
+ export declare function readHttpApiMarkdown(): Promise<string>;
33
+ /**
34
+ * 根据 method/path 生成稳定 slug
35
+ * @param method HTTP 请求方法
36
+ * @param apiPath HTTP API 路径
37
+ * @returns 返回可用于 MCP 资源路径的 slug
38
+ * @example
39
+ * createHttpApiSlug("GET", "/api/control/click")
40
+ */
41
+ export declare function createHttpApiSlug(method: HttpApiMethod, apiPath: string): string;
42
+ /**
43
+ * 解析 HTTP API markdown 为接口条目
44
+ * @returns 返回按文档顺序排列的接口条目
45
+ * @example
46
+ * const entries = await getHttpApiDocEntries()
47
+ */
48
+ export declare function getHttpApiDocEntries(): Promise<HttpApiDocEntry[]>;
49
+ /**
50
+ * 计算 HTTP API 文档条目搜索分数
51
+ * @param entry 文档条目
52
+ * @param normalizedQuery 已转小写的搜索词
53
+ * @returns 返回匹配分数,0 表示不匹配
54
+ * @example
55
+ * scoreHttpApiDocEntry(entry, "click")
56
+ */
57
+ export declare function scoreHttpApiDocEntry(entry: HttpApiDocEntry, normalizedQuery: string): number;
58
+ /**
59
+ * 根据 slug 或 path 查找 HTTP API 文档条目
60
+ * @param identifier slug 或 API 路径
61
+ * @param method 可选 HTTP 方法,用于同路径多方法时消歧
62
+ * @returns 找到时返回条目,否则返回 undefined
63
+ * @example
64
+ * const entry = await findHttpApiDocEntry("/api/control/click", "GET")
65
+ */
66
+ export declare function findHttpApiDocEntry(identifier: string, method?: HttpApiMethod): Promise<HttpApiDocEntry | undefined>;
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readHttpApiMarkdown = readHttpApiMarkdown;
37
+ exports.createHttpApiSlug = createHttpApiSlug;
38
+ exports.getHttpApiDocEntries = getHttpApiDocEntries;
39
+ exports.scoreHttpApiDocEntry = scoreHttpApiDocEntry;
40
+ exports.findHttpApiDocEntry = findHttpApiDocEntry;
41
+ const fsExtra = __importStar(require("fs-extra"));
42
+ const path = __importStar(require("path"));
43
+ const HTTP_API_DOC_RELATIVE_PATH = ["docs", "httpapi", "api.md"];
44
+ const HTTP_API_ENDPOINT_PATTERN = /^(GET|POST)\s+(\/\S+)/;
45
+ const HTTP_API_SECTION_PATTERN = /^#\s+(.+)$/;
46
+ const HTTP_API_TITLE_PATTERN = /^##\s+(.+)$/;
47
+ let cachedHttpApiDocEntries = null;
48
+ /**
49
+ * 获取 HTTP API 文档路径
50
+ * @returns 返回 HTTP API 文档绝对路径
51
+ * @example
52
+ * const filePath = getHttpApiDocPath()
53
+ */
54
+ function getHttpApiDocPath() {
55
+ return path.resolve(__dirname, "..", "..", ...HTTP_API_DOC_RELATIVE_PATH);
56
+ }
57
+ /**
58
+ * 读取 HTTP API markdown 文档
59
+ * @returns 返回完整 markdown 文本
60
+ * @example
61
+ * const markdown = await readHttpApiMarkdown()
62
+ */
63
+ async function readHttpApiMarkdown() {
64
+ const filePath = getHttpApiDocPath();
65
+ if (!(await fsExtra.pathExists(filePath))) {
66
+ throw new Error("HTTP API 文档不可用。");
67
+ }
68
+ return fsExtra.readFile(filePath, "utf8");
69
+ }
70
+ /**
71
+ * 根据 method/path 生成稳定 slug
72
+ * @param method HTTP 请求方法
73
+ * @param apiPath HTTP API 路径
74
+ * @returns 返回可用于 MCP 资源路径的 slug
75
+ * @example
76
+ * createHttpApiSlug("GET", "/api/control/click")
77
+ */
78
+ function createHttpApiSlug(method, apiPath) {
79
+ return `${method.toLowerCase()}-${apiPath}`
80
+ .replace(/[^a-zA-Z0-9]+/g, "-")
81
+ .replace(/^-+|-+$/g, "")
82
+ .toLowerCase();
83
+ }
84
+ /**
85
+ * 解析 HTTP API markdown 为接口条目
86
+ * @returns 返回按文档顺序排列的接口条目
87
+ * @example
88
+ * const entries = await getHttpApiDocEntries()
89
+ */
90
+ async function getHttpApiDocEntries() {
91
+ const filePath = getHttpApiDocPath();
92
+ const stat = await fsExtra.stat(filePath).catch(() => null);
93
+ if (!stat) {
94
+ throw new Error("HTTP API 文档不可用。");
95
+ }
96
+ if (cachedHttpApiDocEntries?.mtimeMs === stat.mtimeMs) {
97
+ return cachedHttpApiDocEntries.entries;
98
+ }
99
+ const markdown = await fsExtra.readFile(filePath, "utf8");
100
+ const lines = markdown.split(/\r?\n/);
101
+ const entries = [];
102
+ let currentSection = "";
103
+ for (let index = 0; index < lines.length; index += 1) {
104
+ const line = lines[index] ?? "";
105
+ const sectionMatch = line.match(HTTP_API_SECTION_PATTERN);
106
+ if (sectionMatch) {
107
+ currentSection = sectionMatch[1]?.trim() ?? "";
108
+ continue;
109
+ }
110
+ const titleMatch = line.match(HTTP_API_TITLE_PATTERN);
111
+ if (!titleMatch) {
112
+ continue;
113
+ }
114
+ const title = titleMatch[1]?.trim() ?? "";
115
+ let method = null;
116
+ let apiPath = "";
117
+ for (let cursor = index + 1; cursor < Math.min(lines.length, index + 8); cursor += 1) {
118
+ const endpointMatch = (lines[cursor] ?? "").match(HTTP_API_ENDPOINT_PATTERN);
119
+ if (endpointMatch) {
120
+ method = endpointMatch[1];
121
+ apiPath = endpointMatch[2] ?? "";
122
+ break;
123
+ }
124
+ }
125
+ if (!method || !apiPath) {
126
+ continue;
127
+ }
128
+ let endIndex = lines.length;
129
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
130
+ if (HTTP_API_TITLE_PATTERN.test(lines[cursor] ?? "")) {
131
+ endIndex = cursor;
132
+ break;
133
+ }
134
+ }
135
+ entries.push({
136
+ slug: createHttpApiSlug(method, apiPath),
137
+ method,
138
+ path: apiPath,
139
+ title,
140
+ section: currentSection,
141
+ startLine: index + 1,
142
+ endLine: endIndex,
143
+ content: lines.slice(index, endIndex).join("\n").trim(),
144
+ });
145
+ }
146
+ cachedHttpApiDocEntries = {
147
+ mtimeMs: stat.mtimeMs,
148
+ entries,
149
+ };
150
+ return entries;
151
+ }
152
+ /**
153
+ * 计算 HTTP API 文档条目搜索分数
154
+ * @param entry 文档条目
155
+ * @param normalizedQuery 已转小写的搜索词
156
+ * @returns 返回匹配分数,0 表示不匹配
157
+ * @example
158
+ * scoreHttpApiDocEntry(entry, "click")
159
+ */
160
+ function scoreHttpApiDocEntry(entry, normalizedQuery) {
161
+ const query = normalizedQuery.trim();
162
+ if (!query) {
163
+ return 0;
164
+ }
165
+ let score = 0;
166
+ if (entry.path.toLowerCase() === query) {
167
+ score += 100;
168
+ }
169
+ if (`${entry.method} ${entry.path}`.toLowerCase() === query) {
170
+ score += 120;
171
+ }
172
+ if (entry.path.toLowerCase().includes(query)) {
173
+ score += 60;
174
+ }
175
+ if (entry.title.toLowerCase().includes(query)) {
176
+ score += 50;
177
+ }
178
+ if (entry.section.toLowerCase().includes(query)) {
179
+ score += 20;
180
+ }
181
+ if (entry.content.toLowerCase().includes(query)) {
182
+ score += 10;
183
+ }
184
+ return score;
185
+ }
186
+ /**
187
+ * 根据 slug 或 path 查找 HTTP API 文档条目
188
+ * @param identifier slug 或 API 路径
189
+ * @param method 可选 HTTP 方法,用于同路径多方法时消歧
190
+ * @returns 找到时返回条目,否则返回 undefined
191
+ * @example
192
+ * const entry = await findHttpApiDocEntry("/api/control/click", "GET")
193
+ */
194
+ async function findHttpApiDocEntry(identifier, method) {
195
+ const normalizedIdentifier = identifier.trim();
196
+ const entries = await getHttpApiDocEntries();
197
+ return entries.find((entry) => {
198
+ const methodMatches = method ? entry.method === method : true;
199
+ return (methodMatches &&
200
+ (entry.slug === normalizedIdentifier ||
201
+ entry.path === normalizedIdentifier));
202
+ });
203
+ }
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * 注册 HTTP API 文档与通用调用工具
4
+ * @param server MCP 服务实例
5
+ * @returns 无返回值
6
+ * @example
7
+ * registerHttpApiTools(server)
8
+ */
9
+ export declare function registerHttpApiTools(server: McpServer): void;
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerHttpApiTools = registerHttpApiTools;
37
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ const z = __importStar(require("zod/v4"));
39
+ const httpapi_docs_service_1 = require("./httpapi-docs-service");
40
+ const tool_utils_1 = require("./tool-utils");
41
+ /**
42
+ * HTTP API 文档完整资源 URI
43
+ */
44
+ const HTTP_API_DOC_RESOURCE_URI = "kuaijs://httpapi/api";
45
+ const HTTP_API_ENDPOINT_RESOURCE_PREFIX = "kuaijs://httpapi/endpoints";
46
+ /**
47
+ * 格式化 HTTP API 文档搜索摘要
48
+ * @param entry HTTP API 文档条目
49
+ * @param index 列表序号
50
+ * @returns 返回摘要文本
51
+ * @example
52
+ * formatHttpApiDocSummary(entry, 0)
53
+ */
54
+ function formatHttpApiDocSummary(entry, index) {
55
+ return [
56
+ `${index + 1}. ${entry.title}`,
57
+ `method: ${entry.method}`,
58
+ `path: ${entry.path}`,
59
+ `slug: ${entry.slug}`,
60
+ `section: ${entry.section || "未分组"}`,
61
+ `lines: ${entry.startLine}-${entry.endLine}`,
62
+ `uri: ${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
63
+ ].join("\n");
64
+ }
65
+ /**
66
+ * 标准化并校验 HTTP API 路径输入
67
+ * @param apiPath 用户传入路径
68
+ * @returns 返回标准化后的路径
69
+ * @example
70
+ * normalizeHttpApiPath("/api/status")
71
+ */
72
+ function normalizeHttpApiPath(apiPath) {
73
+ const normalizedPath = apiPath.trim();
74
+ if (/^https?:\/\//i.test(normalizedPath)) {
75
+ throw new Error("path 只能传 HTTP API 相对路径,不能传完整 URL。");
76
+ }
77
+ if (!normalizedPath.startsWith("/")) {
78
+ throw new Error("path 必须以 / 开头,例如 /api/status。");
79
+ }
80
+ if (normalizedPath.includes("?")) {
81
+ throw new Error("path 不能包含 query string,请使用 query 参数传递查询参数。");
82
+ }
83
+ return normalizedPath;
84
+ }
85
+ /**
86
+ * 将 query 参数追加到 URL
87
+ * @param url 目标 URL
88
+ * @param query 可选 query 参数
89
+ * @returns 无返回值
90
+ * @example
91
+ * appendQueryParams(url, { x: 1 })
92
+ */
93
+ function appendQueryParams(url, query) {
94
+ if (!query) {
95
+ return;
96
+ }
97
+ for (const [key, value] of Object.entries(query)) {
98
+ if (value === undefined || value === null) {
99
+ continue;
100
+ }
101
+ url.searchParams.set(key, String(value));
102
+ }
103
+ }
104
+ /**
105
+ * 读取响应体并按要求格式化
106
+ * @param response fetch 响应对象
107
+ * @param responseFormat 响应格式
108
+ * @returns 返回格式化文本
109
+ * @example
110
+ * const text = await readHttpApiResponseText(response, "json")
111
+ */
112
+ async function readHttpApiResponseText(response, responseFormat) {
113
+ const responseText = await response.text();
114
+ if (responseFormat === "text") {
115
+ return responseText;
116
+ }
117
+ try {
118
+ return (0, tool_utils_1.formatRuntimeJsonText)(JSON.parse(responseText));
119
+ }
120
+ catch {
121
+ throw new Error("响应不是有效 JSON;如需读取文本响应,请设置 responseFormat=text。");
122
+ }
123
+ }
124
+ /**
125
+ * 注册 HTTP API 文档与通用调用工具
126
+ * @param server MCP 服务实例
127
+ * @returns 无返回值
128
+ * @example
129
+ * registerHttpApiTools(server)
130
+ */
131
+ function registerHttpApiTools(server) {
132
+ server.registerResource("http-api-doc", HTTP_API_DOC_RESOURCE_URI, {
133
+ title: "KuaiJS HTTP API document",
134
+ mimeType: "text/markdown",
135
+ }, async () => ({
136
+ contents: [
137
+ {
138
+ uri: HTTP_API_DOC_RESOURCE_URI,
139
+ mimeType: "text/markdown",
140
+ text: await (0, httpapi_docs_service_1.readHttpApiMarkdown)(),
141
+ },
142
+ ],
143
+ }));
144
+ server.registerResource("http-api-endpoint", new mcp_js_1.ResourceTemplate(`${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/{slug}`, {
145
+ list: async () => {
146
+ const entries = await (0, httpapi_docs_service_1.getHttpApiDocEntries)();
147
+ return {
148
+ resources: entries.map((entry) => ({
149
+ uri: `${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
150
+ name: `${entry.method} ${entry.path}`,
151
+ description: `${entry.title} [${entry.section || "未分组"}]`,
152
+ mimeType: "text/markdown",
153
+ })),
154
+ };
155
+ },
156
+ }), {
157
+ title: "KuaiJS HTTP API endpoint documents",
158
+ mimeType: "text/markdown",
159
+ }, async (_uri, variables) => {
160
+ const slug = String(variables.slug || "");
161
+ const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(slug);
162
+ if (!entry) {
163
+ throw new Error(`HTTP API 文档不存在: ${slug}`);
164
+ }
165
+ return {
166
+ contents: [
167
+ {
168
+ uri: `${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
169
+ mimeType: "text/markdown",
170
+ text: entry.content,
171
+ },
172
+ ],
173
+ };
174
+ });
175
+ server.registerTool("search_http_api_docs", {
176
+ title: "Search HTTP API Docs",
177
+ description: "搜索 HTTP API 文档中声明的接口。调用 http_api_call 前应先用本工具确认接口 method、path、参数和 slug。",
178
+ inputSchema: {
179
+ query: z
180
+ .string()
181
+ .min(1)
182
+ .describe("搜索关键词,例如 click、/api/control/click、截图"),
183
+ limit: z
184
+ .number()
185
+ .int()
186
+ .min(1)
187
+ .max(20)
188
+ .optional()
189
+ .default(8)
190
+ .describe("返回条数上限,默认 8"),
191
+ },
192
+ }, async ({ query, limit }) => {
193
+ const normalizedQuery = query.toLowerCase();
194
+ const entries = await (0, httpapi_docs_service_1.getHttpApiDocEntries)();
195
+ const matches = entries
196
+ .map((entry) => ({
197
+ entry,
198
+ score: (0, httpapi_docs_service_1.scoreHttpApiDocEntry)(entry, normalizedQuery),
199
+ }))
200
+ .filter((item) => item.score > 0)
201
+ .sort((a, b) => b.score - a.score ||
202
+ a.entry.path.localeCompare(b.entry.path) ||
203
+ a.entry.method.localeCompare(b.entry.method))
204
+ .slice(0, limit)
205
+ .map((item) => item.entry);
206
+ return (0, tool_utils_1.createTextToolResult)(matches.length === 0
207
+ ? `未找到与 "${query}" 匹配的 HTTP API 文档。`
208
+ : [
209
+ "HTTP API 文档匹配结果:",
210
+ "",
211
+ ...matches.map((entry, index) => formatHttpApiDocSummary(entry, index)),
212
+ ].join("\n"));
213
+ });
214
+ server.registerTool("read_http_api_doc", {
215
+ title: "Read HTTP API Doc",
216
+ description: "按 slug 或 path 读取 HTTP API 文档中的接口片段。调用 http_api_call 前应读取目标接口片段,确认参数位置、类型和返回结构。",
217
+ inputSchema: {
218
+ identifier: z
219
+ .string()
220
+ .min(1)
221
+ .describe("接口 slug 或 path,例如 get-api-control-click 或 /api/control/click"),
222
+ method: z
223
+ .enum(["GET", "POST"])
224
+ .optional()
225
+ .describe("可选 HTTP 方法,用于同路径多方法时消歧"),
226
+ },
227
+ }, async ({ identifier, method }) => {
228
+ const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(identifier, method);
229
+ if (!entry) {
230
+ return (0, tool_utils_1.createTextToolResult)(`未找到 HTTP API 文档: ${identifier}\n请先调用 search_http_api_docs 搜索可用接口。`, true);
231
+ }
232
+ return (0, tool_utils_1.createTextToolResult)([
233
+ `title: ${entry.title}`,
234
+ `method: ${entry.method}`,
235
+ `path: ${entry.path}`,
236
+ `slug: ${entry.slug}`,
237
+ `section: ${entry.section || "未分组"}`,
238
+ `lines: ${entry.startLine}-${entry.endLine}`,
239
+ "",
240
+ entry.content,
241
+ ].join("\n"));
242
+ });
243
+ server.registerTool("http_api_call", {
244
+ title: "HTTP API Call",
245
+ description: "调用 HTTP API 文档中已声明的接口。使用前应先调用 search_http_api_docs/read_http_api_doc 获取 docSlug,并确认 method、path、query/body 参数;本工具会拒绝 docSlug、method、path 不匹配的调用。",
246
+ inputSchema: {
247
+ method: z
248
+ .enum(["GET", "POST"])
249
+ .describe("HTTP 方法,必须与 HTTP API 文档一致"),
250
+ path: z
251
+ .string()
252
+ .min(1)
253
+ .describe("HTTP API 相对路径,例如 /api/control/click;不能包含 query string"),
254
+ docSlug: z
255
+ .string()
256
+ .min(1)
257
+ .describe("search_http_api_docs/read_http_api_doc 返回的接口 slug"),
258
+ query: z
259
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
260
+ .optional()
261
+ .describe("query 参数;GET 接口通常使用 query 传参"),
262
+ body: z
263
+ .unknown()
264
+ .optional()
265
+ .describe("POST 请求体;对象会按 JSON 提交"),
266
+ responseFormat: z
267
+ .enum(["json", "text"])
268
+ .optional()
269
+ .default("json")
270
+ .describe("响应解析格式,默认 json"),
271
+ timeoutMs: z
272
+ .number()
273
+ .int()
274
+ .min(1000)
275
+ .max(600000)
276
+ .optional()
277
+ .default(30000)
278
+ .describe("请求超时时间,默认 30000 毫秒"),
279
+ },
280
+ }, async ({ method, path, docSlug, query, body, responseFormat, timeoutMs, }) => {
281
+ const apiPath = normalizeHttpApiPath(path);
282
+ const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(docSlug);
283
+ if (!entry) {
284
+ return (0, tool_utils_1.createTextToolResult)([
285
+ `HTTP API 文档中不存在该 docSlug: ${docSlug}`,
286
+ "请先调用 search_http_api_docs 搜索可用接口,再调用 read_http_api_doc 确认参数和 slug。",
287
+ ].join("\n"), true);
288
+ }
289
+ if (entry.method !== method || entry.path !== apiPath) {
290
+ return (0, tool_utils_1.createTextToolResult)([
291
+ "HTTP API 调用与文档条目不匹配。",
292
+ `传入: ${method} ${apiPath} (${docSlug})`,
293
+ `文档: ${entry.method} ${entry.path} (${entry.slug})`,
294
+ "请重新调用 read_http_api_doc 确认目标接口。",
295
+ ].join("\n"), true);
296
+ }
297
+ if (method === "GET" && body !== undefined) {
298
+ return (0, tool_utils_1.createTextToolResult)("GET 接口不能传 body,请使用 query 参数。", true);
299
+ }
300
+ const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
301
+ const url = new URL(`http://${target.ip}:${target.port}${apiPath}`);
302
+ appendQueryParams(url, query);
303
+ const controller = new AbortController();
304
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
305
+ try {
306
+ const headers = {};
307
+ let requestBody;
308
+ if (body !== undefined) {
309
+ if (typeof body === "string") {
310
+ requestBody = body;
311
+ headers["Content-Type"] = "text/plain; charset=utf-8";
312
+ }
313
+ else {
314
+ requestBody = JSON.stringify(body);
315
+ headers["Content-Type"] = "application/json";
316
+ }
317
+ }
318
+ const response = await fetch(url.toString(), {
319
+ method,
320
+ signal: controller.signal,
321
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
322
+ body: requestBody,
323
+ });
324
+ const responseText = await readHttpApiResponseText(response, responseFormat);
325
+ return (0, tool_utils_1.createTextToolResult)([
326
+ `HTTP API 调用${response.ok ? "成功" : "失败"}: ${method} ${apiPath}`,
327
+ `设备: ${target.label}`,
328
+ `文档: ${entry.title} (${entry.slug}, lines ${entry.startLine}-${entry.endLine})`,
329
+ `状态码: ${response.status}`,
330
+ "",
331
+ responseText,
332
+ ].join("\n"), !response.ok);
333
+ }
334
+ catch (error) {
335
+ const message = error instanceof Error && error.name === "AbortError"
336
+ ? `请求超时: ${timeoutMs}ms`
337
+ : error instanceof Error
338
+ ? error.message
339
+ : String(error);
340
+ return (0, tool_utils_1.createTextToolResult)(`HTTP API 调用失败: ${method} ${apiPath}\n${message}`, true);
341
+ }
342
+ finally {
343
+ clearTimeout(timeout);
344
+ }
345
+ });
346
+ }