zhitalk 0.0.0 → 0.0.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/CLAUDE.md +98 -0
- package/README.md +36 -1
- package/dist/agent/agent.d.ts +24 -0
- package/dist/agent/agent.js +345 -0
- package/dist/agent/cli.d.ts +1 -0
- package/dist/agent/cli.js +185 -0
- package/dist/agent/colors.d.ts +19 -0
- package/dist/agent/colors.js +81 -0
- package/dist/agent/commands.d.ts +7 -0
- package/dist/agent/commands.js +69 -0
- package/dist/agent/config.d.ts +38 -0
- package/dist/agent/config.js +116 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +107 -0
- package/dist/agent/db.d.ts +21 -0
- package/dist/agent/db.js +159 -0
- package/dist/agent/hooks.d.ts +27 -0
- package/dist/agent/hooks.js +115 -0
- package/dist/agent/mcp/client.d.ts +25 -0
- package/dist/agent/mcp/client.js +113 -0
- package/dist/agent/mcp/index.d.ts +4 -0
- package/dist/agent/mcp/index.js +27 -0
- package/dist/agent/mcp/wrapper.d.ts +7 -0
- package/dist/agent/mcp/wrapper.js +25 -0
- package/dist/agent/model.d.ts +5 -0
- package/dist/agent/model.js +31 -0
- package/dist/agent/permission/dangerous-path.json +115 -0
- package/dist/agent/permission/exec.d.ts +11 -0
- package/dist/agent/permission/exec.js +34 -0
- package/dist/agent/permission/is-dangerous-path.d.ts +1 -0
- package/dist/agent/permission/is-dangerous-path.js +53 -0
- package/dist/agent/permission/is-safe-domains.d.ts +1 -0
- package/dist/agent/permission/is-safe-domains.js +142 -0
- package/dist/agent/permission/network.d.ts +8 -0
- package/dist/agent/permission/network.js +14 -0
- package/dist/agent/permission/read.d.ts +9 -0
- package/dist/agent/permission/read.js +17 -0
- package/dist/agent/permission/util.d.ts +11 -0
- package/dist/agent/permission/util.js +160 -0
- package/dist/agent/permission/write.d.ts +11 -0
- package/dist/agent/permission/write.js +21 -0
- package/dist/agent/prompt.d.ts +1 -0
- package/dist/agent/prompt.js +50 -0
- package/dist/agent/skills.d.ts +8 -0
- package/dist/agent/skills.js +91 -0
- package/dist/agent/tools/agent_tool.d.ts +3 -0
- package/dist/agent/tools/agent_tool.js +44 -0
- package/dist/agent/tools/exec_tool.d.ts +3 -0
- package/dist/agent/tools/exec_tool.js +28 -0
- package/dist/agent/tools/load_skill_tool.d.ts +3 -0
- package/dist/agent/tools/load_skill_tool.js +15 -0
- package/dist/agent/tools/memory_create_tool.d.ts +6 -0
- package/dist/agent/tools/memory_create_tool.js +37 -0
- package/dist/agent/tools/memory_delete_tool.d.ts +3 -0
- package/dist/agent/tools/memory_delete_tool.js +28 -0
- package/dist/agent/tools/memory_retrieve_tool.d.ts +4 -0
- package/dist/agent/tools/memory_retrieve_tool.js +24 -0
- package/dist/agent/tools/profile_update_tool.d.ts +3 -0
- package/dist/agent/tools/profile_update_tool.js +50 -0
- package/dist/agent/tools/read_file_tool.d.ts +3 -0
- package/dist/agent/tools/read_file_tool.js +16 -0
- package/dist/agent/tools/run_js_tool.d.ts +3 -0
- package/dist/agent/tools/run_js_tool.js +36 -0
- package/dist/agent/tools/run_py_tool.d.ts +3 -0
- package/dist/agent/tools/run_py_tool.js +27 -0
- package/dist/agent/tools/search.d.ts +3 -0
- package/dist/agent/tools/search.js +10 -0
- package/dist/agent/tools/web_fetch_tool.d.ts +3 -0
- package/dist/agent/tools/web_fetch_tool.js +33 -0
- package/dist/agent/tools/web_search_tool.d.ts +3 -0
- package/dist/agent/tools/web_search_tool.js +38 -0
- package/dist/agent/tools/write_file_tool.d.ts +4 -0
- package/dist/agent/tools/write_file_tool.js +15 -0
- package/dist/agent/tools.d.ts +8 -0
- package/dist/agent/tools.js +193 -0
- package/dist/agent/utils.d.ts +2 -0
- package/dist/agent/utils.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +72 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +108 -0
- package/package.json +43 -6
- package/index.js +0 -1
package/dist/agent/db.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DB_PATH = void 0;
|
|
7
|
+
exports.searchMemories = searchMemories;
|
|
8
|
+
exports.threadIdExists = threadIdExists;
|
|
9
|
+
exports.initDb = initDb;
|
|
10
|
+
exports.listRecentSessions = listRecentSessions;
|
|
11
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const config_1 = require("./config");
|
|
15
|
+
const utils_1 = require("./utils");
|
|
16
|
+
exports.DB_PATH = (0, path_1.join)(config_1.WORKSPACE_DIR, '.data', 'checkpointer.db');
|
|
17
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(exports.DB_PATH), { recursive: true });
|
|
18
|
+
function searchMemories(query, limit = 10) {
|
|
19
|
+
const trimmedQueries = query.map((q) => q.trim()).filter((q) => q.length > 0);
|
|
20
|
+
if (trimmedQueries.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const queryStr = trimmedQueries.join(' OR ').replace(/-/g, ' ');
|
|
24
|
+
const db = new better_sqlite3_1.default(exports.DB_PATH);
|
|
25
|
+
try {
|
|
26
|
+
const rows = db.prepare(`
|
|
27
|
+
WITH ranked AS (
|
|
28
|
+
SELECT
|
|
29
|
+
m.*,
|
|
30
|
+
-bm25(memory_fts, 10.0, 5.0) AS relevance_score,
|
|
31
|
+
(m.importance * 0.3) AS importance_score,
|
|
32
|
+
(
|
|
33
|
+
1.0 / (
|
|
34
|
+
1.0 +
|
|
35
|
+
((strftime('%s','now') - strftime('%s', m.updated_at)) / 86400.0)
|
|
36
|
+
)
|
|
37
|
+
) AS time_score
|
|
38
|
+
FROM memory_fts
|
|
39
|
+
JOIN memory m ON m.id = memory_fts.rowid
|
|
40
|
+
WHERE memory_fts MATCH ?
|
|
41
|
+
)
|
|
42
|
+
SELECT *,
|
|
43
|
+
(
|
|
44
|
+
relevance_score * 0.6 +
|
|
45
|
+
importance_score * 0.3 +
|
|
46
|
+
time_score * 0.1
|
|
47
|
+
) AS final_score
|
|
48
|
+
FROM ranked
|
|
49
|
+
ORDER BY final_score DESC
|
|
50
|
+
LIMIT ?
|
|
51
|
+
`).all(queryStr, limit);
|
|
52
|
+
return rows.map((r) => ({
|
|
53
|
+
id: r.id,
|
|
54
|
+
type: r.type,
|
|
55
|
+
content: r.content,
|
|
56
|
+
keywords: r.keywords,
|
|
57
|
+
importance: r.importance,
|
|
58
|
+
session_id: r.session_id,
|
|
59
|
+
created_at: r.created_at,
|
|
60
|
+
updated_at: r.updated_at,
|
|
61
|
+
final_score: r.final_score,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
db.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function threadIdExists(threadId) {
|
|
69
|
+
const db = new better_sqlite3_1.default(exports.DB_PATH);
|
|
70
|
+
try {
|
|
71
|
+
const row = db.prepare(`
|
|
72
|
+
SELECT 1 FROM checkpoints WHERE thread_id = ? LIMIT 1
|
|
73
|
+
`).get(threadId);
|
|
74
|
+
return !!row;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
db.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function initDb() {
|
|
81
|
+
const db = new better_sqlite3_1.default(exports.DB_PATH);
|
|
82
|
+
try {
|
|
83
|
+
db.exec(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS memory (
|
|
85
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
86
|
+
type TEXT NOT NULL,
|
|
87
|
+
content TEXT NOT NULL,
|
|
88
|
+
keywords TEXT,
|
|
89
|
+
importance INTEGER DEFAULT 3,
|
|
90
|
+
session_id TEXT,
|
|
91
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
92
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
93
|
+
)
|
|
94
|
+
`);
|
|
95
|
+
db.exec(`
|
|
96
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
97
|
+
content,
|
|
98
|
+
keywords,
|
|
99
|
+
content='memory', content_rowid='id'
|
|
100
|
+
)
|
|
101
|
+
`);
|
|
102
|
+
db.exec(`
|
|
103
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_insert AFTER INSERT ON memory BEGIN
|
|
104
|
+
INSERT INTO memory_fts(rowid, content, keywords)
|
|
105
|
+
VALUES (new.id, new.content, new.keywords);
|
|
106
|
+
END;
|
|
107
|
+
|
|
108
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_delete AFTER DELETE ON memory BEGIN
|
|
109
|
+
INSERT INTO memory_fts(memory_fts, rowid, content, keywords)
|
|
110
|
+
VALUES ('delete', old.id, old.content, old.keywords);
|
|
111
|
+
END;
|
|
112
|
+
|
|
113
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_update AFTER UPDATE ON memory BEGIN
|
|
114
|
+
INSERT INTO memory_fts(memory_fts, rowid, content, keywords)
|
|
115
|
+
VALUES ('delete', old.id, old.content, old.keywords);
|
|
116
|
+
INSERT INTO memory_fts(rowid, content, keywords)
|
|
117
|
+
VALUES (new.id, new.content, new.keywords);
|
|
118
|
+
END;
|
|
119
|
+
`);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
db.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function listRecentSessions() {
|
|
126
|
+
const db = new better_sqlite3_1.default(exports.DB_PATH);
|
|
127
|
+
try {
|
|
128
|
+
const rows = db.prepare(`
|
|
129
|
+
WITH thread_last_ts AS (
|
|
130
|
+
SELECT
|
|
131
|
+
thread_id,
|
|
132
|
+
MAX(json_extract(CAST(checkpoint AS TEXT), '$.ts')) AS last_ts
|
|
133
|
+
FROM checkpoints
|
|
134
|
+
GROUP BY thread_id
|
|
135
|
+
)
|
|
136
|
+
SELECT
|
|
137
|
+
t.thread_id,
|
|
138
|
+
(SELECT json_extract(CAST(w.value AS TEXT), '$[0].kwargs.content')
|
|
139
|
+
FROM writes w
|
|
140
|
+
WHERE w.thread_id = t.thread_id
|
|
141
|
+
AND w.channel = 'messages'
|
|
142
|
+
AND json_extract(CAST(w.value AS TEXT), '$[0].id[2]') = 'HumanMessage'
|
|
143
|
+
ORDER BY w.checkpoint_id DESC
|
|
144
|
+
LIMIT 1) AS last_question,
|
|
145
|
+
t.last_ts
|
|
146
|
+
FROM thread_last_ts t
|
|
147
|
+
ORDER BY t.last_ts DESC
|
|
148
|
+
LIMIT 20
|
|
149
|
+
`).all();
|
|
150
|
+
return rows.map((r) => ({
|
|
151
|
+
thread_id: r.thread_id,
|
|
152
|
+
last_question: (0, utils_1.truncate)(r.last_question, 50),
|
|
153
|
+
last_ts: (0, utils_1.formatRelativeTime)(r.last_ts),
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
db.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { HookConfig, HooksConfig } from './config';
|
|
2
|
+
export type { HookConfig, HooksConfig };
|
|
3
|
+
export type HookResult = {
|
|
4
|
+
action: 'continue';
|
|
5
|
+
} | {
|
|
6
|
+
action: 'block';
|
|
7
|
+
reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
action: 'inject';
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function loadHooksConfig(): HooksConfig;
|
|
13
|
+
export declare function clearHooksCache(): void;
|
|
14
|
+
export declare function matchHooks(hooks: HookConfig[] | undefined, toolName: string): HookConfig[];
|
|
15
|
+
export declare function runHook(hook: HookConfig, env: Record<string, string>, timeout?: number): Promise<HookResult>;
|
|
16
|
+
export interface ToolHookContext {
|
|
17
|
+
toolName: string;
|
|
18
|
+
toolArgs: any;
|
|
19
|
+
toolCallId: string;
|
|
20
|
+
threadId: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function runPreToolUseHooks(context: ToolHookContext): Promise<HookResult>;
|
|
23
|
+
export interface PostToolHookContext extends ToolHookContext {
|
|
24
|
+
toolOutput: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function runPostToolUseHooks(context: PostToolHookContext): Promise<HookResult>;
|
|
27
|
+
export declare function runSessionStartHooks(threadId: string): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadHooksConfig = loadHooksConfig;
|
|
4
|
+
exports.clearHooksCache = clearHooksCache;
|
|
5
|
+
exports.matchHooks = matchHooks;
|
|
6
|
+
exports.runHook = runHook;
|
|
7
|
+
exports.runPreToolUseHooks = runPreToolUseHooks;
|
|
8
|
+
exports.runPostToolUseHooks = runPostToolUseHooks;
|
|
9
|
+
exports.runSessionStartHooks = runSessionStartHooks;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const util_1 = require("util");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
14
|
+
function loadHooksConfig() {
|
|
15
|
+
return (0, config_1.getHooksConfig)();
|
|
16
|
+
}
|
|
17
|
+
function clearHooksCache() {
|
|
18
|
+
(0, config_1.clearConfigCache)();
|
|
19
|
+
}
|
|
20
|
+
function matchHooks(hooks, toolName) {
|
|
21
|
+
if (!hooks)
|
|
22
|
+
return [];
|
|
23
|
+
return hooks.filter((h) => h.matcher === '*' || toolName.includes(h.matcher));
|
|
24
|
+
}
|
|
25
|
+
async function runHook(hook, env, timeout = 30000) {
|
|
26
|
+
const hookType = env.ZHITALK_HOOK_TYPE || 'hook';
|
|
27
|
+
console.log(`[Hook ${hookType}] ${hook.command}`);
|
|
28
|
+
try {
|
|
29
|
+
await execAsync(hook.command, {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
env: { ...process.env, ...env },
|
|
32
|
+
timeout,
|
|
33
|
+
});
|
|
34
|
+
return { action: 'continue' };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const code = error.code;
|
|
38
|
+
const stderr = error.stderr || '';
|
|
39
|
+
if (code === 1) {
|
|
40
|
+
return { action: 'block', reason: stderr };
|
|
41
|
+
}
|
|
42
|
+
else if (code === 2) {
|
|
43
|
+
return { action: 'inject', message: stderr };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
action: 'block',
|
|
47
|
+
reason: stderr || `Hook exited with code ${code}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function runPreToolUseHooks(context) {
|
|
52
|
+
const config = loadHooksConfig();
|
|
53
|
+
const hooks = matchHooks(config.hooks.PreToolUse, context.toolName);
|
|
54
|
+
const injectMessages = [];
|
|
55
|
+
for (const hook of hooks) {
|
|
56
|
+
const result = await runHook(hook, {
|
|
57
|
+
ZHITALK_HOOK_TYPE: 'PreToolUse',
|
|
58
|
+
ZHITALK_TOOL_NAME: context.toolName,
|
|
59
|
+
ZHITALK_TOOL_ARGS: JSON.stringify(context.toolArgs),
|
|
60
|
+
ZHITALK_TOOL_CALL_ID: context.toolCallId,
|
|
61
|
+
ZHITALK_THREAD_ID: context.threadId,
|
|
62
|
+
});
|
|
63
|
+
if (result.action === 'block') {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
if (result.action === 'inject') {
|
|
67
|
+
injectMessages.push(result.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (injectMessages.length > 0) {
|
|
71
|
+
return { action: 'inject', message: injectMessages.join('\n') };
|
|
72
|
+
}
|
|
73
|
+
return { action: 'continue' };
|
|
74
|
+
}
|
|
75
|
+
async function runPostToolUseHooks(context) {
|
|
76
|
+
const config = loadHooksConfig();
|
|
77
|
+
const hooks = matchHooks(config.hooks.PostToolUse, context.toolName);
|
|
78
|
+
const injectMessages = [];
|
|
79
|
+
for (const hook of hooks) {
|
|
80
|
+
const result = await runHook(hook, {
|
|
81
|
+
ZHITALK_HOOK_TYPE: 'PostToolUse',
|
|
82
|
+
ZHITALK_TOOL_NAME: context.toolName,
|
|
83
|
+
ZHITALK_TOOL_ARGS: JSON.stringify(context.toolArgs),
|
|
84
|
+
ZHITALK_TOOL_OUTPUT: context.toolOutput,
|
|
85
|
+
ZHITALK_TOOL_CALL_ID: context.toolCallId,
|
|
86
|
+
ZHITALK_THREAD_ID: context.threadId,
|
|
87
|
+
});
|
|
88
|
+
if (result.action === 'block') {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
if (result.action === 'inject') {
|
|
92
|
+
injectMessages.push(result.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (injectMessages.length > 0) {
|
|
96
|
+
return { action: 'inject', message: injectMessages.join('\n') };
|
|
97
|
+
}
|
|
98
|
+
return { action: 'continue' };
|
|
99
|
+
}
|
|
100
|
+
async function runSessionStartHooks(threadId) {
|
|
101
|
+
const config = loadHooksConfig();
|
|
102
|
+
const hooks = config.hooks.SessionStart || [];
|
|
103
|
+
for (const hook of hooks) {
|
|
104
|
+
const result = await runHook(hook, {
|
|
105
|
+
ZHITALK_HOOK_TYPE: 'SessionStart',
|
|
106
|
+
ZHITALK_THREAD_ID: threadId,
|
|
107
|
+
});
|
|
108
|
+
if (result.action === 'block') {
|
|
109
|
+
console.error(`[SessionStart hook blocked] ${result.reason}`);
|
|
110
|
+
}
|
|
111
|
+
else if (result.action === 'inject') {
|
|
112
|
+
console.log(result.message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
+
export interface McpServerConfig {
|
|
4
|
+
command?: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
url?: string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface McpToolInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
inputSchema: object;
|
|
14
|
+
}
|
|
15
|
+
export interface McpServerConnection {
|
|
16
|
+
name: string;
|
|
17
|
+
client: Client;
|
|
18
|
+
transport: Transport;
|
|
19
|
+
tools: McpToolInfo[];
|
|
20
|
+
}
|
|
21
|
+
export declare function loadMcpConfig(): Record<string, McpServerConfig>;
|
|
22
|
+
export declare function connectMcpServer(name: string, config: McpServerConfig): Promise<McpServerConnection>;
|
|
23
|
+
export declare function initializeMcpClients(): Promise<McpServerConnection[]>;
|
|
24
|
+
export declare function callMcpTool(client: Client, name: string, args: Record<string, unknown>): Promise<string>;
|
|
25
|
+
export declare function disconnectAllMcpClients(connections: McpServerConnection[]): Promise<void>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadMcpConfig = loadMcpConfig;
|
|
4
|
+
exports.connectMcpServer = connectMcpServer;
|
|
5
|
+
exports.initializeMcpClients = initializeMcpClients;
|
|
6
|
+
exports.callMcpTool = callMcpTool;
|
|
7
|
+
exports.disconnectAllMcpClients = disconnectAllMcpClients;
|
|
8
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
9
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
10
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
11
|
+
const colors_1 = require("../colors");
|
|
12
|
+
const config_1 = require("../config");
|
|
13
|
+
const CONNECT_TIMEOUT_MS = 60000;
|
|
14
|
+
function loadMcpConfig() {
|
|
15
|
+
return (0, config_1.getMCPServerConfig)();
|
|
16
|
+
}
|
|
17
|
+
async function withTimeout(promise, ms, label) {
|
|
18
|
+
return Promise.race([
|
|
19
|
+
promise,
|
|
20
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
function createMcpTransport(config) {
|
|
24
|
+
if (config.url) {
|
|
25
|
+
const url = new URL(config.url);
|
|
26
|
+
const requestInit = config.headers
|
|
27
|
+
? { headers: config.headers }
|
|
28
|
+
: undefined;
|
|
29
|
+
return new streamableHttp_js_1.StreamableHTTPClientTransport(url, { requestInit });
|
|
30
|
+
}
|
|
31
|
+
if (config.command) {
|
|
32
|
+
return new stdio_js_1.StdioClientTransport({
|
|
33
|
+
command: config.command,
|
|
34
|
+
args: config.args,
|
|
35
|
+
env: config.env,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Invalid MCP server config: must have either "command" or "url"');
|
|
39
|
+
}
|
|
40
|
+
async function connectMcpServer(name, config) {
|
|
41
|
+
const transport = createMcpTransport(config);
|
|
42
|
+
const client = new index_js_1.Client({ name: 'zhitalk', version: '0.0.1' }, { capabilities: {} });
|
|
43
|
+
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `MCP server "${name}" connect`);
|
|
44
|
+
const toolsResult = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `MCP server "${name}" listTools`);
|
|
45
|
+
const tools = (toolsResult.tools || []).map((t) => ({
|
|
46
|
+
name: t.name,
|
|
47
|
+
description: t.description,
|
|
48
|
+
inputSchema: t.inputSchema,
|
|
49
|
+
}));
|
|
50
|
+
return { name, client, transport, tools };
|
|
51
|
+
}
|
|
52
|
+
async function initializeMcpClients() {
|
|
53
|
+
const config = loadMcpConfig();
|
|
54
|
+
const entries = Object.entries(config);
|
|
55
|
+
if (entries.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
console.log(colors_1.color.info(`[MCP] Connecting to ${entries.length} server(s)...`));
|
|
59
|
+
const results = await Promise.allSettled(entries.map(([name, cfg]) => connectMcpServer(name, cfg).catch((err) => {
|
|
60
|
+
const msg = err.message || '';
|
|
61
|
+
const hint = msg.includes('timed out')
|
|
62
|
+
? ' (Hint: if using npx, the first run may need to download the package. Consider pre-installing with `npm install -g <package>` or increasing timeout.)'
|
|
63
|
+
: '';
|
|
64
|
+
console.error(colors_1.color.error(`[MCP] Failed to connect "${name}": ${msg}${hint}`));
|
|
65
|
+
throw err;
|
|
66
|
+
})));
|
|
67
|
+
const connections = [];
|
|
68
|
+
for (let i = 0; i < results.length; i++) {
|
|
69
|
+
const result = results[i];
|
|
70
|
+
if (result.status === 'fulfilled') {
|
|
71
|
+
const conn = result.value;
|
|
72
|
+
console.log(colors_1.color.good(`[MCP] Connected "${conn.name}" with ${conn.tools.length} tool(s)`));
|
|
73
|
+
connections.push(conn);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error(colors_1.color.error(`[MCP] Skipped "${entries[i][0]}" due to connection error`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return connections;
|
|
80
|
+
}
|
|
81
|
+
async function callMcpTool(client, name, args) {
|
|
82
|
+
const result = await client.callTool({ name, arguments: args });
|
|
83
|
+
if (result.isError) {
|
|
84
|
+
const text = extractTextFromResult(result);
|
|
85
|
+
throw new Error(text || `MCP tool "${name}" returned an error`);
|
|
86
|
+
}
|
|
87
|
+
return extractTextFromResult(result);
|
|
88
|
+
}
|
|
89
|
+
function extractTextFromResult(result) {
|
|
90
|
+
if (!result.content || !Array.isArray(result.content)) {
|
|
91
|
+
return JSON.stringify(result);
|
|
92
|
+
}
|
|
93
|
+
const texts = [];
|
|
94
|
+
for (const item of result.content) {
|
|
95
|
+
if (item.type === 'text' && typeof item.text === 'string') {
|
|
96
|
+
texts.push(item.text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (texts.length === 0) {
|
|
100
|
+
return JSON.stringify(result);
|
|
101
|
+
}
|
|
102
|
+
return texts.join('\n');
|
|
103
|
+
}
|
|
104
|
+
async function disconnectAllMcpClients(connections) {
|
|
105
|
+
await Promise.allSettled(connections.map(async (conn) => {
|
|
106
|
+
try {
|
|
107
|
+
await conn.transport.close();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// ignore
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initMcpTools = initMcpTools;
|
|
4
|
+
exports.getMcpTools = getMcpTools;
|
|
5
|
+
exports.shutdownMcp = shutdownMcp;
|
|
6
|
+
const client_1 = require("./client");
|
|
7
|
+
const wrapper_1 = require("./wrapper");
|
|
8
|
+
let mcpConnections = [];
|
|
9
|
+
let mcpTools = [];
|
|
10
|
+
async function initMcpTools() {
|
|
11
|
+
mcpConnections = await (0, client_1.initializeMcpClients)();
|
|
12
|
+
mcpTools = [];
|
|
13
|
+
for (const conn of mcpConnections) {
|
|
14
|
+
for (const mcpTool of conn.tools) {
|
|
15
|
+
mcpTools.push((0, wrapper_1.wrapMcpTool)(conn, mcpTool));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return mcpTools;
|
|
19
|
+
}
|
|
20
|
+
function getMcpTools() {
|
|
21
|
+
return mcpTools;
|
|
22
|
+
}
|
|
23
|
+
async function shutdownMcp() {
|
|
24
|
+
await (0, client_1.disconnectAllMcpClients)(mcpConnections);
|
|
25
|
+
mcpConnections = [];
|
|
26
|
+
mcpTools = [];
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapMcpTool = wrapMcpTool;
|
|
4
|
+
const tools_1 = require("@langchain/core/tools");
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
const MCP_TOOL_PERMISSION_LEVEL = 'mcp';
|
|
7
|
+
function wrapMcpTool(connection, mcpTool) {
|
|
8
|
+
const prefixedName = `${connection.name}_${mcpTool.name}`;
|
|
9
|
+
const schemaJson = JSON.stringify(mcpTool.inputSchema, null, 2);
|
|
10
|
+
const description = `[MCP:${connection.name}] ${mcpTool.description || 'No description'}\n\nParameters schema:\n${schemaJson}`;
|
|
11
|
+
// Pass the MCP server's JSON Schema directly to LangChain so the LLM
|
|
12
|
+
// sees the real parameter requirements instead of a shapeless passthrough.
|
|
13
|
+
const schema = mcpTool.inputSchema;
|
|
14
|
+
const impl = async (args) => {
|
|
15
|
+
const result = await (0, client_1.callMcpTool)(connection.client, mcpTool.name, args);
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
const t = (0, tools_1.tool)(impl, {
|
|
19
|
+
name: prefixedName,
|
|
20
|
+
description,
|
|
21
|
+
schema,
|
|
22
|
+
});
|
|
23
|
+
t.permission_level = MCP_TOOL_PERMISSION_LEVEL;
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createModel = createModel;
|
|
4
|
+
exports.checkModel = checkModel;
|
|
5
|
+
const openai_1 = require("@langchain/openai");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const modelConfig = (0, config_1.getModelConfig)();
|
|
8
|
+
function createModel(options) {
|
|
9
|
+
return new openai_1.ChatOpenAI({
|
|
10
|
+
model: modelConfig.model,
|
|
11
|
+
apiKey: modelConfig.apiKey,
|
|
12
|
+
configuration: {
|
|
13
|
+
baseURL: modelConfig.baseURL,
|
|
14
|
+
},
|
|
15
|
+
streaming: options?.streaming ?? true,
|
|
16
|
+
modelKwargs: {
|
|
17
|
+
thinking: { type: 'disabled' },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function checkModel() {
|
|
22
|
+
if (modelConfig.apiKey.length < 20) {
|
|
23
|
+
try {
|
|
24
|
+
const model = createModel({ streaming: false });
|
|
25
|
+
await model.invoke([{ role: 'user', content: 'hi' }]);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`API Key 验证失败,请检查配置是否正确\n\n请参考 https://zhitalk.chat/#config 修改配置`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"macos": [
|
|
3
|
+
"~/.ssh",
|
|
4
|
+
"~/.gnupg",
|
|
5
|
+
"~/.aws",
|
|
6
|
+
"~/.azure",
|
|
7
|
+
"~/.config/gcloud",
|
|
8
|
+
"~/.kube",
|
|
9
|
+
"~/.docker",
|
|
10
|
+
"~/.netrc",
|
|
11
|
+
"~/Library/Keychains",
|
|
12
|
+
"~/Library/Application Support/Google/Chrome",
|
|
13
|
+
"~/Library/Application Support/Chromium",
|
|
14
|
+
"~/Library/Application Support/Firefox",
|
|
15
|
+
"~/Library/Application Support/Slack",
|
|
16
|
+
"~/Library/Application Support/Discord",
|
|
17
|
+
"~/Library/Application Support/Telegram",
|
|
18
|
+
"~/Library/Application Support/Signal",
|
|
19
|
+
"~/.zshrc",
|
|
20
|
+
"~/.bashrc",
|
|
21
|
+
"~/.bash_profile",
|
|
22
|
+
"~/.profile",
|
|
23
|
+
"~/.env",
|
|
24
|
+
"~/.env.*",
|
|
25
|
+
"~/.gitconfig",
|
|
26
|
+
"~/.git-credentials",
|
|
27
|
+
"~/.npmrc",
|
|
28
|
+
"~/.pnpmrc",
|
|
29
|
+
"~/.yarnrc",
|
|
30
|
+
"~/.pypirc",
|
|
31
|
+
"~/.m2",
|
|
32
|
+
"~/.gradle",
|
|
33
|
+
"~/.terraform",
|
|
34
|
+
"~/.config/gh",
|
|
35
|
+
"~/.config/gitlab",
|
|
36
|
+
"~/.circleci",
|
|
37
|
+
"~/.vercel",
|
|
38
|
+
"~/.netlify",
|
|
39
|
+
"~/Library/Containers",
|
|
40
|
+
"/private",
|
|
41
|
+
"/etc",
|
|
42
|
+
"/var"
|
|
43
|
+
],
|
|
44
|
+
"linux": [
|
|
45
|
+
"~/.ssh",
|
|
46
|
+
"~/.gnupg",
|
|
47
|
+
"~/.pki",
|
|
48
|
+
"~/.aws",
|
|
49
|
+
"~/.azure",
|
|
50
|
+
"~/.config/gcloud",
|
|
51
|
+
"~/.kube",
|
|
52
|
+
"~/.docker",
|
|
53
|
+
"~/.netrc",
|
|
54
|
+
"~/.local/share/keyrings",
|
|
55
|
+
"~/.config/google-chrome",
|
|
56
|
+
"~/.config/chromium",
|
|
57
|
+
"~/.mozilla/firefox",
|
|
58
|
+
"~/.config/Slack",
|
|
59
|
+
"~/.config/discord",
|
|
60
|
+
"~/.local/share/TelegramDesktop",
|
|
61
|
+
"~/.zshrc",
|
|
62
|
+
"~/.bashrc",
|
|
63
|
+
"~/.bash_profile",
|
|
64
|
+
"~/.profile",
|
|
65
|
+
"~/.env",
|
|
66
|
+
"~/.env.*",
|
|
67
|
+
"~/.gitconfig",
|
|
68
|
+
"~/.git-credentials",
|
|
69
|
+
"~/.npmrc",
|
|
70
|
+
"~/.pnpmrc",
|
|
71
|
+
"~/.yarnrc",
|
|
72
|
+
"~/.pypirc",
|
|
73
|
+
"~/.m2",
|
|
74
|
+
"~/.gradle",
|
|
75
|
+
"~/.terraform",
|
|
76
|
+
"~/.config/gh",
|
|
77
|
+
"~/.config/gitlab",
|
|
78
|
+
"~/.circleci",
|
|
79
|
+
"~/.vercel",
|
|
80
|
+
"~/.netlify",
|
|
81
|
+
"/etc/shadow",
|
|
82
|
+
"/etc/sudoers",
|
|
83
|
+
"/proc",
|
|
84
|
+
"/sys",
|
|
85
|
+
"/var/lib",
|
|
86
|
+
"/root"
|
|
87
|
+
],
|
|
88
|
+
"windows": [
|
|
89
|
+
"%USERPROFILE%\\.ssh",
|
|
90
|
+
"%USERPROFILE%\\.gnupg",
|
|
91
|
+
"%USERPROFILE%\\.aws",
|
|
92
|
+
"%USERPROFILE%\\.azure",
|
|
93
|
+
"%USERPROFILE%\\.kube",
|
|
94
|
+
"%USERPROFILE%\\.docker",
|
|
95
|
+
"%LOCALAPPDATA%\\Google\\Chrome\\User Data",
|
|
96
|
+
"%LOCALAPPDATA%\\Chromium\\User Data",
|
|
97
|
+
"%APPDATA%\\Mozilla\\Firefox",
|
|
98
|
+
"%APPDATA%\\Slack",
|
|
99
|
+
"%APPDATA%\\Discord",
|
|
100
|
+
"%APPDATA%\\Telegram Desktop",
|
|
101
|
+
"%USERPROFILE%\\.gitconfig",
|
|
102
|
+
"%USERPROFILE%\\.git-credentials",
|
|
103
|
+
"%USERPROFILE%\\.npmrc",
|
|
104
|
+
"%USERPROFILE%\\.pnpmrc",
|
|
105
|
+
"%USERPROFILE%\\.yarnrc",
|
|
106
|
+
"%USERPROFILE%\\.m2",
|
|
107
|
+
"%USERPROFILE%\\.gradle",
|
|
108
|
+
"%APPDATA%",
|
|
109
|
+
"%LOCALAPPDATA%",
|
|
110
|
+
"C:\\Windows\\System32",
|
|
111
|
+
"C:\\Windows\\System32\\config\\SAM",
|
|
112
|
+
"C:\\Windows\\System32\\config\\SECURITY",
|
|
113
|
+
"C:\\Windows\\System32\\config\\SYSTEM"
|
|
114
|
+
]
|
|
115
|
+
}
|