@zhin.js/core 1.0.25 → 1.0.27

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +84 -342
  3. package/lib/adapter.d.ts +17 -0
  4. package/lib/adapter.d.ts.map +1 -1
  5. package/lib/adapter.js +84 -2
  6. package/lib/adapter.js.map +1 -1
  7. package/lib/ai/agent.d.ts +126 -0
  8. package/lib/ai/agent.d.ts.map +1 -0
  9. package/lib/ai/agent.js +645 -0
  10. package/lib/ai/agent.js.map +1 -0
  11. package/lib/ai/context-manager.d.ts +213 -0
  12. package/lib/ai/context-manager.d.ts.map +1 -0
  13. package/lib/ai/context-manager.js +313 -0
  14. package/lib/ai/context-manager.js.map +1 -0
  15. package/lib/ai/conversation-memory.d.ts +181 -0
  16. package/lib/ai/conversation-memory.d.ts.map +1 -0
  17. package/lib/ai/conversation-memory.js +581 -0
  18. package/lib/ai/conversation-memory.js.map +1 -0
  19. package/lib/ai/follow-up.d.ts +131 -0
  20. package/lib/ai/follow-up.d.ts.map +1 -0
  21. package/lib/ai/follow-up.js +265 -0
  22. package/lib/ai/follow-up.js.map +1 -0
  23. package/lib/ai/index.d.ts +29 -0
  24. package/lib/ai/index.d.ts.map +1 -0
  25. package/lib/ai/index.js +34 -0
  26. package/lib/ai/index.js.map +1 -0
  27. package/lib/ai/init.d.ts +30 -0
  28. package/lib/ai/init.d.ts.map +1 -0
  29. package/lib/ai/init.js +424 -0
  30. package/lib/ai/init.js.map +1 -0
  31. package/lib/ai/output.d.ts +93 -0
  32. package/lib/ai/output.d.ts.map +1 -0
  33. package/lib/ai/output.js +176 -0
  34. package/lib/ai/output.js.map +1 -0
  35. package/lib/ai/providers/anthropic.d.ts +23 -0
  36. package/lib/ai/providers/anthropic.d.ts.map +1 -0
  37. package/lib/ai/providers/anthropic.js +322 -0
  38. package/lib/ai/providers/anthropic.js.map +1 -0
  39. package/lib/ai/providers/base.d.ts +43 -0
  40. package/lib/ai/providers/base.d.ts.map +1 -0
  41. package/lib/ai/providers/base.js +135 -0
  42. package/lib/ai/providers/base.js.map +1 -0
  43. package/lib/ai/providers/index.d.ts +12 -0
  44. package/lib/ai/providers/index.d.ts.map +1 -0
  45. package/lib/ai/providers/index.js +9 -0
  46. package/lib/ai/providers/index.js.map +1 -0
  47. package/lib/ai/providers/ollama.d.ts +25 -0
  48. package/lib/ai/providers/ollama.d.ts.map +1 -0
  49. package/lib/ai/providers/ollama.js +243 -0
  50. package/lib/ai/providers/ollama.js.map +1 -0
  51. package/lib/ai/providers/openai.d.ts +46 -0
  52. package/lib/ai/providers/openai.d.ts.map +1 -0
  53. package/lib/ai/providers/openai.js +132 -0
  54. package/lib/ai/providers/openai.js.map +1 -0
  55. package/lib/ai/rate-limiter.d.ts +38 -0
  56. package/lib/ai/rate-limiter.d.ts.map +1 -0
  57. package/lib/ai/rate-limiter.js +86 -0
  58. package/lib/ai/rate-limiter.js.map +1 -0
  59. package/lib/ai/service.d.ts +81 -0
  60. package/lib/ai/service.d.ts.map +1 -0
  61. package/lib/ai/service.js +274 -0
  62. package/lib/ai/service.js.map +1 -0
  63. package/lib/ai/session.d.ts +186 -0
  64. package/lib/ai/session.d.ts.map +1 -0
  65. package/lib/ai/session.js +443 -0
  66. package/lib/ai/session.js.map +1 -0
  67. package/lib/ai/tone-detector.d.ts +19 -0
  68. package/lib/ai/tone-detector.d.ts.map +1 -0
  69. package/lib/ai/tone-detector.js +72 -0
  70. package/lib/ai/tone-detector.js.map +1 -0
  71. package/lib/ai/tools.d.ts +45 -0
  72. package/lib/ai/tools.d.ts.map +1 -0
  73. package/lib/ai/tools.js +206 -0
  74. package/lib/ai/tools.js.map +1 -0
  75. package/lib/ai/types.d.ts +264 -0
  76. package/lib/ai/types.d.ts.map +1 -0
  77. package/lib/ai/types.js +6 -0
  78. package/lib/ai/types.js.map +1 -0
  79. package/lib/ai/user-profile.d.ts +56 -0
  80. package/lib/ai/user-profile.d.ts.map +1 -0
  81. package/lib/ai/user-profile.js +130 -0
  82. package/lib/ai/user-profile.js.map +1 -0
  83. package/lib/ai/zhin-agent.d.ts +165 -0
  84. package/lib/ai/zhin-agent.d.ts.map +1 -0
  85. package/lib/ai/zhin-agent.js +707 -0
  86. package/lib/ai/zhin-agent.js.map +1 -0
  87. package/lib/built/ai-trigger.d.ts.map +1 -1
  88. package/lib/built/ai-trigger.js +7 -3
  89. package/lib/built/ai-trigger.js.map +1 -1
  90. package/lib/built/command.d.ts +33 -17
  91. package/lib/built/command.d.ts.map +1 -1
  92. package/lib/built/command.js +71 -44
  93. package/lib/built/command.js.map +1 -1
  94. package/lib/built/component.d.ts +42 -15
  95. package/lib/built/component.d.ts.map +1 -1
  96. package/lib/built/component.js +84 -52
  97. package/lib/built/component.js.map +1 -1
  98. package/lib/built/config.d.ts +64 -5
  99. package/lib/built/config.d.ts.map +1 -1
  100. package/lib/built/config.js +129 -12
  101. package/lib/built/config.js.map +1 -1
  102. package/lib/built/cron.d.ts +41 -18
  103. package/lib/built/cron.d.ts.map +1 -1
  104. package/lib/built/cron.js +106 -63
  105. package/lib/built/cron.js.map +1 -1
  106. package/lib/built/database.d.ts +55 -6
  107. package/lib/built/database.d.ts.map +1 -1
  108. package/lib/built/database.js +93 -22
  109. package/lib/built/database.js.map +1 -1
  110. package/lib/built/dispatcher.d.ts +118 -0
  111. package/lib/built/dispatcher.d.ts.map +1 -0
  112. package/lib/built/dispatcher.js +196 -0
  113. package/lib/built/dispatcher.js.map +1 -0
  114. package/lib/built/permission.d.ts +45 -5
  115. package/lib/built/permission.d.ts.map +1 -1
  116. package/lib/built/permission.js +56 -11
  117. package/lib/built/permission.js.map +1 -1
  118. package/lib/built/skill.d.ts +117 -0
  119. package/lib/built/skill.d.ts.map +1 -0
  120. package/lib/built/skill.js +191 -0
  121. package/lib/built/skill.js.map +1 -0
  122. package/lib/built/tool.d.ts +71 -164
  123. package/lib/built/tool.d.ts.map +1 -1
  124. package/lib/built/tool.js +212 -297
  125. package/lib/built/tool.js.map +1 -1
  126. package/lib/feature.d.ts +75 -0
  127. package/lib/feature.d.ts.map +1 -0
  128. package/lib/feature.js +69 -0
  129. package/lib/feature.js.map +1 -0
  130. package/lib/index.d.ts +4 -0
  131. package/lib/index.d.ts.map +1 -1
  132. package/lib/index.js +7 -0
  133. package/lib/index.js.map +1 -1
  134. package/lib/plugin.d.ts +25 -17
  135. package/lib/plugin.d.ts.map +1 -1
  136. package/lib/plugin.js +180 -20
  137. package/lib/plugin.js.map +1 -1
  138. package/lib/types.d.ts +4 -9
  139. package/lib/types.d.ts.map +1 -1
  140. package/package.json +6 -6
  141. package/src/adapter.ts +101 -2
  142. package/src/ai/agent.ts +772 -0
  143. package/src/ai/context-manager.ts +440 -0
  144. package/src/ai/conversation-memory.ts +774 -0
  145. package/src/ai/follow-up.ts +357 -0
  146. package/src/ai/index.ts +128 -0
  147. package/src/ai/init.ts +502 -0
  148. package/src/ai/output.ts +261 -0
  149. package/src/ai/providers/anthropic.ts +375 -0
  150. package/src/ai/providers/base.ts +173 -0
  151. package/src/ai/providers/index.ts +13 -0
  152. package/src/ai/providers/ollama.ts +292 -0
  153. package/src/ai/providers/openai.ts +167 -0
  154. package/src/ai/rate-limiter.ts +129 -0
  155. package/src/ai/service.ts +319 -0
  156. package/src/ai/session.ts +544 -0
  157. package/src/ai/tone-detector.ts +89 -0
  158. package/src/ai/tools.ts +218 -0
  159. package/src/ai/types.ts +296 -0
  160. package/src/ai/user-profile.ts +181 -0
  161. package/src/ai/zhin-agent.ts +845 -0
  162. package/src/built/ai-trigger.ts +6 -3
  163. package/src/built/command.ts +75 -69
  164. package/src/built/component.ts +94 -76
  165. package/src/built/config.ts +288 -128
  166. package/src/built/cron.ts +117 -101
  167. package/src/built/database.ts +128 -33
  168. package/src/built/dispatcher.ts +332 -0
  169. package/src/built/permission.ts +146 -54
  170. package/src/built/skill.ts +280 -0
  171. package/src/built/tool.ts +245 -366
  172. package/src/feature.ts +113 -0
  173. package/src/index.ts +7 -0
  174. package/src/plugin.ts +198 -33
  175. package/src/types.ts +6 -10
  176. package/tests/adapter.test.ts +153 -1
  177. package/tests/ai/agent.test.ts +614 -0
  178. package/tests/ai/ai-trigger.test.ts +368 -0
  179. package/tests/ai/context-manager.test.ts +413 -0
  180. package/tests/ai/conversation-memory.test.ts +128 -0
  181. package/tests/ai/follow-up.test.ts +175 -0
  182. package/tests/ai/integration.test.ts +584 -0
  183. package/tests/ai/output.test.ts +128 -0
  184. package/tests/ai/providers.integration.test.ts +227 -0
  185. package/tests/ai/rate-limiter.test.ts +108 -0
  186. package/tests/ai/session.test.ts +375 -0
  187. package/tests/ai/setup.ts +308 -0
  188. package/tests/ai/tone-detector.test.ts +80 -0
  189. package/tests/ai/tool.test.ts +800 -0
  190. package/tests/ai/tools-builtin.test.ts +346 -0
  191. package/tests/ai/user-profile.test.ts +73 -0
  192. package/tests/ai/zhin-agent.test.ts +177 -0
  193. package/tests/config.test.ts +46 -0
  194. package/tests/cron.test.ts +94 -5
  195. package/tests/dispatcher.test.ts +146 -0
  196. package/tests/feature.test.ts +145 -0
  197. package/tests/features-builtin.test.ts +191 -0
  198. package/tests/plugin.test.ts +88 -14
  199. package/tests/skill-feature.test.ts +179 -0
  200. package/tests/tool-feature.test.ts +254 -0
  201. package/test/minimal-bot.ts +0 -31
  202. package/test/stress-test.ts +0 -123
