@lobehub/lobehub 2.0.0-next.129 → 2.0.0-next.130
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
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.130](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.129...v2.0.0-next.130)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-11-28**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Add handling for `content_part` and `reasoning_part` events in fetchSSE.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Add handling for `content_part` and `reasoning_part` events in fetchSSE, closes [#10470](https://github.com/lobehub/lobe-chat/issues/10470) ([8aff3ab](https://github.com/lobehub/lobe-chat/commit/8aff3ab))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.129](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.128...v2.0.0-next.129)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2025-11-28**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.130",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -235,6 +235,135 @@ describe('fetchSSE', () => {
|
|
|
235
235
|
});
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
+
describe('content_part and reasoning_part', () => {
|
|
239
|
+
it('should handle content_part event with text and accumulate output', async () => {
|
|
240
|
+
const mockOnMessageHandle = vi.fn();
|
|
241
|
+
const mockOnFinish = vi.fn();
|
|
242
|
+
|
|
243
|
+
(fetchEventSource as any).mockImplementationOnce(
|
|
244
|
+
async (url: string, options: FetchEventSourceInit) => {
|
|
245
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
|
246
|
+
options.onmessage!({
|
|
247
|
+
event: 'content_part',
|
|
248
|
+
data: JSON.stringify({ content: 'Hello', partType: 'text' }),
|
|
249
|
+
} as any);
|
|
250
|
+
options.onmessage!({
|
|
251
|
+
event: 'content_part',
|
|
252
|
+
data: JSON.stringify({ content: ' World', partType: 'text' }),
|
|
253
|
+
} as any);
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
await fetchSSE('/', {
|
|
258
|
+
onMessageHandle: mockOnMessageHandle,
|
|
259
|
+
onFinish: mockOnFinish,
|
|
260
|
+
responseAnimation: 'none',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
|
|
264
|
+
content: 'Hello',
|
|
265
|
+
mimeType: undefined,
|
|
266
|
+
partType: 'text',
|
|
267
|
+
thoughtSignature: undefined,
|
|
268
|
+
type: 'content_part',
|
|
269
|
+
});
|
|
270
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, {
|
|
271
|
+
content: ' World',
|
|
272
|
+
mimeType: undefined,
|
|
273
|
+
partType: 'text',
|
|
274
|
+
thoughtSignature: undefined,
|
|
275
|
+
type: 'content_part',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Verify output is accumulated correctly
|
|
279
|
+
expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
|
|
280
|
+
observationId: null,
|
|
281
|
+
toolCalls: undefined,
|
|
282
|
+
traceId: null,
|
|
283
|
+
type: 'done',
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should handle reasoning_part event with text and accumulate thinking', async () => {
|
|
288
|
+
const mockOnMessageHandle = vi.fn();
|
|
289
|
+
const mockOnFinish = vi.fn();
|
|
290
|
+
|
|
291
|
+
(fetchEventSource as any).mockImplementationOnce(
|
|
292
|
+
async (url: string, options: FetchEventSourceInit) => {
|
|
293
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
|
294
|
+
options.onmessage!({
|
|
295
|
+
event: 'reasoning_part',
|
|
296
|
+
data: JSON.stringify({ content: 'Thinking:', partType: 'text' }),
|
|
297
|
+
} as any);
|
|
298
|
+
options.onmessage!({
|
|
299
|
+
event: 'reasoning_part',
|
|
300
|
+
data: JSON.stringify({ content: ' step 1', partType: 'text' }),
|
|
301
|
+
} as any);
|
|
302
|
+
options.onmessage!({
|
|
303
|
+
event: 'content_part',
|
|
304
|
+
data: JSON.stringify({ content: 'Final answer', partType: 'text' }),
|
|
305
|
+
} as any);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
await fetchSSE('/', {
|
|
310
|
+
onMessageHandle: mockOnMessageHandle,
|
|
311
|
+
onFinish: mockOnFinish,
|
|
312
|
+
responseAnimation: 'none',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Verify reasoning is accumulated correctly
|
|
316
|
+
expect(mockOnFinish).toHaveBeenCalledWith('Final answer', {
|
|
317
|
+
observationId: null,
|
|
318
|
+
reasoning: { content: 'Thinking: step 1' },
|
|
319
|
+
toolCalls: undefined,
|
|
320
|
+
traceId: null,
|
|
321
|
+
type: 'done',
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should not accumulate output for non-text content_part (e.g., image)', async () => {
|
|
326
|
+
const mockOnMessageHandle = vi.fn();
|
|
327
|
+
const mockOnFinish = vi.fn();
|
|
328
|
+
|
|
329
|
+
(fetchEventSource as any).mockImplementationOnce(
|
|
330
|
+
async (url: string, options: FetchEventSourceInit) => {
|
|
331
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
|
332
|
+
options.onmessage!({
|
|
333
|
+
event: 'content_part',
|
|
334
|
+
data: JSON.stringify({
|
|
335
|
+
content: 'base64imagedata',
|
|
336
|
+
partType: 'image',
|
|
337
|
+
mimeType: 'image/png',
|
|
338
|
+
}),
|
|
339
|
+
} as any);
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
await fetchSSE('/', {
|
|
344
|
+
onMessageHandle: mockOnMessageHandle,
|
|
345
|
+
onFinish: mockOnFinish,
|
|
346
|
+
responseAnimation: 'none',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(mockOnMessageHandle).toHaveBeenCalledWith({
|
|
350
|
+
content: 'base64imagedata',
|
|
351
|
+
mimeType: 'image/png',
|
|
352
|
+
partType: 'image',
|
|
353
|
+
thoughtSignature: undefined,
|
|
354
|
+
type: 'content_part',
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Output should be empty since image content is not accumulated
|
|
358
|
+
expect(mockOnFinish).toHaveBeenCalledWith('', {
|
|
359
|
+
observationId: null,
|
|
360
|
+
toolCalls: undefined,
|
|
361
|
+
traceId: null,
|
|
362
|
+
type: 'done',
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
238
367
|
it('should handle grounding event', async () => {
|
|
239
368
|
const mockOnMessageHandle = vi.fn();
|
|
240
369
|
const mockOnFinish = vi.fn();
|
|
@@ -438,8 +438,27 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
|
438
438
|
break;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
case 'reasoning_part':
|
|
441
|
+
case 'reasoning_part': {
|
|
442
|
+
// For reasoning_part, accumulate thinking content
|
|
443
|
+
if (data.partType === 'text' && data.content) {
|
|
444
|
+
thinking += data.content;
|
|
445
|
+
}
|
|
446
|
+
options.onMessageHandle?.({
|
|
447
|
+
content: data.content,
|
|
448
|
+
mimeType: data.mimeType,
|
|
449
|
+
partType: data.partType,
|
|
450
|
+
thoughtSignature: data.thoughtSignature,
|
|
451
|
+
type: ev.event,
|
|
452
|
+
});
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
|
|
442
456
|
case 'content_part': {
|
|
457
|
+
// For content_part, accumulate text content to output
|
|
458
|
+
// This is critical for Gemini 2.5 models which use content_part instead of text events
|
|
459
|
+
if (data.partType === 'text' && data.content) {
|
|
460
|
+
output += data.content;
|
|
461
|
+
}
|
|
443
462
|
options.onMessageHandle?.({
|
|
444
463
|
content: data.content,
|
|
445
464
|
mimeType: data.mimeType,
|