@lobehub/chat 0.156.2 → 0.157.0

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 (114) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +3 -2
  3. package/src/config/modelProviders/anthropic.ts +3 -0
  4. package/src/config/modelProviders/google.ts +3 -0
  5. package/src/config/modelProviders/groq.ts +5 -1
  6. package/src/config/modelProviders/minimax.ts +10 -7
  7. package/src/config/modelProviders/mistral.ts +1 -0
  8. package/src/config/modelProviders/moonshot.ts +3 -0
  9. package/src/config/modelProviders/zhipu.ts +2 -6
  10. package/src/config/server/provider.ts +1 -1
  11. package/src/database/client/core/db.ts +32 -0
  12. package/src/database/client/core/schemas.ts +9 -0
  13. package/src/database/client/models/__tests__/message.test.ts +2 -2
  14. package/src/database/client/schemas/message.ts +8 -1
  15. package/src/features/AgentSetting/store/action.ts +15 -6
  16. package/src/features/Conversation/Actions/Tool.tsx +16 -0
  17. package/src/features/Conversation/Actions/index.ts +2 -2
  18. package/src/features/Conversation/Messages/Assistant/ToolCalls/index.tsx +78 -0
  19. package/src/features/Conversation/Messages/Assistant/ToolCalls/style.ts +25 -0
  20. package/src/features/Conversation/Messages/Assistant/index.tsx +47 -0
  21. package/src/features/Conversation/Messages/Default.tsx +4 -1
  22. package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/index.tsx +34 -35
  23. package/src/features/Conversation/Messages/Tool/index.tsx +44 -0
  24. package/src/features/Conversation/Messages/index.ts +3 -2
  25. package/src/features/Conversation/Plugins/Render/StandaloneType/Iframe.tsx +1 -1
  26. package/src/features/Conversation/components/SkeletonList.tsx +2 -2
  27. package/src/features/Conversation/index.tsx +2 -3
  28. package/src/libs/agent-runtime/BaseAI.ts +2 -9
  29. package/src/libs/agent-runtime/anthropic/index.test.ts +195 -0
  30. package/src/libs/agent-runtime/anthropic/index.ts +71 -15
  31. package/src/libs/agent-runtime/azureOpenai/index.ts +6 -5
  32. package/src/libs/agent-runtime/bedrock/index.ts +24 -18
  33. package/src/libs/agent-runtime/google/index.test.ts +154 -0
  34. package/src/libs/agent-runtime/google/index.ts +91 -10
  35. package/src/libs/agent-runtime/groq/index.test.ts +41 -72
  36. package/src/libs/agent-runtime/groq/index.ts +7 -0
  37. package/src/libs/agent-runtime/minimax/index.test.ts +2 -2
  38. package/src/libs/agent-runtime/minimax/index.ts +14 -37
  39. package/src/libs/agent-runtime/mistral/index.test.ts +0 -53
  40. package/src/libs/agent-runtime/mistral/index.ts +1 -0
  41. package/src/libs/agent-runtime/moonshot/index.test.ts +1 -71
  42. package/src/libs/agent-runtime/ollama/index.test.ts +197 -0
  43. package/src/libs/agent-runtime/ollama/index.ts +3 -3
  44. package/src/libs/agent-runtime/openai/index.test.ts +0 -53
  45. package/src/libs/agent-runtime/openrouter/index.test.ts +1 -53
  46. package/src/libs/agent-runtime/perplexity/index.test.ts +0 -71
  47. package/src/libs/agent-runtime/perplexity/index.ts +2 -3
  48. package/src/libs/agent-runtime/togetherai/__snapshots__/index.test.ts.snap +886 -0
  49. package/src/libs/agent-runtime/togetherai/fixtures/models.json +8111 -0
  50. package/src/libs/agent-runtime/togetherai/index.test.ts +16 -54
  51. package/src/libs/agent-runtime/types/chat.ts +19 -3
  52. package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +120 -1
  53. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +67 -4
  54. package/src/libs/agent-runtime/utils/debugStream.test.ts +70 -0
  55. package/src/libs/agent-runtime/utils/debugStream.ts +39 -9
  56. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.test.ts +521 -0
  57. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +76 -5
  58. package/src/libs/agent-runtime/utils/response.ts +12 -0
  59. package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +197 -0
  60. package/src/libs/agent-runtime/utils/streams/anthropic.ts +91 -0
  61. package/src/libs/agent-runtime/utils/streams/bedrock/claude.ts +21 -0
  62. package/src/libs/agent-runtime/utils/streams/bedrock/common.ts +32 -0
  63. package/src/libs/agent-runtime/utils/streams/bedrock/index.ts +3 -0
  64. package/src/libs/agent-runtime/utils/streams/bedrock/llama.test.ts +196 -0
  65. package/src/libs/agent-runtime/utils/streams/bedrock/llama.ts +51 -0
  66. package/src/libs/agent-runtime/utils/streams/google-ai.test.ts +97 -0
  67. package/src/libs/agent-runtime/utils/streams/google-ai.ts +68 -0
  68. package/src/libs/agent-runtime/utils/streams/index.ts +7 -0
  69. package/src/libs/agent-runtime/utils/streams/minimax.ts +39 -0
  70. package/src/libs/agent-runtime/utils/streams/ollama.test.ts +77 -0
  71. package/src/libs/agent-runtime/utils/streams/ollama.ts +38 -0
  72. package/src/libs/agent-runtime/utils/streams/openai.test.ts +263 -0
  73. package/src/libs/agent-runtime/utils/streams/openai.ts +79 -0
  74. package/src/libs/agent-runtime/utils/streams/protocol.ts +100 -0
  75. package/src/libs/agent-runtime/zeroone/index.test.ts +1 -53
  76. package/src/libs/agent-runtime/zhipu/index.test.ts +1 -1
  77. package/src/libs/agent-runtime/zhipu/index.ts +3 -2
  78. package/src/locales/default/plugin.ts +3 -4
  79. package/src/migrations/FromV4ToV5/fixtures/from-v1-to-v5-output.json +245 -0
  80. package/src/migrations/FromV4ToV5/fixtures/function-input-v4.json +96 -0
  81. package/src/migrations/FromV4ToV5/fixtures/function-output-v5.json +120 -0
  82. package/src/migrations/FromV4ToV5/index.ts +58 -0
  83. package/src/migrations/FromV4ToV5/migrations.test.ts +49 -0
  84. package/src/migrations/FromV4ToV5/types/v4.ts +21 -0
  85. package/src/migrations/FromV4ToV5/types/v5.ts +27 -0
  86. package/src/migrations/index.ts +8 -1
  87. package/src/services/__tests__/chat.test.ts +10 -20
  88. package/src/services/chat.ts +78 -65
  89. package/src/store/chat/slices/enchance/action.ts +15 -10
  90. package/src/store/chat/slices/message/action.test.ts +36 -86
  91. package/src/store/chat/slices/message/action.ts +70 -79
  92. package/src/store/chat/slices/message/reducer.ts +18 -1
  93. package/src/store/chat/slices/message/selectors.test.ts +38 -68
  94. package/src/store/chat/slices/message/selectors.ts +1 -22
  95. package/src/store/chat/slices/plugin/action.test.ts +147 -203
  96. package/src/store/chat/slices/plugin/action.ts +96 -82
  97. package/src/store/chat/slices/share/action.test.ts +3 -3
  98. package/src/store/chat/slices/share/action.ts +1 -1
  99. package/src/store/chat/slices/topic/action.ts +7 -2
  100. package/src/store/tool/selectors/tool.ts +6 -24
  101. package/src/store/tool/slices/builtin/action.test.ts +90 -0
  102. package/src/types/llm.ts +1 -1
  103. package/src/types/message/index.ts +9 -4
  104. package/src/types/message/tools.ts +57 -0
  105. package/src/types/openai/chat.ts +6 -0
  106. package/src/utils/fetch.test.ts +245 -1
  107. package/src/utils/fetch.ts +120 -44
  108. package/src/utils/toolCall.ts +21 -0
  109. package/src/features/Conversation/Messages/Assistant.tsx +0 -26
  110. package/src/features/Conversation/Messages/Function.tsx +0 -35
  111. package/src/libs/agent-runtime/ollama/stream.ts +0 -31
  112. /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/PluginResultJSON.tsx +0 -0
  113. /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/Settings.tsx +0 -0
  114. /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/style.ts +0 -0
