@lobehub/chat 1.88.18 β†’ 1.88.20

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,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.88.20](https://github.com/lobehub/lobe-chat/compare/v1.88.19...v1.88.20)
6
+
7
+ <sup>Released on **2025-05-31**</sup>
8
+
9
+ #### πŸ› Bug Fixes
10
+
11
+ - **misc**: Agent automatic completion meta not working error.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Agent automatic completion meta not working error, closes [#8003](https://github.com/lobehub/lobe-chat/issues/8003) ([c5307bf](https://github.com/lobehub/lobe-chat/commit/c5307bf))
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
+
30
+ ### [Version 1.88.19](https://github.com/lobehub/lobe-chat/compare/v1.88.18...v1.88.19)
31
+
32
+ <sup>Released on **2025-05-30**</sup>
33
+
34
+ #### πŸ’„ Styles
35
+
36
+ - **misc**: Support Web Search Tools and Beta Header from Anthropic.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Support Web Search Tools and Beta Header from Anthropic, closes [#7964](https://github.com/lobehub/lobe-chat/issues/7964) ([a47ddc5](https://github.com/lobehub/lobe-chat/commit/a47ddc5))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.88.18](https://github.com/lobehub/lobe-chat/compare/v1.88.17...v1.88.18)
6
56
 
7
57
  <sup>Released on **2025-05-29**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Agent automatic completion meta not working error."
6
+ ]
7
+ },
8
+ "date": "2025-05-31",
9
+ "version": "1.88.20"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Support Web Search Tools and Beta Header from Anthropic."
15
+ ]
16
+ },
17
+ "date": "2025-05-30",
18
+ "version": "1.88.19"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.88.18",
3
+ "version": "1.88.20",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -2,10 +2,12 @@ import { ColorSwatches, NeutralColors, findCustomThemeName, neutralColors } from
2
2
  import { memo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
- const ThemeSwatchesNeutral = memo<{
5
+ interface IProps {
6
6
  onChange?: (v: NeutralColors) => void;
7
7
  value?: NeutralColors;
8
- }>(({ value, onChange }) => {
8
+ }
9
+
10
+ const ThemeSwatchesNeutral = memo<IProps>(({ value, onChange }) => {
9
11
  const { t } = useTranslation('color');
10
12
 
11
13
  const handleSelect = (v: any) => {
@@ -2,10 +2,12 @@ import { ColorSwatches, PrimaryColors, findCustomThemeName, primaryColors } from
2
2
  import { memo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
- const ThemeSwatchesPrimary = memo<{
5
+ interface IProps {
6
6
  onChange?: (v: PrimaryColors) => void;
7
7
  value?: PrimaryColors;
8
- }>(({ onChange, value }) => {
8
+ }
9
+
10
+ const ThemeSwatchesPrimary = memo<IProps>(({ onChange, value }) => {
9
11
  const { t } = useTranslation('color');
10
12
 
11
13
  const handleSelect = (v: any) => {
@@ -5,6 +5,7 @@ const anthropicChatModels: AIChatModelCard[] = [
5
5
  abilities: {
6
6
  functionCall: true,
7
7
  reasoning: true,
8
+ search: true,
8
9
  vision: true,
9
10
  },
10
11
  contextWindowTokens: 200_000,
@@ -23,6 +24,7 @@ const anthropicChatModels: AIChatModelCard[] = [
23
24
  releasedAt: '2025-05-23',
24
25
  settings: {
25
26
  extendParams: ['disableContextCaching', 'enableReasoning', 'reasoningBudgetToken'],
27
+ searchImpl: 'params',
26
28
  },
27
29
  type: 'chat',
28
30
  },
@@ -30,6 +32,7 @@ const anthropicChatModels: AIChatModelCard[] = [
30
32
  abilities: {
31
33
  functionCall: true,
32
34
  reasoning: true,
35
+ search: true,
33
36
  vision: true,
34
37
  },
35
38
  contextWindowTokens: 200_000,
@@ -48,6 +51,7 @@ const anthropicChatModels: AIChatModelCard[] = [
48
51
  releasedAt: '2025-05-23',
49
52
  settings: {
50
53
  extendParams: ['disableContextCaching', 'enableReasoning', 'reasoningBudgetToken'],
54
+ searchImpl: 'params',
51
55
  },
52
56
  type: 'chat',
53
57
  },
@@ -55,6 +59,7 @@ const anthropicChatModels: AIChatModelCard[] = [
55
59
  abilities: {
56
60
  functionCall: true,
57
61
  reasoning: true,
62
+ search: true,
58
63
  vision: true,
59
64
  },
60
65
  contextWindowTokens: 200_000,
@@ -73,12 +78,14 @@ const anthropicChatModels: AIChatModelCard[] = [
73
78
  releasedAt: '2025-02-24',
74
79
  settings: {
75
80
  extendParams: ['disableContextCaching', 'enableReasoning', 'reasoningBudgetToken'],
81
+ searchImpl: 'params',
76
82
  },
77
83
  type: 'chat',
78
84
  },
79
85
  {
80
86
  abilities: {
81
87
  functionCall: true,
88
+ search: true,
82
89
  vision: true,
83
90
  },
84
91
  contextWindowTokens: 200_000,
@@ -96,6 +103,7 @@ const anthropicChatModels: AIChatModelCard[] = [
96
103
  releasedAt: '2024-10-22',
97
104
  settings: {
98
105
  extendParams: ['disableContextCaching'],
106
+ searchImpl: 'params',
99
107
  },
100
108
  type: 'chat',
101
109
  },
@@ -119,6 +127,7 @@ const anthropicChatModels: AIChatModelCard[] = [
119
127
  releasedAt: '2024-06-20',
120
128
  settings: {
121
129
  extendParams: ['disableContextCaching'],
130
+ searchImpl: 'params',
122
131
  },
123
132
  type: 'chat',
124
133
  },
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Button, Form, type FormGroupItemType, type FormItemProps, Tooltip } from '@lobehub/ui';
4
+ import { useUpdateEffect } from 'ahooks';
4
5
  import isEqual from 'fast-deep-equal';
5
6
  import { Wand2 } from 'lucide-react';
6
7
  import { memo, useState } from 'react';
@@ -30,6 +31,10 @@ const AgentMeta = memo(() => {
30
31
  const meta = useStore(selectors.currentMetaConfig, isEqual);
31
32
  const [background, setBackground] = useState(meta.backgroundColor);
32
33
 
34
+ useUpdateEffect(() => {
35
+ form.setFieldsValue(meta);
36
+ }, [meta]);
37
+
33
38
  if (isInbox) return;
34
39
 
35
40
  const basic = [
@@ -4,7 +4,7 @@ import type { ItemType } from 'antd/es/menu/interface';
4
4
  import { LucideArrowRight, LucideBolt } from 'lucide-react';
5
5
  import Link from 'next/link';
6
6
  import { useRouter } from 'next/navigation';
7
- import { PropsWithChildren, memo, useMemo } from 'react';
7
+ import { type ReactNode, memo, useMemo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
@@ -40,14 +40,15 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
40
40
 
41
41
  const menuKey = (provider: string, model: string) => `${provider}-${model}`;
42
42
 
43
- const ModelSwitchPanel = memo<
44
- PropsWithChildren<{
45
- onOpenChange?: (open: boolean) => void;
46
- open?: boolean;
47
- setUpdating?: (updating: boolean) => void;
48
- updating?: boolean;
49
- }>
50
- >(({ children, setUpdating, onOpenChange, open }) => {
43
+ interface IProps {
44
+ children?: ReactNode;
45
+ onOpenChange?: (open: boolean) => void;
46
+ open?: boolean;
47
+ setUpdating?: (updating: boolean) => void;
48
+ updating?: boolean;
49
+ }
50
+
51
+ const ModelSwitchPanel = memo<IProps>(({ children, setUpdating, onOpenChange, open }) => {
51
52
  const { t } = useTranslation('components');
52
53
  const { styles, theme } = useStyles();
53
54
  const [model, provider, updateAgentConfig] = useAgentStore((s) => [
@@ -141,7 +141,9 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
141
141
  ...(mcpType === 'http' ? [HTTP_URL_KEY] : [STDIO_COMMAND, STDIO_ARGS]),
142
142
  ]);
143
143
  isValid = true;
144
- } catch {}
144
+ } catch {
145
+ // no-thing
146
+ }
145
147
 
146
148
  if (!isValid) {
147
149
  setIsTesting(false);
@@ -302,6 +302,64 @@ describe('LobeAnthropicAI', () => {
302
302
  { enabledContextCaching: true },
303
303
  );
304
304
  });
305
+
306
+ it('should build payload with tools and web search enabled', async () => {
307
+ const tools: ChatCompletionTool[] = [
308
+ { function: { name: 'tool1', description: 'desc1' }, type: 'function' }
309
+ ];
310
+
311
+ const mockAnthropicTools = [{ name: 'tool1', description: 'desc1' }];
312
+
313
+ vi.spyOn(anthropicHelpers, 'buildAnthropicTools').mockReturnValue(mockAnthropicTools as any);
314
+
315
+ const payload: ChatStreamPayload = {
316
+ messages: [{ content: 'Search and get info', role: 'user' }],
317
+ model: 'claude-3-haiku-20240307',
318
+ temperature: 0.5,
319
+ tools,
320
+ enabledSearch: true,
321
+ };
322
+
323
+ const result = await instance['buildAnthropicPayload'](payload);
324
+
325
+ expect(anthropicHelpers.buildAnthropicTools).toHaveBeenCalledWith(tools, {
326
+ enabledContextCaching: true,
327
+ });
328
+
329
+ // Should include both the converted tools and web search tool
330
+ expect(result.tools).toEqual([
331
+ ...mockAnthropicTools,
332
+ {
333
+ name: 'web_search',
334
+ type: 'web_search_20250305',
335
+ },
336
+ ]);
337
+ });
338
+
339
+ it('should build payload with web search enabled but no other tools', async () => {
340
+ vi.spyOn(anthropicHelpers, 'buildAnthropicTools').mockReturnValue(undefined);
341
+
342
+ const payload: ChatStreamPayload = {
343
+ messages: [{ content: 'Search for information', role: 'user' }],
344
+ model: 'claude-3-haiku-20240307',
345
+ temperature: 0.5,
346
+ enabledSearch: true,
347
+ };
348
+
349
+ const result = await instance['buildAnthropicPayload'](payload);
350
+
351
+ expect(anthropicHelpers.buildAnthropicTools).toHaveBeenCalledWith(undefined, {
352
+ enabledContextCaching: true,
353
+ });
354
+
355
+ // Should only include web search tool
356
+ expect(result.tools).toEqual([
357
+ {
358
+ name: 'web_search',
359
+ type: 'web_search_20250305',
360
+ },
361
+ ]);
362
+ });
305
363
  });
306
364
 
307
365
  describe('Error', () => {
@@ -23,6 +23,8 @@ export interface AnthropicModelCard {
23
23
  id: string;
24
24
  }
25
25
 
26
+ type anthropicTools = Anthropic.Tool | Anthropic.WebSearchTool20250305;
27
+
26
28
  const modelsWithSmallContextWindow = new Set(['claude-3-opus-20240229', 'claude-3-haiku-20240307']);
27
29
 
28
30
  const DEFAULT_BASE_URL = 'https://api.anthropic.com';
@@ -45,7 +47,14 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
45
47
  constructor({ apiKey, baseURL = DEFAULT_BASE_URL, id, ...res }: AnthropicAIParams = {}) {
46
48
  if (!apiKey) throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
47
49
 
48
- this.client = new Anthropic({ apiKey, baseURL, ...res });
50
+ const betaHeaders = process.env.ANTHROPIC_BETA_HEADERS;
51
+
52
+ this.client = new Anthropic({
53
+ apiKey,
54
+ baseURL,
55
+ ...(betaHeaders ? { defaultHeaders: { "anthropic-beta": betaHeaders } } : {}),
56
+ ...res
57
+ });
49
58
  this.baseURL = this.client.baseURL;
50
59
  this.apiKey = apiKey;
51
60
  this.id = id || ModelProvider.Anthropic;
@@ -99,6 +108,7 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
99
108
  tools,
100
109
  thinking,
101
110
  enabledContextCaching = true,
111
+ enabledSearch,
102
112
  } = payload;
103
113
 
104
114
  const { default: anthropicModels } = await import('@/config/aiModels/anthropic');
@@ -127,7 +137,27 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
127
137
 
128
138
  const postMessages = await buildAnthropicMessages(user_messages, { enabledContextCaching });
129
139
 
130
- const postTools = buildAnthropicTools(tools, { enabledContextCaching });
140
+ let postTools: anthropicTools[] | undefined = buildAnthropicTools(tools, { enabledContextCaching });
141
+
142
+ if (enabledSearch) {
143
+ // Limit the number of searches per request
144
+ const maxUses = process.env.ANTHROPIC_MAX_USES;
145
+
146
+ const webSearchTool: Anthropic.WebSearchTool20250305 = {
147
+ name: 'web_search',
148
+ type: 'web_search_20250305',
149
+ ...(maxUses && Number.isInteger(Number(maxUses)) && Number(maxUses) > 0 && {
150
+ max_uses: Number(maxUses)
151
+ }),
152
+ };
153
+
154
+ // ε¦‚ζžœε·²ζœ‰ε·₯ε…·οΌŒεˆ™ζ·»εŠ εˆ°ηŽ°ζœ‰ε·₯ε…·εˆ—θ‘¨δΈ­οΌ›ε¦εˆ™εˆ›ε»Ίζ–°ηš„ε·₯ε…·εˆ—θ‘¨
155
+ if (postTools && postTools.length > 0) {
156
+ postTools = [...postTools, webSearchTool];
157
+ } else {
158
+ postTools = [webSearchTool];
159
+ }
160
+ }
131
161
 
132
162
  if (!!thinking && thinking.type === 'enabled') {
133
163
  const maxTokens = getMaxTokens() || 32_000; // Claude Opus 4 has minimum maxOutput
@@ -1,7 +1,7 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import type { Stream } from '@anthropic-ai/sdk/streaming';
3
3
 
4
- import { ModelTokensUsage } from '@/types/message';
4
+ import { ModelTokensUsage, CitationItem } from '@/types/message';
5
5
 
6
6
  import { ChatStreamCallbacks } from '../../types';
7
7
  import {
@@ -23,6 +23,7 @@ export const transformAnthropicStream = (
23
23
  switch (chunk.type) {
24
24
  case 'message_start': {
25
25
  context.id = chunk.message.id;
26
+ context.returnedCitationArray = [];
26
27
  let totalInputTokens = chunk.message.usage?.input_tokens;
27
28
 
28
29
  if (
@@ -59,6 +60,7 @@ export const transformAnthropicStream = (
59
60
  return { data: chunk.content_block.text, id: context.id, type: 'data' };
60
61
  }
61
62
 
63
+ case 'server_tool_use':
62
64
  case 'tool_use': {
63
65
  const toolChunk = chunk.content_block;
64
66
 
@@ -85,6 +87,29 @@ export const transformAnthropicStream = (
85
87
 
86
88
  return { data: [toolCall], id: context.id, type: 'tool_calls' };
87
89
  }
90
+
91
+ /*
92
+ case 'web_search_tool_result': {
93
+ const citations = chunk.content_block.content;
94
+
95
+ return [
96
+ {
97
+ data: {
98
+ citations: (citations as any[]).map(
99
+ (item) =>
100
+ ({
101
+ title: item.title,
102
+ url: item.url,
103
+ }) as CitationItem,
104
+ ),
105
+ },
106
+ id: context.id,
107
+ type: 'grounding',
108
+ },
109
+ ];
110
+ }
111
+ */
112
+
88
113
  case 'thinking': {
89
114
  const thinkingChunk = chunk.content_block;
90
115
 
@@ -148,6 +173,19 @@ export const transformAnthropicStream = (
148
173
  };
149
174
  }
150
175
 
176
+ case 'citations_delta': {
177
+ const citations = (chunk as any).delta.citation;
178
+
179
+ if (context.returnedCitationArray) {
180
+ context.returnedCitationArray.push({
181
+ title: citations.title,
182
+ url: citations.url,
183
+ } as CitationItem)
184
+ }
185
+
186
+ return { data: null, id: context.id, type: 'text' };
187
+ }
188
+
151
189
  default: {
152
190
  break;
153
191
  }
@@ -180,7 +218,17 @@ export const transformAnthropicStream = (
180
218
  }
181
219
 
182
220
  case 'message_stop': {
183
- return { data: 'message_stop', id: context.id, type: 'stop' };
221
+ return [
222
+ ...(context.returnedCitationArray?.length
223
+ ? [{
224
+ data: { citations: context.returnedCitationArray },
225
+ id: context.id,
226
+ type: 'grounding'
227
+ }]
228
+ : []
229
+ ),
230
+ { data: 'message_stop', id: context.id, type: 'stop' }
231
+ ] as any;
184
232
  }
185
233
 
186
234
  default: {
@@ -1,4 +1,4 @@
1
- import { ModelSpeed, ModelTokensUsage } from '@/types/message';
1
+ import { CitationItem, ModelSpeed, ModelTokensUsage } from '@/types/message';
2
2
  import { safeParseJSON } from '@/utils/safeParseJSON';
3
3
 
4
4
  import { AgentRuntimeErrorType } from '../../error';
@@ -16,6 +16,13 @@ export interface StreamContext {
16
16
  * Same as Hunyuan and Wenxin
17
17
  */
18
18
  returnedCitation?: boolean;
19
+ /**
20
+ * Claude's citations are inline and interleaved with text output.
21
+ * Each text segment may carry references to sources (e.g., web search results)
22
+ * relevant to that specific portion of the generated content.
23
+ * This array accumulates all citation items received during the streaming response.
24
+ */
25
+ returnedCitationArray?: CitationItem[];
19
26
  thinking?: {
20
27
  id: string;
21
28
  name: string;