Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: macos-latest
if: startsWith(github.event.head_commit.message , 'chore(release):')
env:
NODE_OPTIONS: "--max-old-space-size=8192"
NODE_OPTIONS: '--max-old-space-size=8192'
steps:
- uses: actions/checkout@v3
with:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ jobs:
- name: Run Continuous Integration
run: |
npm install
npm run lint
npm run build
npx vitest --watch=false
29 changes: 4 additions & 25 deletions __tests__/tools/query_antv_document.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,7 @@
"properties": {
"library": {
"type": "string",
"enum": [
"g2",
"g6",
"l7",
"x6",
"f2",
"s2",
"g",
"ava",
"adc"
],
"enum": ["g2", "g6", "l7", "x6", "f2", "s2", "g", "ava", "adc"],
"description": "Specified AntV library type, intelligently identified based on user query"
},
"query": {
Expand Down Expand Up @@ -58,28 +48,17 @@
"description": "Subtask topic"
}
},
"required": [
"query",
"topic"
]
"required": ["query", "topic"]
},
"description": "Decomposed subtask list for complex tasks, supports batch processing"
},
"channel": {
"default": "Context7",
"description": "Controls the trade-off between search speed and retrieval accuracy. Use \"Context7\" (Default) for quick, interactive responses (~2s latency). Use \"DeepWiki\" ONLY when the user explicitly requests \"deep research\", \"high accuracy\", \"comprehensive analysis\", or when the query is critical and requires verification, despite the slower speed (~20s latency).",
"enum": [
"Context7",
"DeepWiki"
],
"enum": ["Context7", "DeepWiki"],
"type": "string"
}
},
"required": [
"library",
"query",
"topic",
"intent"
]
"required": ["library", "query", "topic", "intent"]
}
}
17 changes: 6 additions & 11 deletions src/tools/query_antv_document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,27 @@ const QueryAntVDocumentInputSchema = z.object({
.default('Context7')
.describe(
'Controls the trade-off between search speed and retrieval accuracy. ' +
'Use "Context7" (Default) for quick, interactive responses (~2s latency). ' +
'Use "DeepWiki" ONLY when the user explicitly requests "deep research", "high accuracy", "comprehensive analysis", or when the query is critical and requires verification, despite the slower speed (~20s latency).'
'Use "Context7" (Default) for quick, interactive responses (~2s latency). ' +
'Use "DeepWiki" ONLY when the user explicitly requests "deep research", "high accuracy", "comprehensive analysis", or when the query is critical and requires verification, despite the slower speed (~20s latency).',
),
});

type QueryAntVDocumentArgs = z.infer<typeof QueryAntVDocumentInputSchema>;

export async function queryDocRouter(params: {
args: QueryAntVDocumentArgs;
libraryId: string,
topic: string,
tokens?: number
libraryId: string;
topic: string;
tokens?: number;
}) {
const { args, libraryId, topic, tokens } = params;
if (args.channel === 'DeepWiki') {
return await adaptedQueryDeepWiki({
repoName: args.library,
question: topic,
});

} else {
return await fetchLibraryDocumentation(
libraryId,
topic,
tokens,
);
return await fetchLibraryDocumentation(libraryId, topic, tokens);
Comment thread
Alexzjt marked this conversation as resolved.
}
}

Expand Down
56 changes: 31 additions & 25 deletions src/utils/deepwiki.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import EventSource from "eventsource";
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import EventSource from 'eventsource';
import { logger } from './logger';
import { AntVLibrary } from '../types';

