@zhin.js/ai 1.0.0 → 1.0.1

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 (94) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/agent.d.ts +54 -6
  3. package/lib/agent.d.ts.map +1 -1
  4. package/lib/agent.js +468 -116
  5. package/lib/agent.js.map +1 -1
  6. package/lib/compaction.d.ts +132 -0
  7. package/lib/compaction.d.ts.map +1 -0
  8. package/lib/compaction.js +370 -0
  9. package/lib/compaction.js.map +1 -0
  10. package/lib/context-manager.d.ts.map +1 -1
  11. package/lib/context-manager.js +10 -3
  12. package/lib/context-manager.js.map +1 -1
  13. package/lib/conversation-memory.d.ts +192 -0
  14. package/lib/conversation-memory.d.ts.map +1 -0
  15. package/lib/conversation-memory.js +619 -0
  16. package/lib/conversation-memory.js.map +1 -0
  17. package/lib/index.d.ts +25 -163
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +24 -1122
  20. package/lib/index.js.map +1 -1
  21. package/lib/output.d.ts +93 -0
  22. package/lib/output.d.ts.map +1 -0
  23. package/lib/output.js +176 -0
  24. package/lib/output.js.map +1 -0
  25. package/lib/providers/anthropic.d.ts +7 -0
  26. package/lib/providers/anthropic.d.ts.map +1 -1
  27. package/lib/providers/anthropic.js +5 -0
  28. package/lib/providers/anthropic.js.map +1 -1
  29. package/lib/providers/ollama.d.ts +10 -0
  30. package/lib/providers/ollama.d.ts.map +1 -1
  31. package/lib/providers/ollama.js +19 -4
  32. package/lib/providers/ollama.js.map +1 -1
  33. package/lib/providers/openai.d.ts +7 -0
  34. package/lib/providers/openai.d.ts.map +1 -1
  35. package/lib/providers/openai.js +11 -0
  36. package/lib/providers/openai.js.map +1 -1
  37. package/lib/rate-limiter.d.ts +38 -0
  38. package/lib/rate-limiter.d.ts.map +1 -0
  39. package/lib/rate-limiter.js +86 -0
  40. package/lib/rate-limiter.js.map +1 -0
  41. package/lib/session.d.ts +7 -0
  42. package/lib/session.d.ts.map +1 -1
  43. package/lib/session.js +47 -18
  44. package/lib/session.js.map +1 -1
  45. package/lib/storage.d.ts +68 -0
  46. package/lib/storage.d.ts.map +1 -0
  47. package/lib/storage.js +105 -0
  48. package/lib/storage.js.map +1 -0
  49. package/lib/tone-detector.d.ts +19 -0
  50. package/lib/tone-detector.d.ts.map +1 -0
  51. package/lib/tone-detector.js +72 -0
  52. package/lib/tone-detector.js.map +1 -0
  53. package/lib/types.d.ts +84 -8
  54. package/lib/types.d.ts.map +1 -1
  55. package/package.json +13 -42
  56. package/src/agent.ts +518 -135
  57. package/src/compaction.ts +529 -0
  58. package/src/context-manager.ts +10 -9
  59. package/src/conversation-memory.ts +816 -0
  60. package/src/index.ts +121 -1406
  61. package/src/output.ts +261 -0
  62. package/src/providers/anthropic.ts +4 -0
  63. package/src/providers/ollama.ts +23 -4
  64. package/src/providers/openai.ts +8 -1
  65. package/src/rate-limiter.ts +129 -0
  66. package/src/session.ts +47 -18
  67. package/src/storage.ts +135 -0
  68. package/src/tone-detector.ts +89 -0
  69. package/src/types.ts +95 -6
  70. package/tests/agent.test.ts +123 -70
  71. package/tests/compaction.test.ts +310 -0
  72. package/tests/context-manager.test.ts +73 -47
  73. package/tests/conversation-memory.test.ts +128 -0
  74. package/tests/output.test.ts +128 -0
  75. package/tests/providers.test.ts +574 -0
  76. package/tests/rate-limiter.test.ts +108 -0
  77. package/tests/session.test.ts +139 -48
  78. package/tests/setup.ts +82 -240
  79. package/tests/storage.test.ts +224 -0
  80. package/tests/tone-detector.test.ts +80 -0
  81. package/tsconfig.json +4 -5
  82. package/vitest.setup.ts +1 -0
  83. package/README.md +0 -564
  84. package/TOOLS.md +0 -294
  85. package/lib/tools.d.ts +0 -45
  86. package/lib/tools.d.ts.map +0 -1
  87. package/lib/tools.js +0 -194
  88. package/lib/tools.js.map +0 -1
  89. package/src/tools.ts +0 -205
  90. package/tests/ai-trigger.test.ts +0 -369
  91. package/tests/integration.test.ts +0 -596
  92. package/tests/providers.integration.test.ts +0 -227
  93. package/tests/tool.test.ts +0 -800
  94. package/tests/tools-builtin.test.ts +0 -346