@@ -1,8 +1,10 @@
1
+ import { fetchEventSource } from '@microsoft/fetch-event-source';
2
+ import { FetchEventSourceInit } from '@microsoft/fetch-event-source';
1
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
4
 
3
5
  import { ErrorResponse } from '@/types/fetch';
4
6
 
5
- import { getMessageError } from './fetch';
7
+ import { fetchSSE, getMessageError, parseToolCalls } from './fetch';
6
8
 
7
9
  // 模拟 i18next
8
10
  vi.mock('i18next', () => ({
@@ -39,6 +41,10 @@ const createMockResponse = (body: any, ok: boolean, status: number = 200) => ({
39
41
  },
40
42
  });
41
43
 
44
+ vi.mock('@microsoft/fetch-event-source', () => ({
45
+ fetchEventSource: vi.fn(),
46
+ }));
47
+
42
48
  // 在每次测试后清理所有模拟
43
49
  afterEach(() => {
44
50
  vi.restoreAllMocks();
@@ -77,3 +83,241 @@ describe('getMessageError', () => {
77
83
  expect(mockResponse.json).toHaveBeenCalled();
78
84
  });
79
85
  });
86
+
87
+ describe('parseToolCalls', () => {
88
+ it('should create add new item', () => {
89
+ const chunk = [
90
+ { index: 0, id: '1', type: 'function', function: { name: 'func', arguments: '' } },
91
+ ];
92
+
93
+ const result = parseToolCalls([], chunk);
94
+ expect(result).toEqual([
95
+ { id: '1', type: 'function', function: { name: 'func', arguments: '' } },
96
+ ]);
97
+ });
98
+
99
+ it('should update arguments if there is a toolCall', () => {
100
+ const origin = [{ id: '1', type: 'function', function: { name: 'func', arguments: '' } }];
101
+
102
+ const chunk1 = [{ index: 0, function: { arguments: '{"lo' } }];
103
+
104
+ const result1 = parseToolCalls(origin, chunk1);
105
+ expect(result1).toEqual([
106
+ { id: '1', type: 'function', function: { name: 'func', arguments: '{"lo' } },
107
+ ]);
108
+
109
+ const chunk2 = [{ index: 0, function: { arguments: 'cation\\": \\"Hangzhou\\"}' } }];
110
+ const result2 = parseToolCalls(result1, chunk2);
111
+
112
+ expect(result2).toEqual([
113
+ {
114
+ id: '1',
115
+ type: 'function',
116
+ function: { name: 'func', arguments: '{"location\\": \\"Hangzhou\\"}' },
117
+ },
118
+ ]);
119
+ });
120
+
121
+ it('should add a new tool call if the index is different', () => {
122
+ const origin = [
123
+ {
124
+ id: '1',
125
+ type: 'function',
126
+ function: { name: 'func', arguments: '{"location\\": \\"Hangzhou\\"}' },
127
+ },
128
+ ];
129
+
130
+ const chunk = [
131
+ {
132
+ index: 1,
133
+ id: '2',
134
+ type: 'function',
135
+ function: { name: 'func', arguments: '' },
136
+ },
137
+ ];
138
+
139
+ const result1 = parseToolCalls(origin, chunk);
140
+ expect(result1).toEqual([
141
+ {
142
+ id: '1',
143
+ type: 'function',
144
+ function: { name: 'func', arguments: '{"location\\": \\"Hangzhou\\"}' },
145
+ },
146
+ { id: '2', type: 'function', function: { name: 'func', arguments: '' } },
147
+ ]);
148
+ });
149
+
150
+ it('should update correct arguments if there are multi tool calls', () => {
151
+ const origin = [
152
+ {
153
+ id: '1',
154
+ type: 'function',
155
+ function: { name: 'func', arguments: '{"location\\": \\"Hangzhou\\"}' },
156
+ },
157
+ { id: '2', type: 'function', function: { name: 'func', arguments: '' } },
158
+ ];
159
+
160
+ const chunk = [{ index: 1, function: { arguments: '{"location\\": \\"Beijing\\"}' } }];
161
+
162
+ const result1 = parseToolCalls(origin, chunk);
163
+ expect(result1).toEqual([
164
+ {
165
+ id: '1',
166
+ type: 'function',
167
+ function: { name: 'func', arguments: '{"location\\": \\"Hangzhou\\"}' },
168
+ },
169
+ {
170
+ id: '2',
171
+ type: 'function',
172
+ function: { name: 'func', arguments: '{"location\\": \\"Beijing\\"}' },
173
+ },
174
+ ]);
175
+ });
176
+ });
177
+
178
+ describe('fetchSSE', () => {
179
+ it('should handle text event correctly', async () => {
180
+ const mockOnMessageHandle = vi.fn();
181
+ const mockOnFinish = vi.fn();
182
+
183
+ (fetchEventSource as any).mockImplementationOnce(
184
+ (url: string, options: FetchEventSourceInit) => {
185
+ options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
186
+ options.onmessage!({ event: 'text', data: JSON.stringify('Hello') } as any);
187
+ options.onmessage!({ event: 'text', data: JSON.stringify(' World') } as any);
188
+ },
189
+ );
190
+
191
+ await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });
192
+
193
+ expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hello', type: 'text' });
194
+ expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: ' World', type: 'text' });
195
+ expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
196
+ observationId: null,
197
+ toolCalls: undefined,
198
+ traceId: null,
199
+ type: 'done',
200
+ });
201
+ });
202
+
203
+ it('should handle tool_calls event correctly', async () => {
204
+ const mockOnMessageHandle = vi.fn();
205
+ const mockOnFinish = vi.fn();
206
+
207
+ (fetchEventSource as any).mockImplementationOnce(
208
+ (url: string, options: FetchEventSourceInit) => {
209
+ options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
210
+ options.onmessage!({
211
+ event: 'tool_calls',
212
+ data: JSON.stringify([
213
+ { index: 0, id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
214
+ ]),
215
+ } as any);
216
+ options.onmessage!({
217
+ event: 'tool_calls',
218
+ data: JSON.stringify([
219
+ { index: 1, id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
220
+ ]),
221
+ } as any);
222
+ },
223
+ );
224
+
225
+ await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });
226
+
227
+ expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
228
+ tool_calls: [{ id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } }],
229
+ type: 'tool_calls',
230
+ });
231
+ expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, {
232
+ tool_calls: [
233
+ { id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
234
+ { id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
235
+ ],
236
+ type: 'tool_calls',
237
+ });
238
+ expect(mockOnFinish).toHaveBeenCalledWith('', {
239
+ observationId: null,
240
+ toolCalls: [
241
+ { id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
242
+ { id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
243
+ ],
244
+ traceId: null,
245
+ type: 'done',
246
+ });
247
+ });
248
+
249
+ it('should call onAbort when AbortError is thrown', async () => {
250
+ const mockOnAbort = vi.fn();
251
+
252
+ (fetchEventSource as any).mockImplementationOnce(
253
+ (url: string, options: FetchEventSourceInit) => {
254
+ options.onmessage!({ event: 'text', data: JSON.stringify('Hello') } as any);
255
+ options.onerror!({ name: 'AbortError' });
256
+ },
257
+ );
258
+
259
+ await fetchSSE('/', { onAbort: mockOnAbort });
260
+
261
+ expect(mockOnAbort).toHaveBeenCalledWith('Hello');
262
+ });
263
+
264
+ it('should call onErrorHandle when other error is thrown', async () => {
265
+ const mockOnErrorHandle = vi.fn();
266
+ const mockError = new Error('Unknown error');
267
+
268
+ (fetchEventSource as any).mockImplementationOnce(
269
+ (url: string, options: FetchEventSourceInit) => {
270
+ options.onerror!(mockError);
271
+ },
272
+ );
273
+
274
+ await fetchSSE('/', { onErrorHandle: mockOnErrorHandle });
275
+
276
+ expect(mockOnErrorHandle).not.toHaveBeenCalled();
277
+ });
278
+
279
+ it('should call onErrorHandle when response is not ok', async () => {
280
+ const mockOnErrorHandle = vi.fn();
281
+
282
+ (fetchEventSource as any).mockImplementationOnce(
283
+ (url: string, options: FetchEventSourceInit) => {
284
+ const res = new Response(JSON.stringify({ errorType: 'SomeError' }), {
285
+ status: 400,
286
+ statusText: 'Error',
287
+ });
288
+
289
+ options.onopen!(res as any);
290
+ },
291
+ );
292
+
293
+ await fetchSSE('/', { onErrorHandle: mockOnErrorHandle });
294
+
295
+ expect(mockOnErrorHandle).toHaveBeenCalledWith({
296
+ body: undefined,
297
+ message: 'translated_response.SomeError',
298
+ type: 'SomeError',
299
+ });
300
+ });
301
+
302
+ it('should call onMessageHandle with full text if no message event', async () => {
303
+ const mockOnMessageHandle = vi.fn();
304
+ const mockOnFinish = vi.fn();
305
+
306
+ (fetchEventSource as any).mockImplementationOnce(
307
+ (url: string, options: FetchEventSourceInit) => {
308
+ const res = new Response('Hello World', { status: 200, statusText: 'OK' });
309
+ options.onopen!(res as any);
310
+ },
311
+ );
312
+
313
+ await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });
314
+
315
+ expect(mockOnMessageHandle).toHaveBeenCalledWith({ text: 'Hello World', type: 'text' });
316
+ expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
317
+ observationId: null,
318
+ toolCalls: undefined,
319
+ traceId: null,
320
+ type: 'done',
321
+ });
322
+ });
323
+ });
@@ -1,8 +1,15 @@
1
+ import { fetchEventSource } from '@microsoft/fetch-event-source';
1
2
  import { t } from 'i18next';