Expand Down Expand Up @@ -41,28 +41,28 @@ async function getMcpClient(): Promise<Client> {
// 开始初始化连接
_connectingPromise = (async () => {
try {
logger.info("DeepWiki MCP: 初始化连接...");
logger.info('DeepWiki MCP: 初始化连接...');

const transport = new SSEClientTransport(
new URL("https://mcp.deepwiki.com/sse")
new URL('https://mcp.deepwiki.com/sse'),
);

const client = new Client(
{
name: "deepwiki-node-client",
version: "1.0.0",
name: 'deepwiki-node-client',
version: '1.0.0',
},
{ capabilities: {} }
{ capabilities: {} },
);

// 错误处理:监听传输层关闭或错误,以便下次重连
transport.onerror = (err) => {
logger.error("DeepWiki MCP Transport Error:", err);
logger.error('DeepWiki MCP Transport Error:', err);
resetClient(); // 出错时重置,下次调用会触发重连
};

transport.onclose = () => {
logger.info("DeepWiki MCP Connection Closed");
logger.info('DeepWiki MCP Connection Closed');
resetClient();
};

Expand All @@ -71,11 +71,11 @@ async function getMcpClient(): Promise<Client> {
// 连接成功,赋值给模块级变量
_client = client;
_transport = transport;
logger.info("DeepWiki MCP: 连接成功");
logger.info('DeepWiki MCP: 连接成功');

return client;
} catch (error) {
logger.error("DeepWiki MCP: 连接失败", error);
logger.error('DeepWiki MCP: 连接失败', error);
resetClient();
throw error;
} finally {
Expand Down Expand Up @@ -103,7 +103,10 @@ function resetClient() {
* 查询 DeepWiki
* 外部调用就像调用普通函数一样,无需关心连接管理
*/
export async function queryDeepWiki(_params: {repoName: string, question: string}): Promise<string> {
export async function queryDeepWiki(_params: {
repoName: string;
question: string;
}): Promise<string> {
try {
const params = {
..._params,
Expand All @@ -117,7 +120,7 @@ export async function queryDeepWiki(_params: {repoName: string, question: string

// 调用工具
const result: any = await client.callTool({
name: "ask_question",
name: 'ask_question',
arguments: params,
});

Expand All @@ -127,27 +130,27 @@ export async function queryDeepWiki(_params: {repoName: string, question: string
// result.content 是一个数组,可能包含 text, image 等
// 这里我们使用 reduce 提取所有 text 类型的内容并拼接
let answer = result.content
.filter((item: any) => item.type === "text")
.filter((item: any) => item.type === 'text')
.map((item: any) => item.text)
Comment on lines +133 to 134

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

filtermap 回调中对 item 使用 any 类型会禁用 TypeScript 的类型检查。为了在 map 中获得正确的类型推断,您可以在 filter 中使用类型谓词(type predicate)作为返回类型。这会告诉 TypeScript,filter 之后的数组只包含特定类型的元素,从而增强类型安全。

通过将 filter 的回调返回类型声明为 item is { type: 'text'; text: string },TypeScript 就知道 map 接收的 item 具有 text 属性,且类型为 string

Suggested change
.filter((item: any) => item.type === 'text')
.map((item: any) => item.text)
.filter((item: any): item is { type: 'text'; text: string } => item.type === 'text')
.map((item) => item.text)

.join("\n"); // 如果有多段文本,用换行符拼接
.join('\n'); // 如果有多段文本,用换行符拼接

const regex = /Wiki pages you might want to explore:|View this search on DeepWiki:/i;
const regex =
/Wiki pages you might want to explore:|View this search on DeepWiki:/i;
const splitIndex = answer.search(regex);
// 如果找到了标记 (splitIndex !== -1)
if (splitIndex !== -1) {
// 截取从开头到标记之前的部分,并清理末尾的空白
answer = answer.slice(0, splitIndex).trimEnd();
answer = answer.slice(0, splitIndex).trimEnd();
}

if (!answer || answer.startsWith('Error')) {
// 防御性编程:如果返回内容为空
throw new Error("DeepWiki return Empty/Error Answer, Answer = " + answer);
throw new Error('DeepWiki return Empty/Error Answer, Answer = ' + answer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议使用模板字符串(template literals)来构建错误信息,以提高代码的可读性。同时,将变量直接拼接到错误信息中可能存在风险,虽然此处 answer 似乎是文档内容,但在其他情况下可能导致敏感信息泄露到日志中。

Suggested change
throw new Error('DeepWiki return Empty/Error Answer, Answer = ' + answer);
throw new Error(`DeepWiki returned an empty or error answer. Answer: ${answer}`);

}

return answer;

} catch (error) {
logger.error("DeepWiki Query Error:", error);
logger.error('DeepWiki Query Error:', error);
// 根据业务需求,这里可以选择抛出错误,或者返回空字符串/错误提示字符串
// 如果是网络断开等错误,resetClient 会在下一次调用时触发重连
if (_client) {
Expand All @@ -159,15 +162,18 @@ export async function queryDeepWiki(_params: {repoName: string, question: string
}
}

export async function adaptedQueryDeepWiki(_params: { repoName: string, question: string }) {
export async function adaptedQueryDeepWiki(_params: {
repoName: string;
question: string;
}) {
try {
const answer = await queryDeepWiki(_params);
return { documentation: answer }
return { documentation: answer };
} catch (error) {
return {
documentation: null,
error: error instanceof Error ? error.message : String(error),
}
};
}
}

Expand All @@ -177,7 +183,7 @@ export async function adaptedQueryDeepWiki(_params: { repoName: string, question
*/
export async function closeDeepWikiConnection() {
if (_transport) {
logger.info("DeepWiki MCP: 关闭连接...");
logger.info('DeepWiki MCP: 关闭连接...');
// SDK 目前可能没有直接暴露 close 方法,通常关闭 transport 即可
// SSEClientTransport 内部并没有显式的 close 方法暴露出来,
// 但我们可以将引用置空,让 GC 回收,或者依赖进程退出
Expand Down