@zhin.js/agent 0.0.16 → 0.0.17
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/CHANGELOG.md +9 -0
- package/lib/builtin-tools.d.ts +93 -0
- package/lib/builtin-tools.d.ts.map +1 -1
- package/lib/builtin-tools.js +358 -3
- package/lib/builtin-tools.js.map +1 -1
- package/lib/init/register-builtin-tools.d.ts.map +1 -1
- package/lib/init/register-builtin-tools.js +138 -2
- package/lib/init/register-builtin-tools.js.map +1 -1
- package/lib/init/register-db-models.js +3 -3
- package/lib/init/register-db-models.js.map +1 -1
- package/lib/init/register-db-upgrade.js +2 -2
- package/lib/init/register-db-upgrade.js.map +1 -1
- package/lib/init/register-management-tools.js +1 -1
- package/lib/init/register-management-tools.js.map +1 -1
- package/lib/service.d.ts +4 -8
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +23 -112
- package/lib/service.js.map +1 -1
- package/lib/subagent.js +1 -1
- package/lib/subagent.js.map +1 -1
- package/lib/zhin-agent/builtin-tools.d.ts +1 -1
- package/lib/zhin-agent/builtin-tools.d.ts.map +1 -1
- package/lib/zhin-agent/config.d.ts +8 -1
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js +8 -1
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/index.d.ts +3 -3
- package/lib/zhin-agent/index.d.ts.map +1 -1
- package/lib/zhin-agent/index.js +52 -29
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/tool-collector.js +1 -1
- package/package.json +3 -3
- package/src/builtin-tools.ts +443 -3
- package/src/init/register-ai-trigger.ts +1 -1
- package/src/init/register-builtin-tools.ts +135 -2
- package/src/init/register-db-models.ts +3 -3
- package/src/init/register-db-upgrade.ts +2 -2
- package/src/init/register-management-tools.ts +1 -1
- package/src/init/register-message-recorder.ts +1 -1
- package/src/service.ts +28 -132
- package/src/subagent.ts +1 -1
- package/src/zhin-agent/builtin-tools.ts +1 -1
- package/src/zhin-agent/config.ts +10 -2
- package/src/zhin-agent/index.ts +51 -29
- package/src/zhin-agent/tool-collector.ts +1 -1
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as os from 'os';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
-
import { getPlugin, type Tool, type SkillFeature } from '@zhin.js/core';
|
|
8
|
+
import { getPlugin, type Tool, type SkillFeature, type AgentPreset } from '@zhin.js/core';
|
|
9
9
|
import {
|
|
10
10
|
collectPluginSkillSearchRoots,
|
|
11
11
|
createBuiltinTools,
|
|
12
12
|
discoverWorkspaceSkills,
|
|
13
|
+
discoverWorkspaceAgents,
|
|
14
|
+
discoverWorkspaceTools,
|
|
15
|
+
buildToolFromMeta,
|
|
13
16
|
loadAlwaysSkillsContent,
|
|
14
17
|
buildSkillsSummaryXML,
|
|
15
18
|
} from '../builtin-tools.js';
|
|
@@ -33,6 +36,10 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
33
36
|
const builtinTools = createBuiltinTools({
|
|
34
37
|
skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullCfg, modelName),
|
|
35
38
|
pluginSkillRootsResolver: () => collectPluginSkillSearchRoots(root),
|
|
39
|
+
skillFileLookup: (name: string) => {
|
|
40
|
+
const skillFeature = root.inject?.('skill') as SkillFeature | undefined;
|
|
41
|
+
return skillFeature?.get(name)?.filePath;
|
|
42
|
+
},
|
|
36
43
|
});
|
|
37
44
|
const disposers: (() => void)[] = [];
|
|
38
45
|
for (const tool of builtinTools) disposers.push(toolService.addTool(tool, root.name));
|
|
@@ -42,6 +49,7 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
42
49
|
|
|
43
50
|
let skillWatchers: fs.FSWatcher[] = [];
|
|
44
51
|
let skillReloadDebounce: ReturnType<typeof setTimeout> | null = null;
|
|
52
|
+
let toolReloadDebounce: ReturnType<typeof setTimeout> | null = null;
|
|
45
53
|
|
|
46
54
|
async function syncWorkspaceSkills(): Promise<number> {
|
|
47
55
|
const skillFeature = root.inject?.('skill') as SkillFeature | undefined;
|
|
@@ -71,11 +79,89 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
71
79
|
keywords: s.keywords || [],
|
|
72
80
|
tags: s.tags || [],
|
|
73
81
|
pluginName: root.name,
|
|
82
|
+
filePath: s.filePath,
|
|
83
|
+
always: s.always,
|
|
74
84
|
}, root.name);
|
|
75
85
|
}
|
|
76
86
|
return skills.length;
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
// 文件化 Tool 的 disposer(用于热重载时移除旧 tool)
|
|
90
|
+
let toolFileDisposers: (() => void)[] = [];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Discover *.tool.md files and register them as tools.
|
|
94
|
+
*/
|
|
95
|
+
async function syncWorkspaceTools(): Promise<number> {
|
|
96
|
+
// 移除之前文件化注册的 tool
|
|
97
|
+
for (const d of toolFileDisposers) d();
|
|
98
|
+
toolFileDisposers = [];
|
|
99
|
+
|
|
100
|
+
const toolMetas = await discoverWorkspaceTools(root);
|
|
101
|
+
if (toolMetas.length === 0) return 0;
|
|
102
|
+
|
|
103
|
+
let added = 0;
|
|
104
|
+
for (const meta of toolMetas) {
|
|
105
|
+
// 跳过已通过程序化方式注册的同名 tool
|
|
106
|
+
if (toolService.get(meta.name)) {
|
|
107
|
+
logger.debug(`Tool '${meta.name}' 已存在(程序化注册),跳过文件化版本`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const tool = await buildToolFromMeta(meta);
|
|
111
|
+
if (!tool) continue;
|
|
112
|
+
const dispose = toolService.addTool(tool, root.name);
|
|
113
|
+
toolFileDisposers.push(dispose);
|
|
114
|
+
added++;
|
|
115
|
+
}
|
|
116
|
+
return added;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 已发现的 Agent 预设(本地缓存,无需存储到 Plugin 树)
|
|
120
|
+
const discoveredAgents = new Map<string, AgentPreset>();
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Discover *.agent.md files and register agent presets.
|
|
124
|
+
*/
|
|
125
|
+
async function syncWorkspaceAgents(): Promise<number> {
|
|
126
|
+
const agentMetas = await discoverWorkspaceAgents(root);
|
|
127
|
+
if (agentMetas.length === 0) return 0;
|
|
128
|
+
const allRegisteredTools = toolService.getAll();
|
|
129
|
+
const toolNameIndex = new Map<string, import('@zhin.js/core').Tool>();
|
|
130
|
+
for (const t of allRegisteredTools) {
|
|
131
|
+
toolNameIndex.set(t.name, t);
|
|
132
|
+
}
|
|
133
|
+
let added = 0;
|
|
134
|
+
for (const meta of agentMetas) {
|
|
135
|
+
if (discoveredAgents.has(meta.name)) continue;
|
|
136
|
+
const associatedTools: import('@zhin.js/core').Tool[] = [];
|
|
137
|
+
for (const toolName of meta.toolNames || []) {
|
|
138
|
+
const tool = toolService.get(toolName) || toolNameIndex.get(toolName);
|
|
139
|
+
if (tool) associatedTools.push(tool);
|
|
140
|
+
}
|
|
141
|
+
// Read body as systemPrompt
|
|
142
|
+
let systemPrompt: string | undefined;
|
|
143
|
+
try {
|
|
144
|
+
const content = await fs.promises.readFile(meta.filePath, 'utf-8');
|
|
145
|
+
const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*(?:\n|$)/, '').trim();
|
|
146
|
+
if (body) systemPrompt = body;
|
|
147
|
+
} catch { /* ignore */ }
|
|
148
|
+
discoveredAgents.set(meta.name, {
|
|
149
|
+
name: meta.name,
|
|
150
|
+
description: meta.description,
|
|
151
|
+
keywords: meta.keywords,
|
|
152
|
+
tags: meta.tags,
|
|
153
|
+
tools: associatedTools.length > 0 ? associatedTools : undefined,
|
|
154
|
+
systemPrompt,
|
|
155
|
+
model: meta.model,
|
|
156
|
+
provider: meta.provider,
|
|
157
|
+
maxIterations: meta.maxIterations,
|
|
158
|
+
filePath: meta.filePath,
|
|
159
|
+
});
|
|
160
|
+
added++;
|
|
161
|
+
}
|
|
162
|
+
return added;
|
|
163
|
+
}
|
|
164
|
+
|
|
79
165
|
(async () => {
|
|
80
166
|
// Step 1: discover workspace skills
|
|
81
167
|
try {
|
|
@@ -88,6 +174,26 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
88
174
|
logger.warn(`Failed to discover workspace skills: ${e.message}`);
|
|
89
175
|
}
|
|
90
176
|
|
|
177
|
+
// Step 1b: discover *.tool.md file-based tools
|
|
178
|
+
try {
|
|
179
|
+
const toolCount = await syncWorkspaceTools();
|
|
180
|
+
if (toolCount > 0) {
|
|
181
|
+
logger.info(`Registered ${toolCount} workspace file-based tools`);
|
|
182
|
+
}
|
|
183
|
+
} catch (e: any) {
|
|
184
|
+
logger.warn(`Failed to discover workspace tools: ${e.message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Step 1c: discover *.agent.md agent presets
|
|
188
|
+
try {
|
|
189
|
+
const agentCount = await syncWorkspaceAgents();
|
|
190
|
+
if (agentCount > 0) {
|
|
191
|
+
logger.info(`Registered ${agentCount} workspace agent presets`);
|
|
192
|
+
}
|
|
193
|
+
} catch (e: any) {
|
|
194
|
+
logger.debug(`Failed to discover workspace agents: ${e.message}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
91
197
|
// Step 2: load bootstrap files
|
|
92
198
|
const loadedFiles: string[] = [];
|
|
93
199
|
try {
|
|
@@ -140,6 +246,30 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
140
246
|
bootstrapFiles: loadedFiles,
|
|
141
247
|
}));
|
|
142
248
|
|
|
249
|
+
// Hot-reload tool directories
|
|
250
|
+
const workspaceToolDir = path.join(process.cwd(), 'tools');
|
|
251
|
+
const onToolDirChange = () => {
|
|
252
|
+
if (toolReloadDebounce) clearTimeout(toolReloadDebounce);
|
|
253
|
+
toolReloadDebounce = setTimeout(async () => {
|
|
254
|
+
toolReloadDebounce = null;
|
|
255
|
+
try {
|
|
256
|
+
const count = await syncWorkspaceTools();
|
|
257
|
+
if (count >= 0) logger.info(`[Tool热重载] 已更新,工作区文件化Tool: ${count}`);
|
|
258
|
+
} catch (e: any) {
|
|
259
|
+
logger.warn(`[Tool热重载] 失败: ${e.message}`);
|
|
260
|
+
}
|
|
261
|
+
}, 400);
|
|
262
|
+
};
|
|
263
|
+
if (fs.existsSync(workspaceToolDir)) {
|
|
264
|
+
try {
|
|
265
|
+
const w = fs.watch(workspaceToolDir, { recursive: true }, onToolDirChange);
|
|
266
|
+
skillWatchers.push(w);
|
|
267
|
+
logger.debug(`[Tool热重载] 监听目录: ${workspaceToolDir}`);
|
|
268
|
+
} catch (e: any) {
|
|
269
|
+
logger.debug(`[Tool热重载] 无法监听 ${workspaceToolDir}: ${e.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
143
273
|
// Hot-reload skill directories
|
|
144
274
|
const workspaceSkillDir = path.join(process.cwd(), 'skills');
|
|
145
275
|
const localSkillDir = path.join(os.homedir(), '.zhin', 'skills');
|
|
@@ -157,7 +287,7 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
157
287
|
refs.zhinAgent.setSkillsSummaryXML(skillsXml);
|
|
158
288
|
}
|
|
159
289
|
await triggerAIHook(createAIHookEvent('agent', 'skills-reloaded', undefined, { skillCount: count }));
|
|
160
|
-
if (count >= 0) logger.info(`[技能热重载]
|
|
290
|
+
if (count >= 0) logger.info(`[技能热重载] 已更新,工作区技能: ${count}`);
|
|
161
291
|
} catch (e: any) {
|
|
162
292
|
logger.warn(`[技能热重载] 失败: ${e.message}`);
|
|
163
293
|
}
|
|
@@ -178,9 +308,12 @@ export function registerBuiltinTools(refs: AIServiceRefs): void {
|
|
|
178
308
|
|
|
179
309
|
return () => {
|
|
180
310
|
disposers.forEach(d => d());
|
|
311
|
+
toolFileDisposers.forEach(d => d());
|
|
312
|
+
toolFileDisposers = [];
|
|
181
313
|
skillWatchers.forEach(w => w.close());
|
|
182
314
|
skillWatchers = [];
|
|
183
315
|
if (skillReloadDebounce) clearTimeout(skillReloadDebounce);
|
|
316
|
+
if (toolReloadDebounce) clearTimeout(toolReloadDebounce);
|
|
184
317
|
};
|
|
185
318
|
});
|
|
186
319
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Define AI-related database models (7 tables).
|
|
3
3
|
*/
|
|
4
4
|
import { getPlugin } from '@zhin.js/core';
|
|
5
|
-
import { AI_SESSION_MODEL } from '
|
|
6
|
-
import { CHAT_MESSAGE_MODEL, CONTEXT_SUMMARY_MODEL } from '
|
|
7
|
-
import { AI_MESSAGE_MODEL, AI_SUMMARY_MODEL } from '
|
|
5
|
+
import { AI_SESSION_MODEL } from '@zhin.js/ai';
|
|
6
|
+
import { CHAT_MESSAGE_MODEL, CONTEXT_SUMMARY_MODEL } from '@zhin.js/ai';
|
|
7
|
+
import { AI_MESSAGE_MODEL, AI_SUMMARY_MODEL } from '@zhin.js/ai';
|
|
8
8
|
import { AI_USER_PROFILE_MODEL } from '../user-profile.js';
|
|
9
9
|
import { AI_FOLLOWUP_MODEL } from '../follow-up.js';
|
|
10
10
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
import './types.js';
|
|
8
8
|
import { getPlugin } from '@zhin.js/core';
|
|
9
9
|
import type { AIConfig } from '@zhin.js/core';
|
|
10
|
-
import { createDatabaseSessionManager } from '
|
|
11
|
-
import { createContextManager } from '
|
|
10
|
+
import { createDatabaseSessionManager } from '@zhin.js/ai';
|
|
11
|
+
import { createContextManager } from '@zhin.js/ai';
|
|
12
12
|
import type { AIServiceRefs } from './shared-refs.js';
|
|
13
13
|
|
|
14
14
|
export function registerDbUpgrade(refs: AIServiceRefs): void {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import './types.js';
|
|
5
5
|
import { getPlugin, Message, ZhinTool } from '@zhin.js/core';
|
|
6
|
-
import { SessionManager } from '
|
|
6
|
+
import { SessionManager } from '@zhin.js/ai';
|
|
7
7
|
|
|
8
8
|
export function registerManagementTools(): void {
|
|
9
9
|
const plugin = getPlugin();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Register middleware that records messages to the context manager.
|
|
3
3
|
*/
|
|
4
4
|
import { getPlugin, Message } from '@zhin.js/core';
|
|
5
|
-
import type { MessageRecord } from '
|
|
5
|
+
import type { MessageRecord } from '@zhin.js/ai';
|
|
6
6
|
import type { AIServiceRefs } from './shared-refs.js';
|
|
7
7
|
|
|
8
8
|
export function registerMessageRecorder(refs: AIServiceRefs): void {
|
package/src/service.ts
CHANGED
|
@@ -25,14 +25,28 @@ import {
|
|
|
25
25
|
import {
|
|
26
26
|
SessionManager,
|
|
27
27
|
createMemorySessionManager,
|
|
28
|
-
|
|
29
|
-
} from '
|
|
30
|
-
import { Agent, createAgent } from './agent.js';
|
|
28
|
+
} from '@zhin.js/ai';
|
|
29
|
+
import { Agent, createAgent } from '@zhin.js/ai';
|
|
31
30
|
import { getBuiltinTools } from './tools.js';
|
|
32
|
-
import type { ContextManager, ContextConfig } from '
|
|
31
|
+
import type { ContextManager, ContextConfig } from '@zhin.js/ai';
|
|
32
|
+
import { PERM_MAP } from './zhin-agent/config.js';
|
|
33
33
|
|
|
34
34
|
const aiLogger = new Logger(null, 'AI');
|
|
35
35
|
|
|
36
|
+
/** Provider 注册表:key → 构造函数 + 是否需要 apiKey */
|
|
37
|
+
const PROVIDER_REGISTRY: Array<{
|
|
38
|
+
key: keyof NonNullable<AIConfig['providers']>;
|
|
39
|
+
factory: new (config: any) => AIProvider;
|
|
40
|
+
requireApiKey: boolean;
|
|
41
|
+
}> = [
|
|
42
|
+
{ key: 'openai', factory: OpenAIProvider, requireApiKey: true },
|
|
43
|
+
{ key: 'anthropic', factory: AnthropicProvider, requireApiKey: true },
|
|
44
|
+
{ key: 'deepseek', factory: DeepSeekProvider, requireApiKey: true },
|
|
45
|
+
{ key: 'moonshot', factory: MoonshotProvider, requireApiKey: true },
|
|
46
|
+
{ key: 'zhipu', factory: ZhipuProvider, requireApiKey: true },
|
|
47
|
+
{ key: 'ollama', factory: OllamaProvider, requireApiKey: false },
|
|
48
|
+
];
|
|
49
|
+
|
|
36
50
|
export class AIService {
|
|
37
51
|
private providers: Map<string, AIProvider> = new Map();
|
|
38
52
|
private defaultProvider: string;
|
|
@@ -55,23 +69,11 @@ export class AIService {
|
|
|
55
69
|
this.sessions = createMemorySessionManager(this.sessionConfig);
|
|
56
70
|
this.builtinTools = getBuiltinTools().map(tool => this.convertToolToAgentTool(tool.toTool()));
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.registerProvider(new
|
|
63
|
-
}
|
|
64
|
-
if (config.providers?.deepseek?.apiKey) {
|
|
65
|
-
this.registerProvider(new DeepSeekProvider(config.providers.deepseek));
|
|
66
|
-
}
|
|
67
|
-
if (config.providers?.moonshot?.apiKey) {
|
|
68
|
-
this.registerProvider(new MoonshotProvider(config.providers.moonshot));
|
|
69
|
-
}
|
|
70
|
-
if (config.providers?.zhipu?.apiKey) {
|
|
71
|
-
this.registerProvider(new ZhipuProvider(config.providers.zhipu));
|
|
72
|
-
}
|
|
73
|
-
if (config.providers?.ollama) {
|
|
74
|
-
this.registerProvider(new OllamaProvider(config.providers.ollama));
|
|
72
|
+
for (const { key, factory, requireApiKey } of PROVIDER_REGISTRY) {
|
|
73
|
+
const providerConfig = config.providers?.[key];
|
|
74
|
+
if (!providerConfig) continue;
|
|
75
|
+
if (requireApiKey && !(providerConfig as any).apiKey) continue;
|
|
76
|
+
this.registerProvider(new factory(providerConfig as any));
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -79,98 +81,15 @@ export class AIService {
|
|
|
79
81
|
return this.providers.size > 0;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
private static readonly PRE_EXEC_TIMEOUT = 10_000;
|
|
83
|
-
|
|
84
84
|
async process(
|
|
85
85
|
content: string,
|
|
86
86
|
context: ToolContext,
|
|
87
|
-
|
|
88
|
-
): Promise<string
|
|
87
|
+
_tools: Tool[],
|
|
88
|
+
): Promise<string> {
|
|
89
89
|
const { platform, senderId, sceneId } = context;
|
|
90
90
|
const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (allTools.length === 0) {
|
|
95
|
-
return this.finishAndSave(sessionId, content, baseSystemPrompt, sceneId);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const callerPermissionLevel = context.senderPermissionLevel
|
|
99
|
-
? (AIService.PERM_MAP[context.senderPermissionLevel] ?? 0)
|
|
100
|
-
: (context.isOwner ? 4 : context.isBotAdmin ? 3 : context.isGroupOwner ? 2 : context.isGroupAdmin ? 1 : 0);
|
|
101
|
-
|
|
102
|
-
const relevantTools = Agent.filterTools(content, allTools, {
|
|
103
|
-
callerPermissionLevel,
|
|
104
|
-
maxTools: 8,
|
|
105
|
-
minScore: 0.1,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (relevantTools.length === 0) {
|
|
109
|
-
return this.finishAndSave(sessionId, content, baseSystemPrompt, sceneId);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const noParamTools: AgentTool[] = [];
|
|
113
|
-
const paramTools: AgentTool[] = [];
|
|
114
|
-
for (const tool of relevantTools) {
|
|
115
|
-
const required = tool.parameters?.required;
|
|
116
|
-
(!required || required.length === 0) ? noParamTools.push(tool) : paramTools.push(tool);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let preExecutedData = '';
|
|
120
|
-
const preExecutedCalls: { tool: string; args: Record<string, any>; result: any }[] = [];
|
|
121
|
-
|
|
122
|
-
if (noParamTools.length > 0) {
|
|
123
|
-
const results = await Promise.allSettled(
|
|
124
|
-
noParamTools.map(async (tool) => {
|
|
125
|
-
const result = await Promise.race([
|
|
126
|
-
tool.execute({}),
|
|
127
|
-
new Promise<never>((_, reject) =>
|
|
128
|
-
setTimeout(() => reject(new Error('预执行超时')), AIService.PRE_EXEC_TIMEOUT)),
|
|
129
|
-
]);
|
|
130
|
-
return { name: tool.name, result };
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
133
|
-
for (const r of results) {
|
|
134
|
-
if (r.status === 'fulfilled') {
|
|
135
|
-
const s = typeof r.value.result === 'string' ? r.value.result : JSON.stringify(r.value.result);
|
|
136
|
-
preExecutedData += `\n${s}`;
|
|
137
|
-
preExecutedCalls.push({ tool: r.value.name, args: {}, result: r.value.result });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let finalResponse: string;
|
|
143
|
-
|
|
144
|
-
if (paramTools.length === 0 && preExecutedData) {
|
|
145
|
-
const singleShotPrompt = `You are a helpful AI assistant. Reply in the language specified in [User profile] (key: language / preferred_language), or in the user's message language if not set.\n\nPre-fetched data:\n${preExecutedData}\n\nAnswer the user's question based on the data above. Do not mention data sources or tool names. Summarize clearly and highlight key information.`;
|
|
146
|
-
finalResponse = await this.simpleChat(content, singleShotPrompt);
|
|
147
|
-
} else {
|
|
148
|
-
const agentSystemPrompt = `You are a helpful AI assistant. Reply in the language specified in [User profile] (key: language / preferred_language), or in the user's message language if not set.
|
|
149
|
-
${preExecutedData ? `\nPre-fetched data:\n${preExecutedData}\n` : ''}
|
|
150
|
-
## Requirements
|
|
151
|
-
- After calling tools, give a complete answer based on the results; do not mention tool names or data sources
|
|
152
|
-
- Summarize in natural language and highlight key information`;
|
|
153
|
-
|
|
154
|
-
const agent = this.createAgent({
|
|
155
|
-
systemPrompt: agentSystemPrompt,
|
|
156
|
-
tools: paramTools.length > 0 ? paramTools : relevantTools,
|
|
157
|
-
useBuiltinTools: false,
|
|
158
|
-
collectExternalTools: false,
|
|
159
|
-
maxIterations: 3,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const agentResult = await agent.run(content);
|
|
163
|
-
finalResponse = agentResult.content || this.formatToolCallsFallback(
|
|
164
|
-
[...preExecutedCalls, ...agentResult.toolCalls],
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
await this.sessions.addMessage(sessionId, { role: 'user', content });
|
|
169
|
-
await this.sessions.addMessage(sessionId, { role: 'assistant', content: finalResponse });
|
|
170
|
-
if (this.contextManager && sceneId) {
|
|
171
|
-
this.contextManager.autoSummarizeIfNeeded(sceneId).catch(() => {});
|
|
172
|
-
}
|
|
173
|
-
return finalResponse;
|
|
91
|
+
const systemPrompt = 'You are a helpful AI assistant. Reply in the language specified in [User profile] (key: language / preferred_language), or in the user\'s message language if not set.';
|
|
92
|
+
return this.finishAndSave(sessionId, content, systemPrompt, sceneId);
|
|
174
93
|
}
|
|
175
94
|
|
|
176
95
|
private async finishAndSave(sessionId: string, content: string, systemPrompt: string, sceneId?: string): Promise<string> {
|
|
@@ -183,13 +102,6 @@ ${preExecutedData ? `\nPre-fetched data:\n${preExecutedData}\n` : ''}
|
|
|
183
102
|
return response;
|
|
184
103
|
}
|
|
185
104
|
|
|
186
|
-
private formatToolCallsFallback(toolCalls: { tool: string; args: any; result: any }[]): string {
|
|
187
|
-
if (toolCalls.length === 0) return 'Done.';
|
|
188
|
-
return toolCalls.map(tc => {
|
|
189
|
-
return typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result, null, 2);
|
|
190
|
-
}).join('\n\n');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
105
|
private async simpleChat(content: string, systemPrompt: string): Promise<string> {
|
|
194
106
|
const provider = this.getProvider();
|
|
195
107
|
const response = await this.chat({
|
|
@@ -203,22 +115,6 @@ ${preExecutedData ? `\nPre-fetched data:\n${preExecutedData}\n` : ''}
|
|
|
203
115
|
return typeof msgContent === 'string' ? msgContent : '';
|
|
204
116
|
}
|
|
205
117
|
|
|
206
|
-
private collectAllToolsWithExternal(externalTools: Tool[]): AgentTool[] {
|
|
207
|
-
const tools: AgentTool[] = [];
|
|
208
|
-
tools.push(...this.builtinTools);
|
|
209
|
-
tools.push(...this.customTools.values());
|
|
210
|
-
for (const tool of externalTools) {
|
|
211
|
-
if (tool.name.startsWith('cmd_') || tool.name.startsWith('process_')) continue;
|
|
212
|
-
tools.push(this.convertToolToAgentTool(tool));
|
|
213
|
-
}
|
|
214
|
-
if (tools.length > 30) return tools.slice(0, 30);
|
|
215
|
-
return tools;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
private static readonly PERM_MAP: Record<string, number> = {
|
|
219
|
-
'user': 0, 'group_admin': 1, 'group_owner': 2, 'bot_admin': 3, 'owner': 4,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
118
|
private convertToolToAgentTool(tool: Tool): AgentTool {
|
|
223
119
|
const agentTool: AgentTool = {
|
|
224
120
|
name: tool.name,
|
|
@@ -227,7 +123,7 @@ ${preExecutedData ? `\nPre-fetched data:\n${preExecutedData}\n` : ''}
|
|
|
227
123
|
execute: async (args) => tool.execute(args),
|
|
228
124
|
};
|
|
229
125
|
if (tool.tags?.length) agentTool.tags = tool.tags;
|
|
230
|
-
if (tool.permissionLevel) agentTool.permissionLevel =
|
|
126
|
+
if (tool.permissionLevel) agentTool.permissionLevel = PERM_MAP[tool.permissionLevel] ?? 0;
|
|
231
127
|
if (tool.keywords?.length) agentTool.keywords = tool.keywords;
|
|
232
128
|
return agentTool;
|
|
233
129
|
}
|
package/src/subagent.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { Logger } from '@zhin.js/core';
|
|
13
13
|
import type { AIProvider, AgentTool } from '@zhin.js/core';
|
|
14
|
-
import { createAgent } from '
|
|
14
|
+
import { createAgent } from '@zhin.js/ai';
|
|
15
15
|
import type { ZhinAgentConfig } from './zhin-agent/config.js';
|
|
16
16
|
import { applyExecPolicyToTools } from './zhin-agent/exec-policy.js';
|
|
17
17
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ToolContext } from '@zhin.js/core';
|
|
9
9
|
import type { AgentTool } from '@zhin.js/core';
|
|
10
|
-
import type { ConversationMemory } from '
|
|
10
|
+
import type { ConversationMemory } from '@zhin.js/ai';
|
|
11
11
|
import type { UserProfileStore } from '../user-profile.js';
|
|
12
12
|
import type { FollowUpManager } from '../follow-up.js';
|
|
13
13
|
import type { SubagentManager, SubagentOrigin } from '../subagent.js';
|
package/src/zhin-agent/config.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* ZhinAgent 配置、常量、类型定义
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { RateLimitConfig } from '
|
|
6
|
-
import { DEFAULT_CONTEXT_TOKENS } from '
|
|
5
|
+
import type { RateLimitConfig } from '@zhin.js/ai';
|
|
6
|
+
import { DEFAULT_CONTEXT_TOKENS } from '@zhin.js/ai';
|
|
7
7
|
|
|
8
8
|
export type ModelSizeHint = 'small' | 'medium' | 'large';
|
|
9
9
|
|
|
@@ -56,6 +56,14 @@ export const PERM_MAP: Record<string, number> = {
|
|
|
56
56
|
|
|
57
57
|
export type OnChunkCallback = (chunk: string, full: string) => void;
|
|
58
58
|
|
|
59
|
+
/** 上下文感知内置工具的关键词触发正则 */
|
|
60
|
+
export const KEYWORD_TRIGGERS = {
|
|
61
|
+
chatHistory: /之前|上次|历史|回忆|聊过|记录|还记得|曾经/i,
|
|
62
|
+
userProfile: /偏好|设置|配置|档案|资料|时区|timezone|profile|喜好|我叫|叫我|记住我/i,
|
|
63
|
+
scheduleFollowUp: /提醒|定时|过一会|跟进|别忘|取消提醒|reminder|分钟后|小时后/i,
|
|
64
|
+
spawnTask: /后台|子任务|spawn|异步|background|并行|独立处理/i,
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
59
67
|
export interface ZhinAgentConfig {
|
|
60
68
|
persona?: string;
|
|
61
69
|
maxIterations?: number;
|
package/src/zhin-agent/index.ts
CHANGED
|
@@ -18,21 +18,21 @@
|
|
|
18
18
|
|
|
19
19
|
import { Logger } from '@zhin.js/core';
|
|
20
20
|
import type { Tool, ToolContext, SkillFeature, AIProvider, AgentTool, ChatMessage, ContentPart } from '@zhin.js/core';
|
|
21
|
-
import { createAgent } from '
|
|
22
|
-
import { SessionManager, createMemorySessionManager } from '
|
|
23
|
-
import type { ContextManager } from '
|
|
24
|
-
import { ConversationMemory } from '
|
|
25
|
-
import type { OutputElement } from '
|
|
26
|
-
import { parseOutput } from '
|
|
21
|
+
import { createAgent } from '@zhin.js/ai';
|
|
22
|
+
import { SessionManager, createMemorySessionManager } from '@zhin.js/ai';
|
|
23
|
+
import type { ContextManager } from '@zhin.js/ai';
|
|
24
|
+
import { ConversationMemory } from '@zhin.js/ai';
|
|
25
|
+
import type { OutputElement } from '@zhin.js/ai';
|
|
26
|
+
import { parseOutput } from '@zhin.js/ai';
|
|
27
27
|
import { UserProfileStore } from '../user-profile.js';
|
|
28
|
-
import { RateLimiter } from '
|
|
29
|
-
import { detectTone } from '
|
|
28
|
+
import { RateLimiter } from '@zhin.js/ai';
|
|
29
|
+
import { detectTone } from '@zhin.js/ai';
|
|
30
30
|
import { FollowUpManager, type FollowUpSender } from '../follow-up.js';
|
|
31
31
|
import { SubagentManager, type SubagentResultSender } from '../subagent.js';
|
|
32
32
|
import {
|
|
33
33
|
pruneHistoryForContext,
|
|
34
34
|
DEFAULT_CONTEXT_TOKENS,
|
|
35
|
-
} from '
|
|
35
|
+
} from '@zhin.js/ai';
|
|
36
36
|
import { triggerAIHook, createAIHookEvent } from '../hooks.js';
|
|
37
37
|
|
|
38
38
|
// ── Sub-modules ─────────────────────────────────────────────────────
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type ZhinAgentConfig,
|
|
41
41
|
type OnChunkCallback,
|
|
42
42
|
DEFAULT_CONFIG,
|
|
43
|
+
KEYWORD_TRIGGERS,
|
|
43
44
|
} from './config.js';
|
|
44
45
|
import { applyExecPolicyToTools } from './exec-policy.js';
|
|
45
46
|
import { collectRelevantTools } from './tool-collector.js';
|
|
@@ -62,6 +63,11 @@ export type { ZhinAgentConfig, OnChunkCallback } from './config.js';
|
|
|
62
63
|
const logger = new Logger(null, 'ZhinAgent');
|
|
63
64
|
const now = () => performance.now();
|
|
64
65
|
|
|
66
|
+
/** Strip `<think>…</think>` blocks that some reasoning models embed in content. */
|
|
67
|
+
function stripThinkBlocks(text: string): string {
|
|
68
|
+
return text.replace(/<think>[\s\S]*?<\/think>\s*/g, '').trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
// ============================================================================
|
|
66
72
|
// ZhinAgent
|
|
67
73
|
// ============================================================================
|
|
@@ -212,16 +218,16 @@ export class ZhinAgent {
|
|
|
212
218
|
});
|
|
213
219
|
|
|
214
220
|
// Inject context-aware built-in tools on keyword match
|
|
215
|
-
if (
|
|
221
|
+
if (KEYWORD_TRIGGERS.chatHistory.test(content)) {
|
|
216
222
|
allTools.push(createChatHistoryTool(sessionId, this.memory));
|
|
217
223
|
}
|
|
218
|
-
if (
|
|
224
|
+
if (KEYWORD_TRIGGERS.userProfile.test(content)) {
|
|
219
225
|
allTools.push(createUserProfileTool(userId, this.userProfiles));
|
|
220
226
|
}
|
|
221
|
-
if (
|
|
227
|
+
if (KEYWORD_TRIGGERS.scheduleFollowUp.test(content)) {
|
|
222
228
|
allTools.push(createScheduleFollowUpTool(sessionId, context, this.followUps));
|
|
223
229
|
}
|
|
224
|
-
if (this.subagentManager &&
|
|
230
|
+
if (this.subagentManager && KEYWORD_TRIGGERS.spawnTask.test(content)) {
|
|
225
231
|
allTools.push(createSpawnTaskTool(context, this.subagentManager));
|
|
226
232
|
}
|
|
227
233
|
|
|
@@ -352,7 +358,7 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
352
358
|
|
|
353
359
|
const userMessageWithHistory = buildUserMessageWithHistory(historyMessages, content);
|
|
354
360
|
const result = await agent.run(userMessageWithHistory, []);
|
|
355
|
-
reply = result.content || this.fallbackFormat(result.toolCalls);
|
|
361
|
+
reply = stripThinkBlocks(result.content) || this.fallbackFormat(result.toolCalls);
|
|
356
362
|
logger.info(`[Agent 路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, Agent=${(now() - tAgent).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
357
363
|
}
|
|
358
364
|
|
|
@@ -433,16 +439,25 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
433
439
|
let reply = '';
|
|
434
440
|
try {
|
|
435
441
|
for await (const chunk of this.provider.chatStream({ model: visionModel, messages })) {
|
|
436
|
-
const delta = chunk.choices?.[0]?.delta
|
|
437
|
-
if (delta
|
|
438
|
-
|
|
439
|
-
|
|
442
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
443
|
+
if (!delta) continue;
|
|
444
|
+
const text = typeof delta.content === 'string' ? delta.content : '';
|
|
445
|
+
if (text) {
|
|
446
|
+
reply += text;
|
|
447
|
+
if (onChunk) onChunk(text, reply);
|
|
440
448
|
}
|
|
441
449
|
}
|
|
450
|
+
reply = stripThinkBlocks(reply);
|
|
451
|
+
if (!reply) {
|
|
452
|
+
logger.warn('[processMultimodal] 流式响应内容为空,尝试非流式回退');
|
|
453
|
+
const response = await this.provider.chat({ model: visionModel, messages });
|
|
454
|
+
const msg = response.choices[0]?.message?.content;
|
|
455
|
+
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
456
|
+
}
|
|
442
457
|
} catch {
|
|
443
458
|
const response = await this.provider.chat({ model: visionModel, messages });
|
|
444
459
|
const msg = response.choices[0]?.message?.content;
|
|
445
|
-
reply = typeof msg === 'string' ? msg : '';
|
|
460
|
+
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
446
461
|
}
|
|
447
462
|
|
|
448
463
|
if (!reply) reply = '抱歉,我无法理解这条消息。';
|
|
@@ -474,20 +489,27 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
474
489
|
try {
|
|
475
490
|
let result = '';
|
|
476
491
|
for await (const chunk of this.provider.chatStream({ model, messages })) {
|
|
477
|
-
const delta = chunk.choices?.[0]?.delta
|
|
478
|
-
if (delta
|
|
479
|
-
|
|
480
|
-
|
|
492
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
493
|
+
if (!delta) continue;
|
|
494
|
+
const text = typeof delta.content === 'string' ? delta.content : '';
|
|
495
|
+
if (text) {
|
|
496
|
+
result += text;
|
|
497
|
+
if (onChunk) onChunk(text, result);
|
|
481
498
|
}
|
|
482
499
|
}
|
|
483
|
-
|
|
500
|
+
result = stripThinkBlocks(result);
|
|
501
|
+
if (result) return result;
|
|
502
|
+
// Streaming returned empty content — fall back to non-streaming
|
|
503
|
+
logger.warn('[streamChat] 流式响应内容为空,尝试非流式回退');
|
|
484
504
|
} catch {
|
|
485
|
-
|
|
486
|
-
const msg = response.choices[0]?.message?.content;
|
|
487
|
-
const result = typeof msg === 'string' ? msg : '';
|
|
488
|
-
if (onChunk && result) onChunk(result, result);
|
|
489
|
-
return result;
|
|
505
|
+
// Stream failed — fall back to non-streaming
|
|
490
506
|
}
|
|
507
|
+
const response = await this.provider.chat({ model, messages });
|
|
508
|
+
const msg = response.choices[0]?.message?.content;
|
|
509
|
+
let result = typeof msg === 'string' ? msg : '';
|
|
510
|
+
result = stripThinkBlocks(result);
|
|
511
|
+
if (onChunk && result) onChunk(result, result);
|
|
512
|
+
return result;
|
|
491
513
|
}
|
|
492
514
|
|
|
493
515
|
private async saveToSession(
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Logger } from '@zhin.js/core';
|
|
6
6
|
import type { Tool, ToolContext, SkillFeature } from '@zhin.js/core';
|
|
7
7
|
import type { AgentTool } from '@zhin.js/core';
|
|
8
|
-
import { Agent } from '
|
|
8
|
+
import { Agent } from '@zhin.js/ai';
|
|
9
9
|
import type { ZhinAgentConfig } from './config.js';
|
|
10
10
|
import { PERM_MAP } from './config.js';
|
|
11
11
|
|