zhitalk 0.0.4 → 0.0.6
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 +2 -2
- package/dist/agent/agent.js +57 -63
- package/dist/agent/cli.js +39 -73
- package/dist/agent/colors.js +6 -44
- package/dist/agent/commands.js +22 -28
- package/dist/agent/config.js +20 -62
- package/dist/agent/context.js +8 -12
- package/dist/agent/db.js +17 -27
- package/dist/agent/hooks.d.ts +1 -1
- package/dist/agent/hooks.js +13 -22
- package/dist/agent/mcp/client.js +25 -29
- package/dist/agent/mcp/index.d.ts +1 -1
- package/dist/agent/mcp/index.js +8 -13
- package/dist/agent/mcp/wrapper.d.ts +2 -2
- package/dist/agent/mcp/wrapper.js +5 -8
- package/dist/agent/model.js +6 -10
- package/dist/agent/permission/exec.js +6 -9
- package/dist/agent/permission/is-dangerous-path.js +13 -19
- package/dist/agent/permission/is-safe-domains.js +1 -4
- package/dist/agent/permission/network.js +3 -6
- package/dist/agent/permission/read.js +3 -6
- package/dist/agent/permission/util.js +16 -26
- package/dist/agent/permission/write.js +5 -8
- package/dist/agent/prompt.js +9 -15
- package/dist/agent/skills.js +19 -24
- package/dist/agent/tools/agent_tool.js +2 -38
- package/dist/agent/tools/exec_tool.js +4 -7
- package/dist/agent/tools/load_skill_tool.js +3 -6
- package/dist/agent/tools/memory_create_tool.js +4 -10
- package/dist/agent/tools/memory_delete_tool.js +4 -10
- package/dist/agent/tools/memory_retrieve_tool.js +3 -6
- package/dist/agent/tools/profile_update_tool.js +11 -14
- package/dist/agent/tools/read_file_tool.js +3 -6
- package/dist/agent/tools/run_js_tool.js +3 -6
- package/dist/agent/tools/run_py_tool.js +4 -7
- package/dist/agent/tools/search.js +1 -4
- package/dist/agent/tools/web_fetch_tool.js +1 -4
- package/dist/agent/tools/web_search_tool.js +5 -8
- package/dist/agent/tools/write_file_tool.js +5 -8
- package/dist/agent/tools.js +76 -81
- package/dist/agent/utils.js +2 -6
- package/dist/index.js +29 -56
- package/dist/install.js +14 -50
- package/jest.config.js +11 -0
- package/package.json +7 -6
- package/pnpm-workspace.yaml +4 -0
package/dist/agent/commands.js
CHANGED
|
@@ -1,51 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const colors_1 = require("./colors");
|
|
10
|
-
const db_1 = require("./db");
|
|
11
|
-
const agent_1 = require("./agent");
|
|
12
|
-
exports.threadId = (0, crypto_1.randomUUID)();
|
|
13
|
-
exports.commands = new Map();
|
|
14
|
-
exports.commands.set('new', {
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { color } from './colors.js';
|
|
4
|
+
import { listRecentSessions, threadIdExists } from './db.js';
|
|
5
|
+
import { compressContext } from './agent.js';
|
|
6
|
+
export let threadId = randomUUID();
|
|
7
|
+
export const commands = new Map();
|
|
8
|
+
commands.set('new', {
|
|
15
9
|
name: 'new',
|
|
16
10
|
description: 'Start a new chat session',
|
|
17
11
|
execute() {
|
|
18
|
-
|
|
19
|
-
console.log(
|
|
12
|
+
threadId = randomUUID();
|
|
13
|
+
console.log(color.goodbye(`\nNew session started ${threadId}.\n`));
|
|
20
14
|
},
|
|
21
15
|
});
|
|
22
|
-
|
|
16
|
+
commands.set('rewind', {
|
|
23
17
|
name: 'rewind',
|
|
24
18
|
description: 'Restore a chat session by thread_id',
|
|
25
19
|
execute(args) {
|
|
26
20
|
const id = args[0];
|
|
27
21
|
if (!id) {
|
|
28
|
-
console.log(
|
|
22
|
+
console.log(color.error('\nError: please provide a thread_id. Usage: /rewind <thread_id>\n'));
|
|
29
23
|
return;
|
|
30
24
|
}
|
|
31
|
-
if (!
|
|
32
|
-
console.log(
|
|
25
|
+
if (!threadIdExists(id)) {
|
|
26
|
+
console.log(color.error(`\nError: thread_id "${id}" not found.\n`));
|
|
33
27
|
return;
|
|
34
28
|
}
|
|
35
|
-
|
|
36
|
-
console.log(
|
|
29
|
+
threadId = id;
|
|
30
|
+
console.log(color.goodbye(`\nRestored session ${threadId}.\n`));
|
|
37
31
|
},
|
|
38
32
|
});
|
|
39
|
-
|
|
33
|
+
commands.set('sessions', {
|
|
40
34
|
name: 'sessions',
|
|
41
35
|
description: 'List recent chat sessions',
|
|
42
36
|
execute() {
|
|
43
|
-
const sessions =
|
|
37
|
+
const sessions = listRecentSessions();
|
|
44
38
|
if (sessions.length === 0) {
|
|
45
39
|
console.log('\n暂无聊天记录\n');
|
|
46
40
|
return;
|
|
47
41
|
}
|
|
48
|
-
const table = new
|
|
42
|
+
const table = new Table({
|
|
49
43
|
head: ['thread_id', '最后用户输入的问题', '时间'],
|
|
50
44
|
});
|
|
51
45
|
for (const s of sessions) {
|
|
@@ -54,13 +48,13 @@ exports.commands.set('sessions', {
|
|
|
54
48
|
console.log('\n' + table.toString() + '\n');
|
|
55
49
|
},
|
|
56
50
|
});
|
|
57
|
-
|
|
51
|
+
commands.set('compact', {
|
|
58
52
|
name: 'compact',
|
|
59
53
|
description: 'Compress context for the current session',
|
|
60
54
|
async execute() {
|
|
61
|
-
const result = await
|
|
55
|
+
const result = await compressContext(threadId);
|
|
62
56
|
if (result.didCompress) {
|
|
63
|
-
console.log(
|
|
57
|
+
console.log(color.goodbye(`\nContext compressed (count: ${result.count}).\n`));
|
|
64
58
|
}
|
|
65
59
|
else {
|
|
66
60
|
console.log('\nNo context to compress.\n');
|
package/dist/agent/config.js
CHANGED
|
@@ -1,90 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.CONFIG_PATH = exports.WORKSPACE_DIR = void 0;
|
|
37
|
-
exports.loadConfig = loadConfig;
|
|
38
|
-
exports.getModelConfig = getModelConfig;
|
|
39
|
-
exports.getEnv = getEnv;
|
|
40
|
-
exports.getHooksConfig = getHooksConfig;
|
|
41
|
-
exports.getMCPServerConfig = getMCPServerConfig;
|
|
42
|
-
exports.clearConfigCache = clearConfigCache;
|
|
43
|
-
const fs = __importStar(require("fs"));
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
const os = __importStar(require("os"));
|
|
46
|
-
exports.WORKSPACE_DIR = path.join(os.homedir(), '.zhitalk');
|
|
47
|
-
exports.CONFIG_PATH = path.join(exports.WORKSPACE_DIR, 'zhitalk.json');
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
export const WORKSPACE_DIR = path.join(os.homedir(), '.zhitalk');
|
|
5
|
+
export const CONFIG_PATH = path.join(WORKSPACE_DIR, 'zhitalk.json');
|
|
48
6
|
let cachedConfig = null;
|
|
49
|
-
function loadConfig() {
|
|
7
|
+
export function loadConfig() {
|
|
50
8
|
if (cachedConfig) {
|
|
51
9
|
return cachedConfig;
|
|
52
10
|
}
|
|
53
|
-
if (!fs.existsSync(
|
|
54
|
-
throw new Error(`找不到配置文件 ${
|
|
11
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
12
|
+
throw new Error(`找不到配置文件 ${CONFIG_PATH}\n\n请创建该文件并配置相关信息,例如:\n{\n "model": {\n "model": "kimi-k2.6",\n "apiKey": "your-api-key",\n "baseURL": "https://api.moonshot.cn/v1"\n },\n "env": {\n "TAVILY_API_KEY": "your-tavily-api-key"\n }\n}`);
|
|
55
13
|
}
|
|
56
14
|
let config;
|
|
57
15
|
try {
|
|
58
|
-
const content = fs.readFileSync(
|
|
16
|
+
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
59
17
|
config = JSON.parse(content);
|
|
60
18
|
}
|
|
61
19
|
catch (e) {
|
|
62
|
-
throw new Error(`配置文件 ${
|
|
20
|
+
throw new Error(`配置文件 ${CONFIG_PATH} 解析失败:${e instanceof Error ? e.message : String(e)}`);
|
|
63
21
|
}
|
|
64
22
|
cachedConfig = config;
|
|
65
23
|
return config;
|
|
66
24
|
}
|
|
67
|
-
function getModelConfig() {
|
|
25
|
+
export function getModelConfig() {
|
|
68
26
|
const config = loadConfig();
|
|
69
27
|
if (!config.model || typeof config.model !== 'object') {
|
|
70
|
-
throw new Error(`配置文件 ${
|
|
28
|
+
throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model 对象,请参考 https://zhitalk.chat/#config 修改配置`);
|
|
71
29
|
}
|
|
72
30
|
if (!config.model.model) {
|
|
73
|
-
throw new Error(`配置文件 ${
|
|
31
|
+
throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model.model 字段,请参考 https://zhitalk.chat/#config 修改配置`);
|
|
74
32
|
}
|
|
75
33
|
if (!config.model.apiKey) {
|
|
76
|
-
throw new Error(`配置文件 ${
|
|
34
|
+
throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model.apiKey 字段,请参考 https://zhitalk.chat/#config 修改配置`);
|
|
77
35
|
}
|
|
78
36
|
if (!config.model.baseURL) {
|
|
79
|
-
throw new Error(`配置文件 ${
|
|
37
|
+
throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model.baseURL 字段,请参考 https://zhitalk.chat/#config 修改配置`);
|
|
80
38
|
}
|
|
81
39
|
return config.model;
|
|
82
40
|
}
|
|
83
|
-
function getEnv(key) {
|
|
41
|
+
export function getEnv(key) {
|
|
84
42
|
const config = loadConfig();
|
|
85
43
|
return config.env?.[key];
|
|
86
44
|
}
|
|
87
|
-
function getHooksConfig() {
|
|
45
|
+
export function getHooksConfig() {
|
|
88
46
|
try {
|
|
89
47
|
const config = loadConfig();
|
|
90
48
|
return { hooks: config.hooks || {} };
|
|
@@ -93,9 +51,9 @@ function getHooksConfig() {
|
|
|
93
51
|
return { hooks: {} };
|
|
94
52
|
}
|
|
95
53
|
}
|
|
96
|
-
function getMCPServerConfig() {
|
|
54
|
+
export function getMCPServerConfig() {
|
|
97
55
|
try {
|
|
98
|
-
const content = fs.readFileSync(
|
|
56
|
+
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
99
57
|
const parsed = JSON.parse(content);
|
|
100
58
|
if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') {
|
|
101
59
|
// console.warn('[MCP] zhitalk.json missing "mcpServers" field, skipping')
|
|
@@ -111,6 +69,6 @@ function getMCPServerConfig() {
|
|
|
111
69
|
return {};
|
|
112
70
|
}
|
|
113
71
|
}
|
|
114
|
-
function clearConfigCache() {
|
|
72
|
+
export function clearConfigCache() {
|
|
115
73
|
cachedConfig = null;
|
|
116
74
|
}
|
package/dist/agent/context.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.compressMessages = compressMessages;
|
|
5
|
-
const messages_1 = require("@langchain/core/messages");
|
|
6
|
-
const model_1 = require("./model");
|
|
7
|
-
const config_1 = require("./config");
|
|
1
|
+
import { HumanMessage, } from '@langchain/core/messages';
|
|
2
|
+
import { createModel } from './model.js';
|
|
3
|
+
import { getModelConfig } from './config.js';
|
|
8
4
|
const MODEL_CONTEXT_LIMITS = {
|
|
9
5
|
// Moonshot / Kimi
|
|
10
6
|
'moonshot-v1-8k': 8192,
|
|
@@ -38,8 +34,8 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
38
34
|
'mimo-7b': 32768,
|
|
39
35
|
'mimo-v2.5': 1048576,
|
|
40
36
|
};
|
|
41
|
-
function getModelContextLimit() {
|
|
42
|
-
const modelName =
|
|
37
|
+
export function getModelContextLimit() {
|
|
38
|
+
const modelName = getModelConfig().model || '';
|
|
43
39
|
return MODEL_CONTEXT_LIMITS[modelName.toLowerCase()] || 128000;
|
|
44
40
|
}
|
|
45
41
|
function formatMessagesForCompression(messages) {
|
|
@@ -56,7 +52,7 @@ function formatMessagesForCompression(messages) {
|
|
|
56
52
|
})
|
|
57
53
|
.join('\n\n');
|
|
58
54
|
}
|
|
59
|
-
async function compressMessages(messages, existingSummary) {
|
|
55
|
+
export async function compressMessages(messages, existingSummary) {
|
|
60
56
|
if (messages.length === 0) {
|
|
61
57
|
return existingSummary || '';
|
|
62
58
|
}
|
|
@@ -95,8 +91,8 @@ async function compressMessages(messages, existingSummary) {
|
|
|
95
91
|
对话内容如下:
|
|
96
92
|
|
|
97
93
|
${text}`;
|
|
98
|
-
const model =
|
|
99
|
-
const response = await model.invoke([new
|
|
94
|
+
const model = createModel({ streaming: false });
|
|
95
|
+
const response = await model.invoke([new HumanMessage(prompt)]);
|
|
100
96
|
const newSummary = typeof response.content === 'string'
|
|
101
97
|
? response.content
|
|
102
98
|
: JSON.stringify(response.content);
|
package/dist/agent/db.js
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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) {
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { WORKSPACE_DIR } from './config.js';
|
|
5
|
+
import { formatRelativeTime, truncate } from './utils.js';
|
|
6
|
+
export const DB_PATH = join(WORKSPACE_DIR, '.data', 'checkpointer.db');
|
|
7
|
+
mkdirSync(dirname(DB_PATH), { recursive: true });
|
|
8
|
+
export function searchMemories(query, limit = 10) {
|
|
19
9
|
const trimmedQueries = query.map((q) => q.trim()).filter((q) => q.length > 0);
|
|
20
10
|
if (trimmedQueries.length === 0) {
|
|
21
11
|
return [];
|
|
22
12
|
}
|
|
23
13
|
const queryStr = trimmedQueries.join(' OR ').replace(/-/g, ' ');
|
|
24
|
-
const db = new
|
|
14
|
+
const db = new Database(DB_PATH);
|
|
25
15
|
try {
|
|
26
16
|
const rows = db.prepare(`
|
|
27
17
|
WITH ranked AS (
|
|
@@ -65,8 +55,8 @@ function searchMemories(query, limit = 10) {
|
|
|
65
55
|
db.close();
|
|
66
56
|
}
|
|
67
57
|
}
|
|
68
|
-
function threadIdExists(threadId) {
|
|
69
|
-
const db = new
|
|
58
|
+
export function threadIdExists(threadId) {
|
|
59
|
+
const db = new Database(DB_PATH);
|
|
70
60
|
try {
|
|
71
61
|
const row = db.prepare(`
|
|
72
62
|
SELECT 1 FROM checkpoints WHERE thread_id = ? LIMIT 1
|
|
@@ -77,8 +67,8 @@ function threadIdExists(threadId) {
|
|
|
77
67
|
db.close();
|
|
78
68
|
}
|
|
79
69
|
}
|
|
80
|
-
function initDb() {
|
|
81
|
-
const db = new
|
|
70
|
+
export function initDb() {
|
|
71
|
+
const db = new Database(DB_PATH);
|
|
82
72
|
try {
|
|
83
73
|
db.exec(`
|
|
84
74
|
CREATE TABLE IF NOT EXISTS memory (
|
|
@@ -122,8 +112,8 @@ function initDb() {
|
|
|
122
112
|
db.close();
|
|
123
113
|
}
|
|
124
114
|
}
|
|
125
|
-
function listRecentSessions() {
|
|
126
|
-
const db = new
|
|
115
|
+
export function listRecentSessions() {
|
|
116
|
+
const db = new Database(DB_PATH);
|
|
127
117
|
try {
|
|
128
118
|
const rows = db.prepare(`
|
|
129
119
|
WITH thread_last_ts AS (
|
|
@@ -149,8 +139,8 @@ function listRecentSessions() {
|
|
|
149
139
|
`).all();
|
|
150
140
|
return rows.map((r) => ({
|
|
151
141
|
thread_id: r.thread_id,
|
|
152
|
-
last_question:
|
|
153
|
-
last_ts:
|
|
142
|
+
last_question: truncate(r.last_question, 50),
|
|
143
|
+
last_ts: formatRelativeTime(r.last_ts),
|
|
154
144
|
}));
|
|
155
145
|
}
|
|
156
146
|
finally {
|
package/dist/agent/hooks.d.ts
CHANGED
package/dist/agent/hooks.js
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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)();
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { getHooksConfig, clearConfigCache, } from './config.js';
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
export function loadHooksConfig() {
|
|
6
|
+
return getHooksConfig();
|
|
16
7
|
}
|
|
17
|
-
function clearHooksCache() {
|
|
18
|
-
|
|
8
|
+
export function clearHooksCache() {
|
|
9
|
+
clearConfigCache();
|
|
19
10
|
}
|
|
20
|
-
function matchHooks(hooks, toolName) {
|
|
11
|
+
export function matchHooks(hooks, toolName) {
|
|
21
12
|
if (!hooks)
|
|
22
13
|
return [];
|
|
23
14
|
return hooks.filter((h) => h.matcher === '*' || toolName.includes(h.matcher));
|
|
24
15
|
}
|
|
25
|
-
async function runHook(hook, env, timeout = 30000) {
|
|
16
|
+
export async function runHook(hook, env, timeout = 30000) {
|
|
26
17
|
const hookType = env.ZHITALK_HOOK_TYPE || 'hook';
|
|
27
18
|
console.log(`[Hook ${hookType}] ${hook.command}`);
|
|
28
19
|
try {
|
|
@@ -48,7 +39,7 @@ async function runHook(hook, env, timeout = 30000) {
|
|
|
48
39
|
};
|
|
49
40
|
}
|
|
50
41
|
}
|
|
51
|
-
async function runPreToolUseHooks(context) {
|
|
42
|
+
export async function runPreToolUseHooks(context) {
|
|
52
43
|
const config = loadHooksConfig();
|
|
53
44
|
const hooks = matchHooks(config.hooks.PreToolUse, context.toolName);
|
|
54
45
|
const injectMessages = [];
|
|
@@ -72,7 +63,7 @@ async function runPreToolUseHooks(context) {
|
|
|
72
63
|
}
|
|
73
64
|
return { action: 'continue' };
|
|
74
65
|
}
|
|
75
|
-
async function runPostToolUseHooks(context) {
|
|
66
|
+
export async function runPostToolUseHooks(context) {
|
|
76
67
|
const config = loadHooksConfig();
|
|
77
68
|
const hooks = matchHooks(config.hooks.PostToolUse, context.toolName);
|
|
78
69
|
const injectMessages = [];
|
|
@@ -97,7 +88,7 @@ async function runPostToolUseHooks(context) {
|
|
|
97
88
|
}
|
|
98
89
|
return { action: 'continue' };
|
|
99
90
|
}
|
|
100
|
-
async function runSessionStartHooks(threadId) {
|
|
91
|
+
export async function runSessionStartHooks(threadId) {
|
|
101
92
|
const config = loadHooksConfig();
|
|
102
93
|
const hooks = config.hooks.SessionStart || [];
|
|
103
94
|
for (const hook of hooks) {
|
package/dist/agent/mcp/client.js
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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");
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { color } from '../colors.js';
|
|
5
|
+
import { getMCPServerConfig } from '../config.js';
|
|
13
6
|
const CONNECT_TIMEOUT_MS = 60000;
|
|
14
|
-
function loadMcpConfig() {
|
|
15
|
-
return
|
|
7
|
+
export function loadMcpConfig() {
|
|
8
|
+
return getMCPServerConfig();
|
|
16
9
|
}
|
|
17
10
|
async function withTimeout(promise, ms, label) {
|
|
18
|
-
return Promise
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
13
|
+
promise
|
|
14
|
+
.then(resolve)
|
|
15
|
+
.catch(reject)
|
|
16
|
+
.finally(() => clearTimeout(timer));
|
|
17
|
+
});
|
|
22
18
|
}
|
|
23
19
|
function createMcpTransport(config) {
|
|
24
20
|
if (config.url) {
|
|
@@ -26,10 +22,10 @@ function createMcpTransport(config) {
|
|
|
26
22
|
const requestInit = config.headers
|
|
27
23
|
? { headers: config.headers }
|
|
28
24
|
: undefined;
|
|
29
|
-
return new
|
|
25
|
+
return new StreamableHTTPClientTransport(url, { requestInit });
|
|
30
26
|
}
|
|
31
27
|
if (config.command) {
|
|
32
|
-
return new
|
|
28
|
+
return new StdioClientTransport({
|
|
33
29
|
command: config.command,
|
|
34
30
|
args: config.args,
|
|
35
31
|
env: config.env,
|
|
@@ -37,9 +33,9 @@ function createMcpTransport(config) {
|
|
|
37
33
|
}
|
|
38
34
|
throw new Error('Invalid MCP server config: must have either "command" or "url"');
|
|
39
35
|
}
|
|
40
|
-
async function connectMcpServer(name, config) {
|
|
36
|
+
export async function connectMcpServer(name, config) {
|
|
41
37
|
const transport = createMcpTransport(config);
|
|
42
|
-
const client = new
|
|
38
|
+
const client = new Client({ name: 'zhitalk', version: '0.0.1' }, { capabilities: {} });
|
|
43
39
|
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `MCP server "${name}" connect`);
|
|
44
40
|
const toolsResult = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `MCP server "${name}" listTools`);
|
|
45
41
|
const tools = (toolsResult.tools || []).map((t) => ({
|
|
@@ -49,19 +45,19 @@ async function connectMcpServer(name, config) {
|
|
|
49
45
|
}));
|
|
50
46
|
return { name, client, transport, tools };
|
|
51
47
|
}
|
|
52
|
-
async function initializeMcpClients() {
|
|
48
|
+
export async function initializeMcpClients() {
|
|
53
49
|
const config = loadMcpConfig();
|
|
54
50
|
const entries = Object.entries(config);
|
|
55
51
|
if (entries.length === 0) {
|
|
56
52
|
return [];
|
|
57
53
|
}
|
|
58
|
-
console.log(
|
|
54
|
+
console.log(color.info(`[MCP] Connecting to ${entries.length} server(s)...`));
|
|
59
55
|
const results = await Promise.allSettled(entries.map(([name, cfg]) => connectMcpServer(name, cfg).catch((err) => {
|
|
60
56
|
const msg = err.message || '';
|
|
61
57
|
const hint = msg.includes('timed out')
|
|
62
58
|
? ' (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
59
|
: '';
|
|
64
|
-
console.error(
|
|
60
|
+
console.error(color.error(`[MCP] Failed to connect "${name}": ${msg}${hint}`));
|
|
65
61
|
throw err;
|
|
66
62
|
})));
|
|
67
63
|
const connections = [];
|
|
@@ -69,16 +65,16 @@ async function initializeMcpClients() {
|
|
|
69
65
|
const result = results[i];
|
|
70
66
|
if (result.status === 'fulfilled') {
|
|
71
67
|
const conn = result.value;
|
|
72
|
-
console.log(
|
|
68
|
+
console.log(color.good(`[MCP] Connected "${conn.name}" with ${conn.tools.length} tool(s)`));
|
|
73
69
|
connections.push(conn);
|
|
74
70
|
}
|
|
75
71
|
else {
|
|
76
|
-
console.error(
|
|
72
|
+
console.error(color.error(`[MCP] Skipped "${entries[i][0]}" due to connection error`));
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
return connections;
|
|
80
76
|
}
|
|
81
|
-
async function callMcpTool(client, name, args) {
|
|
77
|
+
export async function callMcpTool(client, name, args) {
|
|
82
78
|
const result = await client.callTool({ name, arguments: args });
|
|
83
79
|
if (result.isError) {
|
|
84
80
|
const text = extractTextFromResult(result);
|
|
@@ -101,7 +97,7 @@ function extractTextFromResult(result) {
|
|
|
101
97
|
}
|
|
102
98
|
return texts.join('\n');
|
|
103
99
|
}
|
|
104
|
-
async function disconnectAllMcpClients(connections) {
|
|
100
|
+
export async function disconnectAllMcpClients(connections) {
|
|
105
101
|
await Promise.allSettled(connections.map(async (conn) => {
|
|
106
102
|
try {
|
|
107
103
|
await conn.transport.close();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ZhitalkTool } from '../tools';
|
|
1
|
+
import type { ZhitalkTool } from '../tools.js';
|
|
2
2
|
export declare function initMcpTools(): Promise<ZhitalkTool[]>;
|
|
3
3
|
export declare function getMcpTools(): ZhitalkTool[];
|
|
4
4
|
export declare function shutdownMcp(): Promise<void>;
|
package/dist/agent/mcp/index.js
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.initMcpTools = initMcpTools;
|
|
4
|
-
exports.getMcpTools = getMcpTools;
|
|
5
|
-
exports.shutdownMcp = shutdownMcp;
|
|
6
|
-
const client_1 = require("./client");
|
|
7
|
-
const wrapper_1 = require("./wrapper");
|
|
1
|
+
import { initializeMcpClients, disconnectAllMcpClients } from './client.js';
|
|
2
|
+
import { wrapMcpTool } from './wrapper.js';
|
|
8
3
|
let mcpConnections = [];
|
|
9
4
|
let mcpTools = [];
|
|
10
|
-
async function initMcpTools() {
|
|
11
|
-
mcpConnections = await
|
|
5
|
+
export async function initMcpTools() {
|
|
6
|
+
mcpConnections = await initializeMcpClients();
|
|
12
7
|
mcpTools = [];
|
|
13
8
|
for (const conn of mcpConnections) {
|
|
14
9
|
for (const mcpTool of conn.tools) {
|
|
15
|
-
mcpTools.push(
|
|
10
|
+
mcpTools.push(wrapMcpTool(conn, mcpTool));
|
|
16
11
|
}
|
|
17
12
|
}
|
|
18
13
|
return mcpTools;
|
|
19
14
|
}
|
|
20
|
-
function getMcpTools() {
|
|
15
|
+
export function getMcpTools() {
|
|
21
16
|
return mcpTools;
|
|
22
17
|
}
|
|
23
|
-
async function shutdownMcp() {
|
|
24
|
-
await
|
|
18
|
+
export async function shutdownMcp() {
|
|
19
|
+
await disconnectAllMcpClients(mcpConnections);
|
|
25
20
|
mcpConnections = [];
|
|
26
21
|
mcpTools = [];
|
|
27
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type McpServerConnection } from './client';
|
|
2
|
-
import type { ZhitalkTool } from '../tools';
|
|
1
|
+
import { type McpServerConnection } from './client.js';
|
|
2
|
+
import type { ZhitalkTool } from '../tools.js';
|
|
3
3
|
export declare function wrapMcpTool(connection: McpServerConnection, mcpTool: {
|
|
4
4
|
name: string;
|
|
5
5
|
description?: string;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.wrapMcpTool = wrapMcpTool;
|
|
4
|
-
const tools_1 = require("@langchain/core/tools");
|
|
5
|
-
const client_1 = require("./client");
|
|
1
|
+
import { tool } from '@langchain/core/tools';
|
|
2
|
+
import { callMcpTool } from './client.js';
|
|
6
3
|
const MCP_TOOL_PERMISSION_LEVEL = 'mcp';
|
|
7
|
-
function wrapMcpTool(connection, mcpTool) {
|
|
4
|
+
export function wrapMcpTool(connection, mcpTool) {
|
|
8
5
|
const prefixedName = `${connection.name}_${mcpTool.name}`;
|
|
9
6
|
const schemaJson = JSON.stringify(mcpTool.inputSchema, null, 2);
|
|
10
7
|
const description = `[MCP:${connection.name}] ${mcpTool.description || 'No description'}\n\nParameters schema:\n${schemaJson}`;
|
|
@@ -12,10 +9,10 @@ function wrapMcpTool(connection, mcpTool) {
|
|
|
12
9
|
// sees the real parameter requirements instead of a shapeless passthrough.
|
|
13
10
|
const schema = mcpTool.inputSchema;
|
|
14
11
|
const impl = async (args) => {
|
|
15
|
-
const result = await
|
|
12
|
+
const result = await callMcpTool(connection.client, mcpTool.name, args);
|
|
16
13
|
return result;
|
|
17
14
|
};
|
|
18
|
-
const t =
|
|
15
|
+
const t = tool(impl, {
|
|
19
16
|
name: prefixedName,
|
|
20
17
|
description,
|
|
21
18
|
schema,
|
package/dist/agent/model.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const config_1 = require("./config");
|
|
7
|
-
const modelConfig = (0, config_1.getModelConfig)();
|
|
8
|
-
function createModel(options) {
|
|
9
|
-
return new openai_1.ChatOpenAI({
|
|
1
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
2
|
+
import { getModelConfig } from './config.js';
|
|
3
|
+
const modelConfig = getModelConfig();
|
|
4
|
+
export function createModel(options) {
|
|
5
|
+
return new ChatOpenAI({
|
|
10
6
|
model: modelConfig.model,
|
|
11
7
|
apiKey: modelConfig.apiKey,
|
|
12
8
|
configuration: {
|
|
@@ -18,7 +14,7 @@ function createModel(options) {
|
|
|
18
14
|
},
|
|
19
15
|
});
|
|
20
16
|
}
|
|
21
|
-
async function checkModel() {
|
|
17
|
+
export async function checkModel() {
|
|
22
18
|
if (modelConfig.apiKey.length < 20) {
|
|
23
19
|
try {
|
|
24
20
|
const model = createModel({ streaming: false });
|