@@ -0,0 +1,218 @@
1
+ /**
2
+ * @zhin.js/ai - Built-in Tools
3
+ * 内置工具集合 - 使用 ZhinTool 类定义
4
+ */
5
+
6
+ import { ZhinTool } from '../built/tool.js';
7
+ import type { Tool } from '../types.js';
8
+
9
+ /**
10
+ * 计算器工具
11
+ * 支持基本运算和数学函数
12
+ */
13
+ export const calculatorTool = new ZhinTool('calculator')
14
+ .desc('执行数学计算。支持基本运算(+、-、*、/、^)和数学函数(sin、cos、sqrt 等)')
15
+ .keyword('计算', '算', 'calc', '数学', '求值', '运算')
16
+ .tag('math', 'utility')
17
+ .param('expression', { type: 'string', description: '数学表达式,例如 "2 + 3 * 4" 或 "sqrt(16)"' }, true)
18
+ .execute(async ({ expression }) => {
19
+ try {
20
+ // 安全的数学表达式求值
21
+ const sanitized = (expression as string)
22
+ .replace(/[^0-9+\-*/().^eEsincosqrtabspowlogxMathPI\s]/g, '')
23
+ .replace(/\bsqrt\b/g, 'Math.sqrt')
24
+ .replace(/\bsin\b/g, 'Math.sin')
25
+ .replace(/\bcos\b/g, 'Math.cos')
26
+ .replace(/\btan\b/g, 'Math.tan')
27
+ .replace(/\blog\b/g, 'Math.log')
28
+ .replace(/\babs\b/g, 'Math.abs')
29
+ .replace(/\bpow\b/g, 'Math.pow')
30
+ .replace(/\bPI\b/g, 'Math.PI')
31
+ .replace(/\bE\b(?![0-9])/g, 'Math.E')
32
+ .replace(/\^/g, '**');
33
+
34
+ const result = new Function(`return ${sanitized}`)();
35
+ return { result, expression };
36
+ } catch (error) {
37
+ return { error: '无法计算表达式', expression };
38
+ }
39
+ });
40
+
41
+ /**
42
+ * 时间工具
43
+ * 获取当前时间和日期
44
+ */
45
+ export const timeTool = new ZhinTool('get_time')
46
+ .desc('获取当前时间和日期信息')
47
+ .keyword('时间', '日期', '几点', '今天', '现在', 'time', 'date')
48
+ .tag('time', 'utility')
49
+ .param('timezone', { type: 'string', description: '时区,例如 "Asia/Shanghai" 或 "UTC"' })
50
+ .param('format', { type: 'string', description: '输出格式: full(完整), date(日期), time(时间), timestamp(时间戳)' })
51
+ .execute(async ({ timezone, format = 'full' }) => {
52
+ const now = new Date();
53
+ const options: Intl.DateTimeFormatOptions = {
54
+ timeZone: (timezone as string) || 'Asia/Shanghai',
55
+ };
56
+
57
+ switch (format) {
58
+ case 'date':
59
+ options.dateStyle = 'full';
60
+ break;
61
+ case 'time':
62
+ options.timeStyle = 'long';
63
+ break;
64
+ case 'timestamp':
65
+ return { timestamp: now.getTime(), iso: now.toISOString() };
66
+ default:
67
+ options.dateStyle = 'full';
68
+ options.timeStyle = 'long';
69
+ }
70
+
71
+ return {
72
+ formatted: now.toLocaleString('zh-CN', options),
73
+ timestamp: now.getTime(),
74
+ iso: now.toISOString(),
75
+ };
76
+ });
77
+
78
+ /**
79
+ * 网页搜索工具
80
+ * 需要配置搜索 API
81
+ */
82
+ export const searchTool = new ZhinTool('web_search')
83
+ .desc('在互联网上搜索信息(需要配置搜索 API)')
84
+ .keyword('搜索', '搜一下', '查找', '查一查', 'search', 'google')
85
+ .tag('search', 'web')
86
+ .param('query', { type: 'string', description: '搜索关键词' }, true)
87
+ .param('limit', { type: 'number', description: '返回结果数量(默认 5)' })
88
+ .execute(async ({ query, limit = 5 }) => {
89
+ // 默认实现提示需要配置
90
+ return {
91
+ query,
92
+ error: '搜索功能未配置,请提供搜索 API',
93
+ hint: '可以集成 SerpAPI、Bing Search API 或 Google Custom Search',
94
+ };
95
+ });
96
+
97
+ /**
98
+ * 代码执行工具
99
+ * 在安全沙箱中执行 JavaScript
100
+ */
101
+ export const codeRunnerTool = new ZhinTool('run_code')
102
+ .desc('执行 JavaScript 代码(在安全沙箱中)')
103
+ .keyword('代码', '执行', '运行', 'code', 'js', 'javascript')
104
+ .tag('code', 'dev')
105
+ .param('code', { type: 'string', description: 'JavaScript 代码' }, true)
106
+ .execute(async ({ code }) => {
107
+ try {
108
+ // 简单的沙箱执行(生产环境应使用 vm2 或 isolated-vm)
109
+ const result = new Function(`
110
+ 'use strict';
111
+ const console = { log: (...args) => args.join(' ') };
112
+ return (function() { ${code} })();
113
+ `)();
114
+
115
+ return {
116
+ success: true,
117
+ result: result !== undefined ? String(result) : 'undefined',
118
+ };
119
+ } catch (error) {
120
+ return {
121
+ success: false,
122
+ error: error instanceof Error ? error.message : String(error),
123
+ };
124
+ }
125
+ });
126
+
127
+ /**
128
+ * HTTP 请求工具
129
+ * 发送 HTTP 请求获取数据
130
+ */
131
+ export const httpTool = new ZhinTool('http_request')
132
+ .desc('发送 HTTP 请求获取数据')
133
+ .keyword('http', 'api', '请求', '接口', 'url', 'fetch')
134
+ .tag('http', 'web')
135
+ .param('url', { type: 'string', description: '请求 URL' }, true)
136
+ .param('method', { type: 'string', description: 'HTTP 方法: GET, POST, PUT, DELETE(默认 GET)' })
137
+ .param('headers', { type: 'object', description: '请求头(JSON 对象)' })
138
+ .param('body', { type: 'string', description: '请求体(JSON 字符串)' })
139
+ .execute(async ({ url, method = 'GET', headers = {}, body }) => {
140
+ try {
141
+ const response = await fetch(url as string, {
142
+ method: method as string,
143
+ headers: {
144
+ 'Content-Type': 'application/json',
145
+ ...(headers as Record<string, string>),
146
+ },
147
+ body: body ? (body as string) : undefined,
148
+ });
149
+
150
+ const contentType = response.headers.get('content-type') || '';
151
+ let data: any;
152
+
153
+ if (contentType.includes('application/json')) {
154
+ data = await response.json();
155
+ } else {
156
+ data = await response.text();
157
+ // 限制文本长度
158
+ if (data.length > 5000) {
159
+ data = data.substring(0, 5000) + '... (truncated)';
160
+ }
161
+ }
162
+
163
+ return {
164
+ status: response.status,
165
+ statusText: response.statusText,
166
+ data,
167
+ };
168
+ } catch (error) {
169
+ return {
170
+ error: error instanceof Error ? error.message : String(error),
171
+ };
172
+ }
173
+ });
174
+
175
+ /**
176
+ * 记忆工具
177
+ * 让 AI 记住重要信息
178
+ */
179
+ export const memoryTool = new ZhinTool('remember')
180
+ .desc('记住用户告诉你的重要信息,以便后续对话中使用')
181
+ .keyword('记住', '记忆', '记下', 'remember', '别忘了')
182
+ .tag('memory', 'context')
183
+ .param('key', { type: 'string', description: '记忆的标识符,如 "user_name", "preference"' }, true)
184
+ .param('value', { type: 'string', description: '要记住的内容' }, true)
185
+ .execute(async ({ key, value }, context) => {
186
+ // 这里需要与 session/context manager 集成
187
+ return {
188
+ success: true,
189
+ message: `已记住 ${key}: ${value}`,
190
+ key,
191
+ value,
192
+ };
193
+ });
194
+
195
+ /**
196
+ * 获取所有内置工具(ZhinTool 实例)
197
+ * 注意:天气工具已移除,请使用 weather-tool 插件,支持多平台配置
198
+ */
199
+ export function getBuiltinTools(): ZhinTool[] {
200
+ return [
201
+ calculatorTool,
202
+ timeTool,
203
+ ];
204
+ }
205
+
206
+ /**
207
+ * 获取所有可用的内置工具(包括可选工具)
208
+ */
209
+ export function getAllBuiltinTools(): ZhinTool[] {
210
+ return [
211
+ calculatorTool,
212
+ timeTool,
213
+ searchTool,
214
+ codeRunnerTool,
215
+ httpTool,
216
+ memoryTool,
217
+ ];
218
+ }
@@ -0,0 +1,296 @@
1
+ /**
2
+ * @zhin.js/ai - AI Service Types
3
+ * 统一的 AI 服务类型定义
4
+ */
5
+
6
+ // ============================================================================
7
+ // 基础类型
8
+ // ============================================================================
9
+
10
+ /** 消息角色 */
11
+ export type MessageRole = 'system' | 'user' | 'assistant' | 'tool' | 'tool_call' | 'tool_result';
12
+
13
+ /** 聊天消息 */
14
+ export interface ChatMessage {
15
+ role: MessageRole;
16
+ content: string | ContentPart[];
17
+ name?: string;
18
+ tool_call_id?: string;
19
+ tool_calls?: ToolCall[];
20
+ }
21
+
22
+ /** 内容部分(支持多模态) */
23
+ export type ContentPart =
24
+ | { type: 'text'; text: string }
25
+ | { type: 'image_url'; image_url: { url: string; detail?: 'auto' | 'low' | 'high' } }
26
+ | { type: 'audio'; audio: { data: string; format: 'wav' | 'mp3' } };
27
+
28
+ /** 工具调用 */
29
+ export interface ToolCall {
30
+ id: string;
31
+ type: 'function';
32
+ function: {
33
+ name: string;
34
+ arguments: string;
35
+ };
36
+ }
37
+
38
+ /** 工具定义 */
39
+ export interface ToolDefinition {
40
+ type: 'function';
41
+ function: {
42
+ name: string;
43
+ description: string;
44
+ parameters: JsonSchema;
45
+ };
46
+ }
47
+
48
+ /** JSON Schema */
49
+ export interface JsonSchema {
50
+ type: string;
51
+ properties?: Record<string, JsonSchema>;
52
+ required?: string[];
53
+ items?: JsonSchema;
54
+ enum?: any[];
55
+ description?: string;
56
+ default?: any;
57
+ [key: string]: any;
58
+ }
59
+
60
+ // ============================================================================
61
+ // 请求/响应类型
62
+ // ============================================================================
63
+
64
+ /** 聊天补全请求 */
65
+ export interface ChatCompletionRequest {
66
+ model: string;
67
+ messages: ChatMessage[];
68
+ tools?: ToolDefinition[];
69
+ tool_choice?: 'auto' | 'none' | 'required' | { type: 'function'; function: { name: string } };
70
+ temperature?: number;
71
+ top_p?: number;
72
+ max_tokens?: number;
73
+ stream?: boolean;
74
+ stop?: string | string[];
75
+ presence_penalty?: number;
76
+ frequency_penalty?: number;
77
+ user?: string;
78
+ /** 是否启用模型思考(如 qwen3 的 <think> 模式)。设为 false 可跳过思考加速响应。 */
79
+ think?: boolean;
80
+ }
81
+
82
+ /** 聊天补全响应 */
83
+ export interface ChatCompletionResponse {
84
+ id: string;
85
+ object: 'chat.completion';
86
+ created: number;
87
+ model: string;
88
+ choices: ChatCompletionChoice[];
89
+ usage?: Usage;
90
+ }
91
+
92
+ /** 选择 */
93
+ export interface ChatCompletionChoice {
94
+ index: number;
95
+ message: ChatMessage;
96
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
97
+ }
98
+
99
+ /** 用量统计 */
100
+ export interface Usage {
101
+ prompt_tokens: number;
102
+ completion_tokens: number;
103
+ total_tokens: number;
104
+ }
105
+
106
+ // ============================================================================
107
+ // 流式响应类型
108
+ // ============================================================================
109
+
110
+ /** 流式响应块 */
111
+ export interface ChatCompletionChunk {
112
+ id: string;
113
+ object: 'chat.completion.chunk';
114
+ created: number;
115
+ model: string;
116
+ choices: ChatCompletionChunkChoice[];
117
+ usage?: Usage;
118
+ }
119
+
120
+ /** 流式选择 */
121
+ export interface ChatCompletionChunkChoice {
122
+ index: number;
123
+ delta: Partial<ChatMessage>;
124
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
125
+ }
126
+
127
+ // ============================================================================
128
+ // Provider 类型
129
+ // ============================================================================
130
+
131
+ /** Provider 配置 */
132
+ export interface ProviderConfig {
133
+ apiKey?: string;
134
+ baseUrl?: string;
135
+ defaultModel?: string;
136
+ timeout?: number;
137
+ maxRetries?: number;
138
+ headers?: Record<string, string>;
139
+ }
140
+
141
+ /** Provider 接口 */
142
+ export interface AIProvider {
143
+ name: string;
144
+ models: string[];
145
+
146
+ /** 聊天补全 */
147
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
148
+
149
+ /** 流式聊天补全 */
150
+ chatStream(request: ChatCompletionRequest): AsyncIterable<ChatCompletionChunk>;
151
+
152
+ /** 列出可用模型 */
153
+ listModels?(): Promise<string[]>;
154
+
155
+ /** 检查连接 */
156
+ healthCheck?(): Promise<boolean>;
157
+ }
158
+
159
+ // ============================================================================
160
+ // Agent 类型
161
+ // ============================================================================
162
+
163
+ /** Agent 工具 */
164
+ export interface AgentTool {
165
+ name: string;
166
+ description: string;
167
+ parameters: JsonSchema;
168
+ execute: (args: Record<string, any>) => Promise<any>;
169
+ /** 工具标签,用于分类和快速匹配 */
170
+ tags?: string[];
171
+ /** 触发关键词,用户消息包含这些词时优先选择此工具 */
172
+ keywords?: string[];
173
+ /** 所需权限级别 (0=所有人, 1=群管理, 2=群主, 3=Bot管理员, 4=拥有者) */
174
+ permissionLevel?: number;
175
+ }
176
+
177
+ /**
178
+ * 工具过滤选项
179
+ * 在 Agent.run() / runStream() 中启用程序化工具预过滤,
180
+ * 省去额外的 AI 意图分析往返
181
+ */
182
+ export interface ToolFilterOptions {
183
+ /** 调用者权限级别 (0-4),高于工具要求才能使用 */
184
+ callerPermissionLevel?: number;
185
+ /** 最大返回工具数量 (默认 10) */
186
+ maxTools?: number;
187
+ /** 最低相关性得分阈值,低于此分数的工具被过滤掉 (默认 0.1) */
188
+ minScore?: number;
189
+ }
190
+
191
+ /** Agent 配置 */
192
+ export interface AgentConfig {
193
+ provider: string;
194
+ model?: string;
195
+ systemPrompt?: string;
196
+ tools?: AgentTool[];
197
+ maxIterations?: number;
198
+ temperature?: number;
199
+ }
200
+
201
+ /** Agent 运行结果 */
202
+ export interface AgentResult {
203
+ content: string;
204
+ toolCalls: {
205
+ tool: string;
206
+ args: Record<string, any>;
207
+ result: any;
208
+ }[];
209
+ usage: Usage;
210
+ iterations: number;
211
+ }
212
+
213
+ // ============================================================================
214
+ // Session 类型
215
+ // ============================================================================
216
+
217
+ /** 会话配置 */
218
+ export interface SessionConfig {
219
+ provider: string;
220
+ model?: string;
221
+ systemPrompt?: string;
222
+ maxHistory?: number;
223
+ expireMs?: number;
224
+ }
225
+
226
+ /** 会话 */
227
+ export interface Session {
228
+ id: string;
229
+ config: SessionConfig;
230
+ messages: ChatMessage[];
231
+ createdAt: number;
232
+ updatedAt: number;
233
+ metadata?: Record<string, any>;
234
+ }
235
+
236
+ // ============================================================================
237
+ // AI Service 配置
238
+ // ============================================================================
239
+
240
+ /** AI 服务配置 */
241
+ export interface AIConfig {
242
+ enabled?: boolean;
243
+ defaultProvider?: string;
244
+ providers?: {
245
+ openai?: ProviderConfig;
246
+ anthropic?: ProviderConfig;
247
+ deepseek?: ProviderConfig;
248
+ moonshot?: ProviderConfig;
249
+ zhipu?: ProviderConfig;
250
+ ollama?: ProviderConfig & { host?: string; models?: string[] };
251
+ custom?: ProviderConfig[];
252
+ };
253
+ sessions?: {
254
+ /** 最大历史消息数(数据库模式默认200,内存模式默认100) */
255
+ maxHistory?: number;
256
+ /** 会话过期时间(毫秒,数据库模式默认7天,内存模式默认24小时) */
257
+ expireMs?: number;
258
+ /** 是否使用数据库持久化存储(默认 true) */
259
+ useDatabase?: boolean;
260
+ };
261
+ context?: {
262
+ /** 是否启用消息记录(默认 true) */
263
+ enabled?: boolean;
264
+ /** 读取的最近消息数量(默认 100) */
265
+ maxRecentMessages?: number;
266
+ /** 触发总结的消息数量阈值(默认 50) */
267
+ summaryThreshold?: number;
268
+ /** 总结后保留的消息数量(默认 10) */
269
+ keepAfterSummary?: number;
270
+ /** 上下文最大 token 估算(默认 4000) */
271
+ maxContextTokens?: number;
272
+ /** 自定义总结提示词 */
273
+ summaryPrompt?: string;
274
+ };
275
+ /** AI 触发配置 */
276
+ trigger?: {
277
+ /** 是否启用(默认 true) */
278
+ enabled?: boolean;
279
+ /** 触发前缀列表(默认 ['#', 'AI:']) */
280
+ prefixes?: string[];
281
+ /** 是否响应 @ 机器人(默认 true) */
282
+ respondToAt?: boolean;
283
+ /** 是否响应私聊(默认 true) */
284
+ respondToPrivate?: boolean;
285
+ /** 触发关键词(可选) */
286
+ keywords?: string[];
287
+ /** 忽略的前缀(命令前缀,避免与命令冲突,默认 ['/', '!', '!']) */
288
+ ignorePrefixes?: string[];
289
+ /** 超时时间(毫秒,默认 60000) */
290
+ timeout?: number;
291
+ /** 思考中提示语(可选,设置后会在处理前发送) */
292
+ thinkingMessage?: string;
293
+ /** 错误提示模板(默认 '❌ AI 处理失败: {error}') */
294
+ errorTemplate?: string;
295
+ };
296
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * UserProfileStore — 用户画像存储
3
+ *
4
+ * 持久化存储用户偏好和特征,让 AI 跨会话记住用户的个性化信息。
5
+ *
6
+ * ai_user_profiles 表:
7
+ * ┌──────────────────────────────────────────────────────────┐
8
+ * │ user_id | key | value | updated_at │
9
+ * │ u1 | name | 小明 | 1700000000 │
10
+ * │ u1 | style | 简洁正式 | 1700000001 │
11
+ * │ u1 | interests | 编程,天气 | 1700000010 │
12
+ * └──────────────────────────────────────────────────────────┘
13
+ */
14
+
15
+ import { Logger } from '@zhin.js/logger';
16
+
17
+ const logger = new Logger(null, 'UserProfile');
18
+
19
+ // ============================================================================
20
+ // 数据库模型
21
+ // ============================================================================
22
+
23
+ export const AI_USER_PROFILE_MODEL = {
24
+ user_id: { type: 'text' as const, nullable: false },
25
+ key: { type: 'text' as const, nullable: false },
26
+ value: { type: 'text' as const, nullable: false },
27
+ updated_at: { type: 'integer' as const, default: 0 },
28
+ };
29
+
30
+ // ============================================================================
31
+ // 类型
32
+ // ============================================================================
33
+
34
+ interface ProfileRecord {
35
+ id?: number;
36
+ user_id: string;
37
+ key: string;
38
+ value: string;
39
+ updated_at: number;
40
+ }
41
+
42
+ /**
43
+ * 数据库模型接口(与 RelatedModel 的链式查询 API 对齐)
44
+ */
45
+ interface DbModel {
46
+ select(...fields: string[]): any; // 返回 Selection (thenable, 支持 .where())
47
+ create(data: Record<string, any>): Promise<any>;
48
+ update(data: Partial<any>): any; // 返回 Updation (thenable, 支持 .where())
49
+ delete(condition: Record<string, any>): any; // 返回 Deletion (thenable, 支持 .where())
50
+ }
51
+
52
+ // ============================================================================
53
+ // Store 接口
54
+ // ============================================================================
55
+
56
+ interface IProfileStore {
57
+ get(userId: string, key: string): Promise<string | null>;
58
+ getAll(userId: string): Promise<Record<string, string>>;
59
+ set(userId: string, key: string, value: string): Promise<void>;
60
+ delete(userId: string, key: string): Promise<boolean>;
61
+ dispose(): void;
62
+ }
63
+
64
+ // ============================================================================
65
+ // 内存实现
66
+ // ============================================================================
67
+
68
+ class MemoryProfileStore implements IProfileStore {
69
+ private data: Map<string, Map<string, string>> = new Map();
70
+
71
+ async get(userId: string, key: string): Promise<string | null> {
72
+ return this.data.get(userId)?.get(key) ?? null;
73
+ }
74
+
75
+ async getAll(userId: string): Promise<Record<string, string>> {
76
+ const map = this.data.get(userId);
77
+ if (!map) return {};
78
+ return Object.fromEntries(map);
79
+ }
80
+
81
+ async set(userId: string, key: string, value: string): Promise<void> {
82
+ let map = this.data.get(userId);
83
+ if (!map) { map = new Map(); this.data.set(userId, map); }
84
+ map.set(key, value);
85
+ }
86
+
87
+ async delete(userId: string, key: string): Promise<boolean> {
88
+ return this.data.get(userId)?.delete(key) ?? false;
89
+ }
90
+
91
+ dispose(): void { this.data.clear(); }
92
+ }
93
+
94
+ // ============================================================================
95
+ // 数据库实现
96
+ // ============================================================================
97
+
98
+ class DatabaseProfileStore implements IProfileStore {
99
+ constructor(private model: DbModel) {}
100
+
101
+ async get(userId: string, key: string): Promise<string | null> {
102
+ const records = await this.model.select().where({ user_id: userId, key }) as ProfileRecord[];
103
+ return records.length > 0 ? records[0].value : null;
104
+ }
105
+
106
+ async getAll(userId: string): Promise<Record<string, string>> {
107
+ const records = await this.model.select().where({ user_id: userId }) as ProfileRecord[];
108
+ const result: Record<string, string> = {};
109
+ for (const r of records) result[r.key] = r.value;
110
+ return result;
111
+ }
112
+
113
+ async set(userId: string, key: string, value: string): Promise<void> {
114
+ const existing = await this.model.select().where({ user_id: userId, key });
115
+ if (existing.length > 0) {
116
+ await this.model.update({ value, updated_at: Date.now() }).where({ user_id: userId, key });
117
+ } else {
118
+ await this.model.create({ user_id: userId, key, value, updated_at: Date.now() });
119
+ }
120
+ }
121
+
122
+ async delete(userId: string, key: string): Promise<boolean> {
123
+ const existing = await this.model.select().where({ user_id: userId, key });
124
+ if (existing.length === 0) return false;
125
+ await this.model.delete({ user_id: userId, key });
126
+ return true;
127
+ }
128
+
129
+ dispose(): void {}
130
+ }
131
+
132
+ // ============================================================================
133
+ // UserProfileStore
134
+ // ============================================================================
135
+
136
+ export class UserProfileStore {
137
+ private store: IProfileStore;
138
+
139
+ constructor() {
140
+ this.store = new MemoryProfileStore();
141
+ }
142
+
143
+ upgradeToDatabase(model: DbModel): void {
144
+ const old = this.store;
145
+ this.store = new DatabaseProfileStore(model);
146
+ old.dispose();
147
+ logger.debug('UserProfileStore: upgraded to database storage');
148
+ }
149
+
150
+ async get(userId: string, key: string): Promise<string | null> {
151
+ return this.store.get(userId, key);
152
+ }
153
+
154
+ async getAll(userId: string): Promise<Record<string, string>> {
155
+ return this.store.getAll(userId);
156
+ }
157
+
158
+ async set(userId: string, key: string, value: string): Promise<void> {
159
+ return this.store.set(userId, key, value);
160
+ }
161
+
162
+ async delete(userId: string, key: string): Promise<boolean> {
163
+ return this.store.delete(userId, key);
164
+ }
165
+
166
+ /**
167
+ * 构建用户画像摘要,注入 system prompt
168
+ */
169
+ async buildProfileSummary(userId: string): Promise<string> {
170
+ const profile = await this.store.getAll(userId);
171
+ const entries = Object.entries(profile);
172
+ if (entries.length === 0) return '';
173
+
174
+ const lines = entries.map(([k, v]) => `- ${k}: ${v}`).join('\n');
175
+ return `[用户画像]\n${lines}`;
176
+ }
177
+
178
+ dispose(): void {
179
+ this.store.dispose();
180
+ }
181
+ }