3
+ import { produce } from 'immer';
2
4
 
3
5
  import { LOBE_CHAT_OBSERVATION_ID, LOBE_CHAT_TRACE_ID } from '@/const/trace';
4
6
  import { ErrorResponse, ErrorType } from '@/types/fetch';
5
- import { ChatMessageError } from '@/types/message';
7
+ import {
8
+ ChatMessageError,
9
+ MessageToolCall,
10
+ MessageToolCallChunk,
11
+ MessageToolCallSchema,
12
+ } from '@/types/message';
6
13
 
7
14
  export const getMessageError = async (response: Response) => {
8
15
  let chatMessageError: ChatMessageError;
@@ -32,68 +39,137 @@ export type OnFinishHandler = (
32
39
  text: string,
33
40
  context: {
34
41
  observationId?: string | null;
42
+ toolCalls?: MessageToolCall[];
35
43
  traceId?: string | null;
36
44
  type?: SSEFinishType;
37
45
  },
38
46
  ) => Promise<void>;
39
47
 
48
+ export interface MessageTextChunk {
49
+ text: string;
50
+ type: 'text';
51
+ }
52
+
53
+ interface MessageToolCallsChunk {
54
+ tool_calls: MessageToolCall[];
55
+ type: 'tool_calls';
56
+ }
57
+
40
58
  export interface FetchSSEOptions {
59
+ fetcher?: typeof fetch;
41
60
  onAbort?: (text: string) => Promise<void>;
42
61
  onErrorHandle?: (error: ChatMessageError) => void;
43
62
  onFinish?: OnFinishHandler;
44
- onMessageHandle?: (text: string) => void;
63
+ onMessageHandle?: (chunk: MessageTextChunk | MessageToolCallsChunk) => void;
45
64
  }
46
65
 
66
+ export const parseToolCalls = (origin: MessageToolCall[], value: MessageToolCallChunk[]) =>
67
+ produce(origin, (draft) => {
68
+ if (draft.length === 0) {
69
+ draft.push(...value.map((item) => MessageToolCallSchema.parse(item)));
70
+ } else {
71
+ value.forEach(({ index, ...item }) => {
72
+ if (!draft?.[index]) {
73
+ draft?.splice(index, 0, MessageToolCallSchema.parse(item));
74
+ } else {
75
+ if (item.function?.arguments) {
76
+ draft[index].function.arguments += item.function.arguments;
77
+ }
78
+ }
79
+ });
80
+ }
81
+ });
82
+
47
83
  /**
48
84
  * Fetch data using stream method
49
85
  */
50
- export const fetchSSE = async (fetchFn: () => Promise<Response>, options: FetchSSEOptions = {}) => {
51
- const response = await fetchFn();
52
-
53
- // 如果不 ok 说明有请求错误
54
- if (!response.ok) {
55
- const chatMessageError = await getMessageError(response);
56
-
57
- options.onErrorHandle?.(chatMessageError);
58
- return;
59
- }
60
-
61
- const returnRes = response.clone();
62
-
63
- const data = response.body;
64
-
65
- if (!data) return;
86
+ // eslint-disable-next-line no-undef
87
+ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptions = {}) => {
66
88
  let output = '';
67
- const reader = data.getReader();
68
- const decoder = new TextDecoder();
89
+ let toolCalls: undefined | MessageToolCall[];
90
+ let triggerOnMessageHandler = false;
69
91
 
70
- let done = false;
71
92
  let finishedType: SSEFinishType = 'done';
93
+ let response!: Response;
72
94
 
73
- while (!done) {
74
- try {
75
- const { value, done: doneReading } = await reader.read();
76
- done = doneReading;
77
- const chunkValue = decoder.decode(value, { stream: true });
78
-
79
- output += chunkValue;
80
- options.onMessageHandle?.(chunkValue);
81
- } catch (error) {
82
- done = true;
83
-
84
- if ((error as TypeError).name === 'AbortError') {
85
- finishedType = 'abort';
86
- options?.onAbort?.(output);
87
- } else {
88
- finishedType = 'error';
89
- console.error(error);
90
- }
95
+ try {
96
+ await fetchEventSource(url, {
97
+ body: options.body,
98
+ fetch: options?.fetcher,
99
+ headers: options.headers as Record<string, string>,
100
+ method: options.method,
101
+ onerror: (error) => {
102
+ if ((error as TypeError).name === 'AbortError') {
103
+ finishedType = 'abort';
104
+ options?.onAbort?.(output);
105
+ } else {
106
+ finishedType = 'error';
107
+ console.error(error);
108
+ }
109
+ throw new Error('Fetch error');
110
+ // options.onErrorHandle()
111
+ },
112
+ onmessage: (ev) => {
113
+ triggerOnMessageHandler = true;
114
+ let data;
115
+ try {
116
+ data = JSON.parse(ev.data);
117
+ } catch (e) {
118
+ console.warn('parse error, fallback to stream', e);
119
+ options.onMessageHandle?.({ text: data, type: 'text' });
120
+ return;
121
+ }
122
+
123
+ switch (ev.event) {
124
+ case 'text': {
125
+ output += data;
126
+ options.onMessageHandle?.({ text: data, type: 'text' });
127
+ break;
128
+ }
129
+
130
+ case 'tool_calls': {
131
+ if (!toolCalls) {
132
+ toolCalls = [];
133
+ }
134
+
135
+ toolCalls = parseToolCalls(toolCalls, data);
136
+
137
+ options.onMessageHandle?.({
138
+ tool_calls: toolCalls,
139
+ type: 'tool_calls',
140
+ });
141
+ }
142
+ }
143
+ },
144
+ onopen: async (res) => {
145
+ response = res.clone();
146
+
147
+ // 如果不 ok 说明有请求错误
148
+ if (!response.ok) {
149
+ const chatMessageError = await getMessageError(res);
150
+
151
+ options.onErrorHandle?.(chatMessageError);
152
+ return;
153
+ }
154
+ },
155
+
156
+ signal: options.signal,
157
+ });
158
+ } catch {}
159
+
160
+ // only call onFinish when response is available
161
+ // so like abort, we don't need to call onFinish
162
+ if (response) {
163
+ // if there is no onMessageHandler, we should call onHandleMessage first
164
+ if (!triggerOnMessageHandler) {
165
+ output = await response.clone().text();
166
+ options.onMessageHandle?.({ text: output, type: 'text' });
91
167
  }
92
- }
93
168
 
94
- const traceId = response.headers.get(LOBE_CHAT_TRACE_ID);
95
- const observationId = response.headers.get(LOBE_CHAT_OBSERVATION_ID);
96
- await options?.onFinish?.(output, { observationId, traceId, type: finishedType });
169
+ const traceId = response.headers.get(LOBE_CHAT_TRACE_ID);
170
+ const observationId = response.headers.get(LOBE_CHAT_OBSERVATION_ID);
171
+ await options?.onFinish?.(output, { observationId, toolCalls, traceId, type: finishedType });
172
+ }
97
173
 
98
- return returnRes;
174
+ return response;
99
175
  };
@@ -0,0 +1,21 @@
1
+ import { Md5 } from 'ts-md5';
2
+
3
+ import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
4
+
5
+ export const genToolCallingName = (identifier: string, name: string, type: string = 'default') => {
6
+ const pluginType = type && type !== 'default' ? `${PLUGIN_SCHEMA_SEPARATOR + type}` : '';
7
+
8
+ // 将插件的 identifier 作为前缀,避免重复
9
+ let apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + name + pluginType;
10
+
11
+ // OpenAI GPT function_call name can't be longer than 64 characters
12
+ // So we need to use md5 to shorten the name
13
+ // and then find the correct apiName in response by md5
14
+ if (apiName.length >= 64) {
15
+ const md5Content = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(name).toString();
16
+
17
+ apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + md5Content + pluginType;
18
+ }
19
+
20
+ return apiName;
21
+ };
@@ -1,26 +0,0 @@
1
- import { ReactNode, memo } from 'react';
2
-
3
- import { isFunctionMessageAtStart } from '@/const/message';
4
- import { useChatStore } from '@/store/chat';
5
- import { chatSelectors } from '@/store/chat/selectors';
6
- import { ChatMessage } from '@/types/message';
7
-
8
- import Inspector from '../Plugins/Inspector';
9
- import { DefaultMessage } from './Default';
10
-
11
- export const AssistantMessage = memo<
12
- ChatMessage & {
13
- editableContent: ReactNode;
14
- }
15
- >(({ id, plugin, content, ...props }) => {
16
- const fcProps = useChatStore(chatSelectors.getFunctionMessageProps({ content, id, plugin }));
17
-
18
- if (!isFunctionMessageAtStart(content))
19
- return <DefaultMessage content={content} id={id} {...props} />;
20
-
21
- return (
22
- <div id={id}>
23
- <Inspector {...fcProps} />
24
- </div>
25
- );
26
- });
@@ -1,35 +0,0 @@
1
- import isEqual from 'fast-deep-equal';
2
- import { memo, useState } from 'react';
3
- import { Flexbox } from 'react-layout-kit';
4
-
5
- import { useChatStore } from '@/store/chat';
6
- import { chatSelectors } from '@/store/chat/selectors';
7
- import { ChatMessage } from '@/types/message';
8
-
9
- import Inspector from '../Plugins/Inspector';
10
- import PluginRender from '../Plugins/Render';
11
-
12
- export const FunctionMessage = memo<ChatMessage>(({ id, content, plugin }) => {
13
- const fcProps = useChatStore(
14
- chatSelectors.getFunctionMessageProps({ content, id, plugin }),
15
- isEqual,
16
- );
17
-
18
- const [showRender, setShow] = useState(true);
19
-
20
- return (
21
- <Flexbox gap={12} id={id} width={'100%'}>
22
- <Inspector showRender={showRender} {...fcProps} setShow={setShow} />
23
- {showRender && (
24
- <PluginRender
25
- content={content}
26
- id={id}
27
- identifier={plugin?.identifier}
28
- loading={fcProps.loading}
29
- payload={fcProps.command}
30
- type={fcProps.type}
31
- />
32
- )}
33
- </Flexbox>
34
- );
35
- });
@@ -1,31 +0,0 @@
1
- // copy from https://github.com/vercel/ai/discussions/539#discussioncomment-8193721
2
- // and I have remove the unnecessary code
3
- import {
4
- type AIStreamCallbacksAndOptions,
5
- createCallbacksTransformer,
6
- createStreamDataTransformer,
7
- readableFromAsyncIterable,
8
- } from 'ai';
9
- import { ChatResponse } from 'ollama/browser';
10
-
11
- // A modified version of the streamable function specifically for chat messages
12
- const chatStreamable = async function* (stream: AsyncIterable<ChatResponse>) {
13
- for await (const response of stream) {
14
- if (response.message) {
15
- yield response.message;
16
- }
17
- if (response.done) {
18
- // Additional final response data can be handled here if necessary
19
- return;
20
- }
21
- }
22
- };
23
-
24
- export const OllamaStream = (
25
- res: AsyncIterable<ChatResponse>,
26
- cb?: AIStreamCallbacksAndOptions,
27
- ): ReadableStream<string> => {
28
- return readableFromAsyncIterable(chatStreamable(res))
29
- .pipeThrough(createCallbacksTransformer(cb) as any)
30
- .pipeThrough(createStreamDataTransformer(cb?.experimental_streamData));
31
- };