@lobehub/lobehub 2.0.0-next.335 → 2.0.0-next.337
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 +59 -0
- package/changelog/v1.json +21 -0
- package/package.json +1 -1
- package/packages/builtin-tool-agent-builder/src/manifest.ts +0 -2
- package/packages/builtin-tool-group-management/src/manifest.ts +54 -53
- package/packages/builtin-tool-group-management/src/systemRole.ts +43 -111
- package/packages/builtin-tool-memory/src/client/Render/AddPreferenceMemory/index.tsx +17 -0
- package/packages/builtin-tool-memory/src/client/Render/index.ts +2 -0
- package/packages/builtin-tool-memory/src/client/Streaming/AddPreferenceMemory/index.tsx +17 -0
- package/packages/builtin-tool-memory/src/client/Streaming/index.ts +4 -3
- package/packages/builtin-tool-memory/src/client/components/PreferenceMemoryCard.tsx +357 -0
- package/packages/builtin-tool-memory/src/client/components/index.ts +1 -0
- package/packages/builtin-tool-memory/src/executor/index.ts +3 -3
- package/packages/builtin-tool-memory/src/systemRole.ts +1 -0
- package/packages/context-engine/src/engine/tools/ToolArgumentsRepairer.ts +129 -0
- package/packages/context-engine/src/engine/tools/__tests__/ToolArgumentsRepairer.test.ts +186 -0
- package/packages/context-engine/src/engine/tools/index.ts +3 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/index.ts +2 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-assistant-group.json +156 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +22 -0
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +88 -11
- package/packages/database/src/models/userMemory/model.ts +1 -1
- package/packages/memory-user-memory/src/extractors/context.test.ts +0 -1
- package/packages/memory-user-memory/src/extractors/experience.test.ts +0 -1
- package/packages/memory-user-memory/src/extractors/identity.test.ts +0 -1
- package/packages/memory-user-memory/src/extractors/preference.test.ts +0 -1
- package/packages/memory-user-memory/src/schemas/context.ts +0 -2
- package/packages/memory-user-memory/src/schemas/experience.ts +0 -2
- package/packages/memory-user-memory/src/schemas/identity.ts +1 -2
- package/packages/memory-user-memory/src/schemas/preference.ts +0 -2
- package/packages/types/src/openai/chat.ts +0 -4
- package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +5 -1
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +8 -8
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +142 -15
- package/src/app/[variants]/(main)/community/(detail)/user/features/useUserDetail.ts +45 -20
- package/src/server/routers/lambda/market/agentGroup.ts +179 -1
- package/src/server/routers/lambda/userMemories/tools.ts +5 -4
- package/src/server/routers/lambda/userMemories.ts +4 -4
- package/src/server/services/discover/index.ts +4 -0
- package/src/server/services/memory/userMemory/extract.ts +3 -3
- package/src/services/chat/chat.test.ts +109 -104
- package/src/services/chat/index.ts +13 -32
- package/src/services/chat/mecha/agentConfigResolver.test.ts +113 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +15 -5
- package/src/services/marketApi.ts +14 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +13 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +5 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +14 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +131 -7
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +61 -62
- package/src/store/chat/slices/plugin/action.test.ts +71 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +14 -5
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Accordion, AccordionItem, Avatar, Flexbox, Tag, Text } from '@lobehub/ui';
|
|
4
|
+
import { Steps } from 'antd';
|
|
5
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
|
|
8
|
+
import BubblesLoading from '@/components/BubblesLoading';
|
|
9
|
+
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
10
|
+
import StreamingMarkdown from '@/components/StreamingMarkdown';
|
|
11
|
+
import { highlightTextStyles } from '@/styles';
|
|
12
|
+
|
|
13
|
+
import type { AddPreferenceMemoryParams } from '../../types';
|
|
14
|
+
|
|
15
|
+
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
16
|
+
container: css`
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
|
|
19
|
+
width: 100%;
|
|
20
|
+
border: 1px solid ${cssVar.colorBorderSecondary};
|
|
21
|
+
border-radius: 16px;
|
|
22
|
+
|
|
23
|
+
background: ${cssVar.colorBgContainer};
|
|
24
|
+
`,
|
|
25
|
+
content: css`
|
|
26
|
+
padding-block: 12px;
|
|
27
|
+
padding-inline: 16px;
|
|
28
|
+
`,
|
|
29
|
+
detail: css`
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
line-height: 1.6;
|
|
32
|
+
color: ${cssVar.colorTextSecondary};
|
|
33
|
+
`,
|
|
34
|
+
directive: css`
|
|
35
|
+
font-size: 14px;
|
|
36
|
+
line-height: 1.6;
|
|
37
|
+
color: ${cssVar.colorText};
|
|
38
|
+
`,
|
|
39
|
+
header: css`
|
|
40
|
+
padding-block: 10px;
|
|
41
|
+
padding-inline: 12px;
|
|
42
|
+
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
|
|
43
|
+
`,
|
|
44
|
+
section: css`
|
|
45
|
+
padding: 4px;
|
|
46
|
+
border-block-start: 1px solid ${cssVar.colorBorderSecondary};
|
|
47
|
+
`,
|
|
48
|
+
stepContent: css`
|
|
49
|
+
font-size: 13px;
|
|
50
|
+
line-height: 1.6;
|
|
51
|
+
color: ${cssVar.colorTextSecondary};
|
|
52
|
+
white-space: pre-wrap;
|
|
53
|
+
`,
|
|
54
|
+
stepsContainer: css`
|
|
55
|
+
.ant-steps-item-content {
|
|
56
|
+
min-height: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ant-steps-item-description {
|
|
60
|
+
padding-block-end: 12px !important;
|
|
61
|
+
}
|
|
62
|
+
`,
|
|
63
|
+
suggestion: css`
|
|
64
|
+
padding-block: 8px;
|
|
65
|
+
padding-inline: 12px;
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
|
|
68
|
+
font-size: 13px;
|
|
69
|
+
line-height: 1.5;
|
|
70
|
+
color: ${cssVar.colorTextSecondary};
|
|
71
|
+
|
|
72
|
+
background: ${cssVar.colorFillQuaternary};
|
|
73
|
+
`,
|
|
74
|
+
summary: css`
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
font-weight: 500;
|
|
77
|
+
color: ${cssVar.colorTextSecondary};
|
|
78
|
+
`,
|
|
79
|
+
tags: css`
|
|
80
|
+
padding-block-start: 8px;
|
|
81
|
+
border-block-start: 1px dashed ${cssVar.colorBorderSecondary};
|
|
82
|
+
`,
|
|
83
|
+
title: css`
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
display: -webkit-box;
|
|
86
|
+
-webkit-box-orient: vertical;
|
|
87
|
+
-webkit-line-clamp: 1;
|
|
88
|
+
|
|
89
|
+
font-weight: 500;
|
|
90
|
+
color: ${cssVar.colorText};
|
|
91
|
+
`,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
export interface PreferenceMemoryCardProps {
|
|
95
|
+
data?: AddPreferenceMemoryParams;
|
|
96
|
+
loading?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const PreferenceMemoryCard = memo<PreferenceMemoryCardProps>(({ data, loading }) => {
|
|
100
|
+
const { summary, details, tags, title, withPreference } = data || {};
|
|
101
|
+
const { conclusionDirectives, originContext, appContext, suggestions, type } =
|
|
102
|
+
withPreference || {};
|
|
103
|
+
|
|
104
|
+
const hasContextContent =
|
|
105
|
+
originContext?.actor ||
|
|
106
|
+
originContext?.scenario ||
|
|
107
|
+
originContext?.trigger ||
|
|
108
|
+
originContext?.applicableWhen ||
|
|
109
|
+
originContext?.notApplicableWhen;
|
|
110
|
+
|
|
111
|
+
const hasAppContext = appContext?.app || appContext?.feature || appContext?.surface;
|
|
112
|
+
|
|
113
|
+
const hasSuggestions = suggestions && suggestions.length > 0;
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
!summary &&
|
|
117
|
+
!details &&
|
|
118
|
+
!tags?.length &&
|
|
119
|
+
!title &&
|
|
120
|
+
!conclusionDirectives &&
|
|
121
|
+
!hasContextContent &&
|
|
122
|
+
!hasSuggestions
|
|
123
|
+
)
|
|
124
|
+
return null;
|
|
125
|
+
|
|
126
|
+
const contextItems = [
|
|
127
|
+
{ avatar: '👤', content: originContext?.actor, title: 'Actor' },
|
|
128
|
+
{ avatar: '🎯', content: originContext?.scenario, title: 'Scenario' },
|
|
129
|
+
{ avatar: '⚡', content: originContext?.trigger, title: 'Trigger' },
|
|
130
|
+
{ avatar: '✅', content: originContext?.applicableWhen, title: 'Applicable When' },
|
|
131
|
+
{ avatar: '❌', content: originContext?.notApplicableWhen, title: 'Not Applicable When' },
|
|
132
|
+
].filter((item) => item.content);
|
|
133
|
+
|
|
134
|
+
const appContextItems = [
|
|
135
|
+
{ avatar: '📱', content: appContext?.app, title: 'App' },
|
|
136
|
+
{ avatar: '🔧', content: appContext?.feature, title: 'Feature' },
|
|
137
|
+
{ avatar: '📍', content: appContext?.surface, title: 'Surface' },
|
|
138
|
+
].filter((item) => item.content);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Flexbox className={styles.container}>
|
|
142
|
+
{/* Header */}
|
|
143
|
+
<Flexbox align={'center'} className={styles.header} gap={8} horizontal>
|
|
144
|
+
<Flexbox flex={1}>
|
|
145
|
+
<div className={styles.title}>{title || 'Preference Memory'}</div>
|
|
146
|
+
</Flexbox>
|
|
147
|
+
{type && <Tag>{type}</Tag>}
|
|
148
|
+
{loading && <NeuralNetworkLoading size={20} />}
|
|
149
|
+
</Flexbox>
|
|
150
|
+
|
|
151
|
+
{/* When has context content: collapse summary */}
|
|
152
|
+
{hasContextContent || hasAppContext ? (
|
|
153
|
+
<>
|
|
154
|
+
{/* Collapsed Summary */}
|
|
155
|
+
{(summary || tags?.length) && (
|
|
156
|
+
<Accordion gap={0}>
|
|
157
|
+
<AccordionItem
|
|
158
|
+
itemKey="summary"
|
|
159
|
+
paddingBlock={8}
|
|
160
|
+
paddingInline={8}
|
|
161
|
+
styles={{
|
|
162
|
+
base: { marginBlock: 4, marginInline: 4 },
|
|
163
|
+
}}
|
|
164
|
+
title={
|
|
165
|
+
<Text fontSize={12} type={'secondary'} weight={500}>
|
|
166
|
+
Summary
|
|
167
|
+
</Text>
|
|
168
|
+
}
|
|
169
|
+
>
|
|
170
|
+
<Flexbox gap={8} paddingBlock={'8px 12px'} paddingInline={8}>
|
|
171
|
+
{summary && <div className={styles.summary}>{summary}</div>}
|
|
172
|
+
{details && <div className={styles.detail}>{details}</div>}
|
|
173
|
+
{tags && tags.length > 0 && (
|
|
174
|
+
<Flexbox className={styles.tags} gap={8} horizontal wrap={'wrap'}>
|
|
175
|
+
{tags.map((tag, index) => (
|
|
176
|
+
<Tag key={index}>{tag}</Tag>
|
|
177
|
+
))}
|
|
178
|
+
</Flexbox>
|
|
179
|
+
)}
|
|
180
|
+
</Flexbox>
|
|
181
|
+
</AccordionItem>
|
|
182
|
+
</Accordion>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Origin Context Steps */}
|
|
186
|
+
{hasContextContent && (
|
|
187
|
+
<Accordion className={styles.section} defaultExpandedKeys={['context']} gap={0}>
|
|
188
|
+
<AccordionItem
|
|
189
|
+
itemKey="context"
|
|
190
|
+
paddingBlock={8}
|
|
191
|
+
paddingInline={8}
|
|
192
|
+
title={
|
|
193
|
+
<Text fontSize={12} type={'secondary'} weight={500}>
|
|
194
|
+
Origin Context
|
|
195
|
+
</Text>
|
|
196
|
+
}
|
|
197
|
+
>
|
|
198
|
+
<Flexbox paddingBlock={'8px 12px'} paddingInline={8}>
|
|
199
|
+
<Steps
|
|
200
|
+
className={styles.stepsContainer}
|
|
201
|
+
current={null as any}
|
|
202
|
+
direction="vertical"
|
|
203
|
+
items={contextItems.map((item) => ({
|
|
204
|
+
description: <div className={styles.stepContent}>{item.content}</div>,
|
|
205
|
+
icon: (
|
|
206
|
+
<Avatar
|
|
207
|
+
avatar={item.avatar}
|
|
208
|
+
shadow
|
|
209
|
+
shape={'square'}
|
|
210
|
+
size={20}
|
|
211
|
+
style={{
|
|
212
|
+
border: `1px solid ${cssVar.colorBorderSecondary}`,
|
|
213
|
+
fontSize: 11,
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
),
|
|
217
|
+
title: (
|
|
218
|
+
<Text as={'span'} fontSize={12} type={'secondary'} weight={500}>
|
|
219
|
+
{item.title}
|
|
220
|
+
</Text>
|
|
221
|
+
),
|
|
222
|
+
}))}
|
|
223
|
+
size="small"
|
|
224
|
+
/>
|
|
225
|
+
</Flexbox>
|
|
226
|
+
</AccordionItem>
|
|
227
|
+
</Accordion>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{/* App Context */}
|
|
231
|
+
{hasAppContext && (
|
|
232
|
+
<Accordion className={styles.section} gap={0}>
|
|
233
|
+
<AccordionItem
|
|
234
|
+
itemKey="appContext"
|
|
235
|
+
paddingBlock={8}
|
|
236
|
+
paddingInline={8}
|
|
237
|
+
title={
|
|
238
|
+
<Text fontSize={12} type={'secondary'} weight={500}>
|
|
239
|
+
App Context
|
|
240
|
+
</Text>
|
|
241
|
+
}
|
|
242
|
+
>
|
|
243
|
+
<Flexbox paddingBlock={'8px 12px'} paddingInline={8}>
|
|
244
|
+
<Steps
|
|
245
|
+
className={styles.stepsContainer}
|
|
246
|
+
current={null as any}
|
|
247
|
+
direction="vertical"
|
|
248
|
+
items={appContextItems.map((item) => ({
|
|
249
|
+
description: <div className={styles.stepContent}>{item.content}</div>,
|
|
250
|
+
icon: (
|
|
251
|
+
<Avatar
|
|
252
|
+
avatar={item.avatar}
|
|
253
|
+
shadow
|
|
254
|
+
shape={'square'}
|
|
255
|
+
size={20}
|
|
256
|
+
style={{
|
|
257
|
+
border: `1px solid ${cssVar.colorBorderSecondary}`,
|
|
258
|
+
fontSize: 11,
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
),
|
|
262
|
+
title: (
|
|
263
|
+
<Text as={'span'} fontSize={12} type={'secondary'} weight={500}>
|
|
264
|
+
{item.title}
|
|
265
|
+
</Text>
|
|
266
|
+
),
|
|
267
|
+
}))}
|
|
268
|
+
size="small"
|
|
269
|
+
/>
|
|
270
|
+
</Flexbox>
|
|
271
|
+
</AccordionItem>
|
|
272
|
+
</Accordion>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
{/* Conclusion Directive */}
|
|
276
|
+
{conclusionDirectives && (
|
|
277
|
+
<Flexbox
|
|
278
|
+
className={styles.section}
|
|
279
|
+
gap={8}
|
|
280
|
+
style={{ paddingBlock: 16, paddingInline: 12 }}
|
|
281
|
+
>
|
|
282
|
+
<Text fontSize={12} weight={500}>
|
|
283
|
+
<span className={highlightTextStyles.primary}>Directive</span>
|
|
284
|
+
</Text>
|
|
285
|
+
<div className={styles.directive}>{conclusionDirectives}</div>
|
|
286
|
+
</Flexbox>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
{/* Suggestions */}
|
|
290
|
+
{hasSuggestions && (
|
|
291
|
+
<Flexbox
|
|
292
|
+
className={styles.section}
|
|
293
|
+
gap={8}
|
|
294
|
+
style={{ paddingBlock: 16, paddingInline: 12 }}
|
|
295
|
+
>
|
|
296
|
+
<Text fontSize={12} weight={500}>
|
|
297
|
+
<span className={highlightTextStyles.info}>Suggestions</span>
|
|
298
|
+
</Text>
|
|
299
|
+
<Flexbox gap={8}>
|
|
300
|
+
{suggestions.map((suggestion, index) => (
|
|
301
|
+
<div className={styles.suggestion} key={index}>
|
|
302
|
+
{suggestion}
|
|
303
|
+
</div>
|
|
304
|
+
))}
|
|
305
|
+
</Flexbox>
|
|
306
|
+
</Flexbox>
|
|
307
|
+
)}
|
|
308
|
+
</>
|
|
309
|
+
) : (
|
|
310
|
+
/* When no context content: show summary and details */
|
|
311
|
+
<Flexbox className={styles.content} gap={8}>
|
|
312
|
+
{!summary && loading ? (
|
|
313
|
+
<BubblesLoading />
|
|
314
|
+
) : (
|
|
315
|
+
<>
|
|
316
|
+
{summary && <div className={styles.summary}>{summary}</div>}
|
|
317
|
+
{details && <StreamingMarkdown>{details}</StreamingMarkdown>}
|
|
318
|
+
{conclusionDirectives && (
|
|
319
|
+
<Flexbox gap={4} paddingBlock={8}>
|
|
320
|
+
<Text fontSize={12} weight={500}>
|
|
321
|
+
<span className={highlightTextStyles.primary}>Directive</span>
|
|
322
|
+
</Text>
|
|
323
|
+
<div className={styles.directive}>{conclusionDirectives}</div>
|
|
324
|
+
</Flexbox>
|
|
325
|
+
)}
|
|
326
|
+
{hasSuggestions && (
|
|
327
|
+
<Flexbox gap={8} paddingBlock={8}>
|
|
328
|
+
<Text fontSize={12} weight={500}>
|
|
329
|
+
<span className={highlightTextStyles.info}>Suggestions</span>
|
|
330
|
+
</Text>
|
|
331
|
+
<Flexbox gap={8}>
|
|
332
|
+
{suggestions.map((suggestion, index) => (
|
|
333
|
+
<div className={styles.suggestion} key={index}>
|
|
334
|
+
{suggestion}
|
|
335
|
+
</div>
|
|
336
|
+
))}
|
|
337
|
+
</Flexbox>
|
|
338
|
+
</Flexbox>
|
|
339
|
+
)}
|
|
340
|
+
{tags && tags.length > 0 && (
|
|
341
|
+
<Flexbox className={styles.tags} gap={8} horizontal wrap={'wrap'}>
|
|
342
|
+
{tags.map((tag, index) => (
|
|
343
|
+
<Tag key={index}>{tag}</Tag>
|
|
344
|
+
))}
|
|
345
|
+
</Flexbox>
|
|
346
|
+
)}
|
|
347
|
+
</>
|
|
348
|
+
)}
|
|
349
|
+
</Flexbox>
|
|
350
|
+
)}
|
|
351
|
+
</Flexbox>
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
PreferenceMemoryCard.displayName = 'PreferenceMemoryCard';
|
|
356
|
+
|
|
357
|
+
export default PreferenceMemoryCard;
|
|
@@ -74,7 +74,7 @@ class MemoryExecutor extends BaseExecutor<typeof MemoryApiName> {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
return {
|
|
77
|
-
content:
|
|
77
|
+
content: `Context memory "${params.title}" saved with memoryId: "${result.memoryId}" and contextId: "${result.contextId}"`,
|
|
78
78
|
state: { contextId: result.contextId, memoryId: result.memoryId },
|
|
79
79
|
success: true,
|
|
80
80
|
};
|
|
@@ -151,7 +151,7 @@ class MemoryExecutor extends BaseExecutor<typeof MemoryApiName> {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
return {
|
|
154
|
-
content:
|
|
154
|
+
content: `Identity memory "${params.title}" saved with memoryId: "${result.memoryId}" and identityId: "${result.identityId}"`,
|
|
155
155
|
state: { identityId: result.identityId, memoryId: result.memoryId },
|
|
156
156
|
success: true,
|
|
157
157
|
};
|
|
@@ -189,7 +189,7 @@ class MemoryExecutor extends BaseExecutor<typeof MemoryApiName> {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
return {
|
|
192
|
-
content:
|
|
192
|
+
content: `Preference memory "${params.title}" saved with memoryId: "${result.memoryId}" and preferenceId: "${result.preferenceId}"`,
|
|
193
193
|
state: { memoryId: result.memoryId, preferenceId: result.preferenceId },
|
|
194
194
|
success: true,
|
|
195
195
|
};
|
|
@@ -54,4 +54,5 @@ Conversation language: {{language}}
|
|
|
54
54
|
- When memory activity is warranted, explain which layers are affected, cite any matching memories you found, and justify why extraction or updates are needed.
|
|
55
55
|
- When nothing qualifies, explicitly state that no memory action is required after reviewing the context.
|
|
56
56
|
- Keep your reasoning concise, structured, and aligned with the conversation language.
|
|
57
|
+
- **Never expose internal memory IDs** (e.g., mem_xxx, id: xxx) to users in your responses. Refer to memories by their descriptive titles or summaries instead.
|
|
57
58
|
</response_expectations>`;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { LobeToolManifest } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSON Schema type for tool parameters
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolParameterSchema {
|
|
7
|
+
properties?: Record<string, unknown>;
|
|
8
|
+
required?: string[];
|
|
9
|
+
type?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Safe JSON parse utility
|
|
14
|
+
*/
|
|
15
|
+
const safeParseJSON = <T = Record<string, unknown>>(text?: string): T | undefined => {
|
|
16
|
+
if (typeof text !== 'string') return undefined;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(text) as T;
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tool Arguments Repairer
|
|
26
|
+
*
|
|
27
|
+
* Handles repair of malformed tool call arguments caused by LLM string escape issues.
|
|
28
|
+
*
|
|
29
|
+
* When some LLMs (like Claude haiku-4.5) output tool calls, they may produce malformed JSON
|
|
30
|
+
* where the entire content gets stuffed into the first field with escaped quotes.
|
|
31
|
+
*
|
|
32
|
+
* @example Malformed data:
|
|
33
|
+
* ```javascript
|
|
34
|
+
* { description: 'real desc", "instruction": "real instruction", "timeout": 120}' }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Expected data:
|
|
38
|
+
* ```javascript
|
|
39
|
+
* { description: 'real desc', instruction: 'real instruction', timeout: 120 }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example Usage:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const repairer = new ToolArgumentsRepairer(manifest);
|
|
45
|
+
* const args = repairer.parse('execTask', argumentsString);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class ToolArgumentsRepairer {
|
|
49
|
+
private manifest?: LobeToolManifest;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a new ToolArgumentsRepairer
|
|
53
|
+
* @param manifest - Tool manifest for schema lookup
|
|
54
|
+
*/
|
|
55
|
+
constructor(manifest?: LobeToolManifest) {
|
|
56
|
+
this.manifest = manifest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse and repair tool call arguments
|
|
61
|
+
*
|
|
62
|
+
* @param apiName - API name
|
|
63
|
+
* @param argumentsStr - Raw arguments string from LLM
|
|
64
|
+
* @returns Parsed and repaired arguments object
|
|
65
|
+
*/
|
|
66
|
+
parse(apiName: string, argumentsStr: string): Record<string, unknown> {
|
|
67
|
+
const parsed = safeParseJSON<Record<string, unknown>>(argumentsStr) || {};
|
|
68
|
+
|
|
69
|
+
// Get API schema for repair
|
|
70
|
+
const apiSchema = this.manifest?.api?.find((a) => a.name === apiName)?.parameters;
|
|
71
|
+
|
|
72
|
+
return this.repair(parsed, apiSchema);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Repair malformed arguments using schema
|
|
77
|
+
*
|
|
78
|
+
* @param parsed - The parsed (but potentially malformed) arguments object
|
|
79
|
+
* @param schema - The JSON schema for the tool's parameters (with required fields)
|
|
80
|
+
* @returns The repaired arguments object
|
|
81
|
+
*/
|
|
82
|
+
repair(parsed: Record<string, unknown>, schema?: ToolParameterSchema): Record<string, unknown> {
|
|
83
|
+
// If no schema or no required fields, skip repair
|
|
84
|
+
if (!schema?.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const keys = Object.keys(parsed);
|
|
89
|
+
const missingFields = schema.required.filter((f) => !(f in parsed));
|
|
90
|
+
|
|
91
|
+
// If no missing required fields, no need to repair
|
|
92
|
+
if (missingFields.length === 0) {
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if any existing field's value contains the missing field patterns
|
|
97
|
+
// This indicates the string escape issue
|
|
98
|
+
for (const key of keys) {
|
|
99
|
+
const value = parsed[key];
|
|
100
|
+
if (typeof value !== 'string') continue;
|
|
101
|
+
|
|
102
|
+
// Check if value contains patterns like `", "missingField":` or `", \"missingField\":`
|
|
103
|
+
const hasMissingFieldPattern = missingFields.some(
|
|
104
|
+
(field) => value.includes(`", "${field}":`) || value.includes(`", \\"${field}\\":`),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (hasMissingFieldPattern) {
|
|
108
|
+
// Try to reconstruct the correct JSON
|
|
109
|
+
// The value is actually: 'realValue", "field2": "value2", ...}'
|
|
110
|
+
// So we rebuild: '{"key": "realValue", "field2": "value2", ...}'
|
|
111
|
+
try {
|
|
112
|
+
const reconstructed = `{"${key}": "${value}`;
|
|
113
|
+
const repaired = JSON.parse(reconstructed) as Record<string, unknown>;
|
|
114
|
+
|
|
115
|
+
// Verify the repair was successful - all required fields should be present
|
|
116
|
+
const stillMissing = schema.required.filter((f) => !(f in repaired));
|
|
117
|
+
if (stillMissing.length === 0) {
|
|
118
|
+
return repaired;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Repair failed, continue to try other approaches or return original
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Could not repair, return original parsed data
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
}
|