package/src/tools.ts DELETED
@@ -1,205 +0,0 @@
1
- /**
2
- * @zhin.js/ai - Built-in Tools
3
- * 内置工具集合 - 使用 ZhinTool 类定义
4
- */
5
-
6
- import { ZhinTool, type Tool } from '@zhin.js/core';
7
-
8
- /**
9
- * 计算器工具
10
- * 支持基本运算和数学函数
11
- */
12
- export const calculatorTool = new ZhinTool('calculator')
13
- .desc('执行数学计算。支持基本运算(+、-、*、/、^)和数学函数(sin、cos、sqrt 等)')
14
- .param('expression', { type: 'string', description: '数学表达式,例如 "2 + 3 * 4" 或 "sqrt(16)"' }, true)
15
- .execute(async ({ expression }) => {
16
- try {
17
- // 安全的数学表达式求值
18
- const sanitized = (expression as string)
19
- .replace(/[^0-9+\-*/().^eEsincosqrtabspowlogxMathPI\s]/g, '')
20
- .replace(/\bsqrt\b/g, 'Math.sqrt')
21
- .replace(/\bsin\b/g, 'Math.sin')
22
- .replace(/\bcos\b/g, 'Math.cos')
23
- .replace(/\btan\b/g, 'Math.tan')
24
- .replace(/\blog\b/g, 'Math.log')
25
- .replace(/\babs\b/g, 'Math.abs')
26
- .replace(/\bpow\b/g, 'Math.pow')
27
- .replace(/\bPI\b/g, 'Math.PI')
28
- .replace(/\bE\b(?![0-9])/g, 'Math.E')
29
- .replace(/\^/g, '**');
30
-
31
- const result = new Function(`return ${sanitized}`)();
32
- return { result, expression };
33
- } catch (error) {
34
- return { error: '无法计算表达式', expression };
35
- }
36
- });
37
-
38
- /**
39
- * 时间工具
40
- * 获取当前时间和日期
41
- */
42
- export const timeTool = new ZhinTool('get_time')
43
- .desc('获取当前时间和日期信息')
44
- .param('timezone', { type: 'string', description: '时区,例如 "Asia/Shanghai" 或 "UTC"' })
45
- .param('format', { type: 'string', description: '输出格式: full(完整), date(日期), time(时间), timestamp(时间戳)' })
46
- .execute(async ({ timezone, format = 'full' }) => {
47
- const now = new Date();
48
- const options: Intl.DateTimeFormatOptions = {
49
- timeZone: (timezone as string) || 'Asia/Shanghai',
50
- };
51
-
52
- switch (format) {
53
- case 'date':
54
- options.dateStyle = 'full';
55
- break;
56
- case 'time':
57
- options.timeStyle = 'long';
58
- break;
59
- case 'timestamp':
60
- return { timestamp: now.getTime(), iso: now.toISOString() };
61
- default:
62
- options.dateStyle = 'full';
63
- options.timeStyle = 'long';
64
- }
65
-
66
- return {
67
- formatted: now.toLocaleString('zh-CN', options),
68
- timestamp: now.getTime(),
69
- iso: now.toISOString(),
70
- };
71
- });
72
-
73
- /**
74
- * 网页搜索工具
75
- * 需要配置搜索 API
76
- */
77
- export const searchTool = new ZhinTool('web_search')
78
- .desc('在互联网上搜索信息(需要配置搜索 API)')
79
- .param('query', { type: 'string', description: '搜索关键词' }, true)
80
- .param('limit', { type: 'number', description: '返回结果数量(默认 5)' })
81
- .execute(async ({ query, limit = 5 }) => {
82
- // 默认实现提示需要配置
83
- return {
84
- query,
85
- error: '搜索功能未配置,请提供搜索 API',
86
- hint: '可以集成 SerpAPI、Bing Search API 或 Google Custom Search',
87
- };
88
- });
89
-
90
- /**
91
- * 代码执行工具
92
- * 在安全沙箱中执行 JavaScript
93
- */
94
- export const codeRunnerTool = new ZhinTool('run_code')
95
- .desc('执行 JavaScript 代码(在安全沙箱中)')
96
- .param('code', { type: 'string', description: 'JavaScript 代码' }, true)
97
- .execute(async ({ code }) => {
98
- try {
99
- // 简单的沙箱执行(生产环境应使用 vm2 或 isolated-vm)
100
- const result = new Function(`
101
- 'use strict';
102
- const console = { log: (...args) => args.join(' ') };
103
- return (function() { ${code} })();
104
- `)();
105
-
106
- return {
107
- success: true,
108
- result: result !== undefined ? String(result) : 'undefined',
109
- };
110
- } catch (error) {
111
- return {
112
- success: false,
113
- error: error instanceof Error ? error.message : String(error),
114
- };
115
- }
116
- });
117
-
118
- /**
119
- * HTTP 请求工具
120
- * 发送 HTTP 请求获取数据
121
- */
122
- export const httpTool = new ZhinTool('http_request')
123
- .desc('发送 HTTP 请求获取数据')
124
- .param('url', { type: 'string', description: '请求 URL' }, true)
125
- .param('method', { type: 'string', description: 'HTTP 方法: GET, POST, PUT, DELETE(默认 GET)' })
126
- .param('headers', { type: 'object', description: '请求头(JSON 对象)' })
127
- .param('body', { type: 'string', description: '请求体(JSON 字符串)' })
128
- .execute(async ({ url, method = 'GET', headers = {}, body }) => {
129
- try {
130
- const response = await fetch(url as string, {
131
- method: method as string,
132
- headers: {
133
- 'Content-Type': 'application/json',
134
- ...(headers as Record<string, string>),
135
- },
136
- body: body ? (body as string) : undefined,
137
- });
138
-
139
- const contentType = response.headers.get('content-type') || '';
140
- let data: any;
141
-
142
- if (contentType.includes('application/json')) {
143
- data = await response.json();
144
- } else {
145
- data = await response.text();
146
- // 限制文本长度
147
- if (data.length > 5000) {
148
- data = data.substring(0, 5000) + '... (truncated)';
149
- }
150
- }
151
-
152
- return {
153
- status: response.status,
154
- statusText: response.statusText,
155
- data,
156
- };
157
- } catch (error) {
158
- return {
159
- error: error instanceof Error ? error.message : String(error),
160
- };
161
- }
162
- });
163
-
164
- /**
165
- * 记忆工具
166
- * 让 AI 记住重要信息
167
- */
168
- export const memoryTool = new ZhinTool('remember')
169
- .desc('记住用户告诉你的重要信息,以便后续对话中使用')
170
- .param('key', { type: 'string', description: '记忆的标识符,如 "user_name", "preference"' }, true)
171
- .param('value', { type: 'string', description: '要记住的内容' }, true)
172
- .execute(async ({ key, value }, context) => {
173
- // 这里需要与 session/context manager 集成
174
- return {
175
- success: true,
176
- message: `已记住 ${key}: ${value}`,
177
- key,
178
- value,
179
- };
180
- });
181
-
182
- /**
183
- * 获取所有内置工具(ZhinTool 实例)
184
- * 注意:天气工具已移除,请使用 weather-tool 插件,支持多平台配置
185
- */
186
- export function getBuiltinTools(): ZhinTool[] {
187
- return [
188
- calculatorTool,
189
- timeTool,
190
- ];
191
- }
192
-
193
- /**
194
- * 获取所有可用的内置工具(包括可选工具)
195
- */
196
- export function getAllBuiltinTools(): ZhinTool[] {
197
- return [
198
- calculatorTool,
199
- timeTool,
200
- searchTool,
201
- codeRunnerTool,
202
- httpTool,
203
- memoryTool,
204
- ];
205
- }
@@ -1,369 +0,0 @@
1
- /**
2
- * AI Trigger 工具函数测试
3
- *
4
- * 测试内容:
5
- * 1. 前缀触发检测
6
- * 2. @机器人触发检测
7
- * 3. 私聊直接对话检测
8
- * 4. 关键词触发检测
9
- * 5. 忽略前缀检测
10
- * 6. 权限推断
11
- */
12
- import { describe, it, expect, vi } from 'vitest';
13
- import {
14
- shouldTriggerAI,
15
- mergeAITriggerConfig,
16
- inferSenderPermissions,
17
- parseRichMediaContent,
18
- extractTextContent,
19
- DEFAULT_AI_TRIGGER_CONFIG,
20
- type AITriggerConfig,
21
- } from '@zhin.js/core';
22
-
23
- // 创建模拟消息
24
- function createMockMessage(options: {
25
- content: string | any[];
26
- bot?: string;
27
- channelType?: 'private' | 'group' | 'channel';
28
- senderId?: string;
29
- senderPermissions?: string[];
30
- senderRole?: string;
31
- }) {
32
- const content = typeof options.content === 'string'
33
- ? [{ type: 'text', data: { text: options.content } }]
34
- : options.content;
35
-
36
- return {
37
- $content: content,
38
- $bot: options.bot || 'bot123',
39
- $channel: options.channelType ? { type: options.channelType, id: 'channel1' } : null,
40
- $sender: {
41
- id: options.senderId || 'user1',
42
- permissions: options.senderPermissions || [],
43
- role: options.senderRole,
44
- },
45
- $adapter: 'test',
46
- $reply: vi.fn(),
47
- };
48
- }
49
-
50
- describe('AI Trigger 工具函数', () => {
51
- describe('shouldTriggerAI - 前缀触发', () => {
52
- it('应该检测 # 前缀', () => {
53
- const message = createMockMessage({ content: '# 你好' });
54
- const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
55
-
56
- expect(result.triggered).toBe(true);
57
- expect(result.content).toBe('你好');
58
- });
59
-
60
- it('应该检测 AI: 前缀', () => {
61
- const message = createMockMessage({ content: 'AI:帮我计算' });
62
- const result = shouldTriggerAI(message as any, { prefixes: ['AI:', 'ai:'] });
63
-
64
- expect(result.triggered).toBe(true);
65
- expect(result.content).toBe('帮我计算');
66
- });
67
-
68
- it('应该检测小写 ai: 前缀', () => {
69
- const message = createMockMessage({ content: 'ai:今天天气' });
70
- const result = shouldTriggerAI(message as any, { prefixes: ['AI:', 'ai:'] });
71
-
72
- expect(result.triggered).toBe(true);
73
- expect(result.content).toBe('今天天气');
74
- });
75
-
76
- it('没有匹配前缀时不应触发', () => {
77
- const message = createMockMessage({ content: '普通消息' });
78
- const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
79
-
80
- expect(result.triggered).toBe(false);
81
- });
82
- });
83
-
84
- describe('shouldTriggerAI - 忽略前缀', () => {
85
- it('应该忽略命令前缀 /', () => {
86
- const message = createMockMessage({
87
- content: '/help',
88
- channelType: 'private',
89
- });
90
- const result = shouldTriggerAI(message as any, {
91
- prefixes: ['#'],
92
- ignorePrefixes: ['/'],
93
- respondToPrivate: true,
94
- });
95
-
96
- expect(result.triggered).toBe(false);
97
- });
98
-
99
- it('应该忽略命令前缀 !', () => {
100
- const message = createMockMessage({
101
- content: '!command',
102
- channelType: 'private',
103
- });
104
- const result = shouldTriggerAI(message as any, {
105
- prefixes: ['#'],
106
- ignorePrefixes: ['!', '!'],
107
- respondToPrivate: true,
108
- });
109
-
110
- expect(result.triggered).toBe(false);
111
- });
112
- });
113
-
114
- describe('shouldTriggerAI - @机器人触发', () => {
115
- it('应该检测 @机器人', () => {
116
- const message = createMockMessage({
117
- content: [
118
- { type: 'at', data: { user_id: 'bot123' } },
119
- { type: 'text', data: { text: ' 你好呀' } },
120
- ],
121
- bot: 'bot123',
122
- });
123
- const result = shouldTriggerAI(message as any, { respondToAt: true });
124
-
125
- expect(result.triggered).toBe(true);
126
- expect(result.content).toBe(' 你好呀');
127
- });
128
-
129
- it('关闭 respondToAt 时不应触发', () => {
130
- const message = createMockMessage({
131
- content: [
132
- { type: 'at', data: { user_id: 'bot123' } },
133
- { type: 'text', data: { text: ' 你好' } },
134
- ],
135
- bot: 'bot123',
136
- });
137
- const result = shouldTriggerAI(message as any, { respondToAt: false });
138
-
139
- expect(result.triggered).toBe(false);
140
- });
141
-
142
- it('@其他人时不应触发', () => {
143
- const message = createMockMessage({
144
- content: [
145
- { type: 'at', data: { user_id: 'other_user' } },
146
- { type: 'text', data: { text: ' 你好' } },
147
- ],
148
- bot: 'bot123',
149
- });
150
- const result = shouldTriggerAI(message as any, { respondToAt: true });
151
-
152
- expect(result.triggered).toBe(false);
153
- });
154
- });
155
-
156
- describe('shouldTriggerAI - 私聊直接对话', () => {
157
- it('私聊应该直接触发', () => {
158
- const message = createMockMessage({
159
- content: '你好',
160
- channelType: 'private',
161
- });
162
- const result = shouldTriggerAI(message as any, { respondToPrivate: true });
163
-
164
- expect(result.triggered).toBe(true);
165
- expect(result.content).toBe('你好');
166
- });
167
-
168
- it('关闭 respondToPrivate 时私聊不应触发', () => {
169
- const message = createMockMessage({
170
- content: '你好',
171
- channelType: 'private',
172
- });
173
- const result = shouldTriggerAI(message as any, { respondToPrivate: false });
174
-
175
- expect(result.triggered).toBe(false);
176
- });
177
-
178
- it('群聊时不应直接触发(需要前缀或@)', () => {
179
- const message = createMockMessage({
180
- content: '你好',
181
- channelType: 'group',
182
- });
183
- const result = shouldTriggerAI(message as any, { respondToPrivate: true });
184
-
185
- expect(result.triggered).toBe(false);
186
- });
187
- });
188
-
189
- describe('shouldTriggerAI - 关键词触发', () => {
190
- it('应该检测关键词', () => {
191
- const message = createMockMessage({ content: '今天天气怎么样' });
192
- const result = shouldTriggerAI(message as any, { keywords: ['天气', '新闻'] });
193
-
194
- expect(result.triggered).toBe(true);
195
- expect(result.content).toBe('今天天气怎么样');
196
- });
197
-
198
- it('关键词不区分大小写', () => {
199
- const message = createMockMessage({ content: '说 hello 世界' });
200
- const result = shouldTriggerAI(message as any, { keywords: ['HELLO'] });
201
-
202
- expect(result.triggered).toBe(true);
203
- });
204
-
205
- it('没有关键词配置时不应触发', () => {
206
- const message = createMockMessage({ content: '天气真好' });
207
- const result = shouldTriggerAI(message as any, { keywords: [] });
208
-
209
- expect(result.triggered).toBe(false);
210
- });
211
- });
212
-
213
- describe('shouldTriggerAI - 禁用状态', () => {
214
- it('enabled 为 false 时不应触发', () => {
215
- const message = createMockMessage({ content: '# 你好' });
216
- const result = shouldTriggerAI(message as any, {
217
- enabled: false,
218
- prefixes: ['#'],
219
- });
220
-
221
- expect(result.triggered).toBe(false);
222
- });
223
- });
224
-
225
- describe('触发优先级', () => {
226
- it('前缀触发应该优先于私聊触发', () => {
227
- const message = createMockMessage({
228
- content: '# 命令内容',
229
- channelType: 'private',
230
- });
231
- const result = shouldTriggerAI(message as any, {
232
- prefixes: ['#'],
233
- respondToPrivate: true,
234
- });
235
-
236
- expect(result.triggered).toBe(true);
237
- expect(result.content).toBe('命令内容'); // 应该去掉前缀
238
- });
239
- });
240
-
241
- describe('边界情况', () => {
242
- it('空消息不应触发', () => {
243
- const message = createMockMessage({
244
- content: '',
245
- channelType: 'private',
246
- });
247
- const result = shouldTriggerAI(message as any, { respondToPrivate: true });
248
-
249
- expect(result.triggered).toBe(false);
250
- });
251
-
252
- it('只有空格的消息不应触发', () => {
253
- const message = createMockMessage({
254
- content: ' ',
255
- channelType: 'private',
256
- });
257
- const result = shouldTriggerAI(message as any, { respondToPrivate: true });
258
-
259
- expect(result.triggered).toBe(false);
260
- });
261
-
262
- it('前缀后没有内容时应该触发但内容为空', () => {
263
- const message = createMockMessage({ content: '#' });
264
- const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
265
-
266
- expect(result.triggered).toBe(true);
267
- expect(result.content).toBe('');
268
- });
269
- });
270
-
271
- describe('mergeAITriggerConfig', () => {
272
- it('应该合并配置', () => {
273
- const config = mergeAITriggerConfig({ prefixes: ['##'] });
274
-
275
- expect(config.prefixes).toEqual(['##']);
276
- expect(config.enabled).toBe(true);
277
- expect(config.respondToAt).toBe(true);
278
- });
279
-
280
- it('应该使用默认值', () => {
281
- const config = mergeAITriggerConfig({});
282
-
283
- expect(config).toEqual(DEFAULT_AI_TRIGGER_CONFIG);
284
- });
285
- });
286
-
287
- describe('inferSenderPermissions', () => {
288
- it('应该推断 owner 权限', () => {
289
- const message = createMockMessage({
290
- content: 'test',
291
- senderId: 'owner1',
292
- });
293
- const result = inferSenderPermissions(message as any, { owners: ['owner1'] });
294
-
295
- expect(result.isOwner).toBe(true);
296
- expect(result.permissionLevel).toBe('owner');
297
- });
298
-
299
- it('应该推断 bot_admin 权限', () => {
300
- const message = createMockMessage({
301
- content: 'test',
302
- senderId: 'admin1',
303
- });
304
- const result = inferSenderPermissions(message as any, { botAdmins: ['admin1'] });
305
-
306
- expect(result.isBotAdmin).toBe(true);
307
- expect(result.permissionLevel).toBe('bot_admin');
308
- });
309
-
310
- it('应该推断 group_owner 权限', () => {
311
- const message = createMockMessage({
312
- content: 'test',
313
- senderPermissions: ['owner'],
314
- });
315
- const result = inferSenderPermissions(message as any, {});
316
-
317
- expect(result.isGroupOwner).toBe(true);
318
- expect(result.permissionLevel).toBe('group_owner');
319
- });
320
-
321
- it('应该推断 group_admin 权限', () => {
322
- const message = createMockMessage({
323
- content: 'test',
324
- senderPermissions: ['admin'],
325
- });
326
- const result = inferSenderPermissions(message as any, {});
327
-
328
- expect(result.isGroupAdmin).toBe(true);
329
- expect(result.permissionLevel).toBe('group_admin');
330
- });
331
-
332
- it('默认应该是 user 权限', () => {
333
- const message = createMockMessage({ content: 'test' });
334
- const result = inferSenderPermissions(message as any, {});
335
-
336
- expect(result.permissionLevel).toBe('user');
337
- });
338
-
339
- it('应该推断 scope', () => {
340
- const privateMsg = createMockMessage({ content: 'test', channelType: 'private' });
341
- const groupMsg = createMockMessage({ content: 'test', channelType: 'group' });
342
-
343
- expect(inferSenderPermissions(privateMsg as any, {}).scope).toBe('private');
344
- expect(inferSenderPermissions(groupMsg as any, {}).scope).toBe('group');
345
- });
346
- });
347
-
348
- describe('parseRichMediaContent', () => {
349
- it('应该解析纯文本', () => {
350
- const result = parseRichMediaContent('Hello World');
351
-
352
- expect(result.length).toBeGreaterThan(0);
353
- const textElement = result.find(el => el.type === 'text');
354
- expect(textElement?.data?.text).toContain('Hello');
355
- });
356
-
357
- it('应该处理 XML 标签', () => {
358
- const result = parseRichMediaContent('文本<image url="test.jpg"/>');
359
-
360
- expect(result.length).toBeGreaterThan(0);
361
- });
362
-
363
- it('应该处理空字符串', () => {
364
- const result = parseRichMediaContent('');
365
-
366
- expect(Array.isArray(result)).toBe(true);
367
- });
368
- });
369
- });