@lobehub/lobehub 2.0.0-next.57 → 2.0.0-next.58

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.58](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.57...v2.0.0-next.58)
6
+
7
+ <sup>Released on **2025-11-14**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Support DeepSeek Interleaved thinking.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Support DeepSeek Interleaved thinking, closes [#10219](https://github.com/lobehub/lobe-chat/issues/10219) ([3736a85](https://github.com/lobehub/lobe-chat/commit/3736a85))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.57](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.56...v2.0.0-next.57)
6
31
 
7
32
  <sup>Released on **2025-11-14**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Support DeepSeek Interleaved thinking."
6
+ ]
7
+ },
8
+ "date": "2025-11-14",
9
+ "version": "2.0.0-next.58"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.57",
3
+ "version": "2.0.0-next.58",
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",
@@ -171,7 +171,7 @@ describe('convertOpenAIMessages', () => {
171
171
  expect((result[0] as any).reasoning).toBeUndefined();
172
172
  });
173
173
 
174
- it('should filter out reasoning_content field from messages', async () => {
174
+ it('should preserve reasoning_content field from messages (for DeepSeek compatibility)', async () => {
175
175
  const messages = [
176
176
  {
177
177
  role: 'assistant',
@@ -184,14 +184,14 @@ describe('convertOpenAIMessages', () => {
184
184
  const result = await convertOpenAIMessages(messages);
185
185
 
186
186
  expect(result).toEqual([
187
- { role: 'assistant', content: 'Hello' },
187
+ { role: 'assistant', content: 'Hello', reasoning_content: 'some reasoning content' },
188
188
  { role: 'user', content: 'Hi' },
189
189
  ]);
190
- // Ensure reasoning_content field is removed
191
- expect((result[0] as any).reasoning_content).toBeUndefined();
190
+ // Ensure reasoning_content field is preserved
191
+ expect((result[0] as any).reasoning_content).toBe('some reasoning content');
192
192
  });
193
193
 
194
- it('should filter out both reasoning and reasoning_content fields from messages', async () => {
194
+ it('should filter out reasoning but preserve reasoning_content field', async () => {
195
195
  const messages = [
196
196
  {
197
197
  role: 'assistant',
@@ -203,10 +203,12 @@ describe('convertOpenAIMessages', () => {
203
203
 
204
204
  const result = await convertOpenAIMessages(messages);
205
205
 
206
- expect(result).toEqual([{ role: 'assistant', content: 'Hello' }]);
207
- // Ensure both fields are removed
206
+ expect(result).toEqual([
207
+ { role: 'assistant', content: 'Hello', reasoning_content: 'some reasoning content' },
208
+ ]);
209
+ // Ensure reasoning object is removed but reasoning_content is preserved
208
210
  expect((result[0] as any).reasoning).toBeUndefined();
209
- expect((result[0] as any).reasoning_content).toBeUndefined();
211
+ expect((result[0] as any).reasoning_content).toBe('some reasoning content');
210
212
  });
211
213
  });
212
214
 
@@ -49,6 +49,9 @@ export const convertOpenAIMessages = async (messages: OpenAI.ChatCompletionMessa
49
49
  if (msg.tool_call_id !== undefined) result.tool_call_id = msg.tool_call_id;
50
50
  if (msg.function_call !== undefined) result.function_call = msg.function_call;
51
51
 
52
+ // it's compatible for DeepSeek
53
+ if (msg.reasoning_content !== undefined) result.reasoning_content = msg.reasoning_content;
54
+
52
55
  return result;
53
56
  }),
54
57
  )) as OpenAI.ChatCompletionMessageParam[];
@@ -20,6 +20,92 @@ testProvider({
20
20
  });
21
21
 
22
22
  describe('LobeDeepSeekAI - custom features', () => {
23
+ describe('chatCompletion.handlePayload', () => {
24
+ it('should transform reasoning object to reasoning_content string', () => {
25
+ const payload = {
26
+ messages: [
27
+ { role: 'user', content: 'Hello' },
28
+ {
29
+ role: 'assistant',
30
+ content: 'Hi there',
31
+ reasoning: { content: 'Let me think...', duration: 1000 },
32
+ },
33
+ { role: 'user', content: 'How are you?' },
34
+ ],
35
+ model: 'deepseek-r1',
36
+ };
37
+
38
+ const result = params.chatCompletion!.handlePayload!(payload as any);
39
+
40
+ expect(result.messages).toEqual([
41
+ { role: 'user', content: 'Hello' },
42
+ {
43
+ role: 'assistant',
44
+ content: 'Hi there',
45
+ reasoning_content: 'Let me think...',
46
+ },
47
+ { role: 'user', content: 'How are you?' },
48
+ ]);
49
+ });
50
+
51
+ it('should not modify messages without reasoning field', () => {
52
+ const payload = {
53
+ messages: [
54
+ { role: 'user', content: 'Hello' },
55
+ { role: 'assistant', content: 'Hi there' },
56
+ ],
57
+ model: 'deepseek-chat',
58
+ };
59
+
60
+ const result = params.chatCompletion!.handlePayload!(payload as any);
61
+
62
+ expect(result.messages).toEqual(payload.messages);
63
+ });
64
+
65
+ it('should handle empty reasoning content', () => {
66
+ const payload = {
67
+ messages: [
68
+ {
69
+ role: 'assistant',
70
+ content: 'Response',
71
+ reasoning: { duration: 1000 },
72
+ },
73
+ ],
74
+ model: 'deepseek-r1',
75
+ };
76
+
77
+ const result = params.chatCompletion!.handlePayload!(payload as any);
78
+
79
+ expect(result.messages[0]).toEqual({
80
+ role: 'assistant',
81
+ content: 'Response',
82
+ });
83
+ });
84
+
85
+ it('should set stream to true by default', () => {
86
+ const payload = {
87
+ messages: [{ role: 'user', content: 'Hello' }],
88
+ model: 'deepseek-chat',
89
+ };
90
+
91
+ const result = params.chatCompletion!.handlePayload!(payload as any);
92
+
93
+ expect(result.stream).toBe(true);
94
+ });
95
+
96
+ it('should preserve existing stream value', () => {
97
+ const payload = {
98
+ messages: [{ role: 'user', content: 'Hello' }],
99
+ model: 'deepseek-chat',
100
+ stream: false,
101
+ };
102
+
103
+ const result = params.chatCompletion!.handlePayload!(payload as any);
104
+
105
+ expect(result.stream).toBe(false);
106
+ });
107
+ });
108
+
23
109
  describe('Debug Configuration', () => {
24
110
  it('should disable debug by default', () => {
25
111
  delete process.env.DEBUG_DEEPSEEK_CHAT_COMPLETION;
@@ -12,6 +12,30 @@ export interface DeepSeekModelCard {
12
12
 
13
13
  export const params = {
14
14
  baseURL: 'https://api.deepseek.com/v1',
15
+ chatCompletion: {
16
+ handlePayload: (payload) => {
17
+ // Transform reasoning object to reasoning_content string for multi-turn conversations
18
+ const messages = payload.messages.map((message: any) => {
19
+ // Only transform if message has reasoning.content
20
+ if (message.reasoning?.content) {
21
+ const { reasoning, ...rest } = message;
22
+ return {
23
+ ...rest,
24
+ reasoning_content: reasoning.content,
25
+ };
26
+ }
27
+ // If message has reasoning but no content, remove reasoning field entirely
28
+ delete message.reasoning;
29
+ return message;
30
+ });
31
+
32
+ return {
33
+ ...payload,
34
+ messages,
35
+ stream: payload.stream ?? true,
36
+ } as any;
37
+ },
38
+ },
15
39
  debug: {
16
40
  chatCompletion: () => process.env.DEBUG_DEEPSEEK_CHAT_COMPLETION === '1',
17
41
  },