@lobehub/chat 1.69.1 → 1.69.3

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 (38) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/models.json +9 -0
  4. package/locales/bg-BG/models.json +9 -0
  5. package/locales/de-DE/models.json +9 -0
  6. package/locales/en-US/models.json +9 -0
  7. package/locales/es-ES/models.json +9 -0
  8. package/locales/fa-IR/models.json +9 -0
  9. package/locales/fr-FR/models.json +9 -0
  10. package/locales/it-IT/models.json +9 -0
  11. package/locales/ja-JP/models.json +9 -0
  12. package/locales/ko-KR/models.json +9 -0
  13. package/locales/nl-NL/models.json +9 -0
  14. package/locales/pl-PL/models.json +9 -0
  15. package/locales/pt-BR/models.json +9 -0
  16. package/locales/ru-RU/models.json +9 -0
  17. package/locales/tr-TR/models.json +9 -0
  18. package/locales/vi-VN/models.json +9 -0
  19. package/locales/zh-CN/models.json +9 -0
  20. package/locales/zh-TW/models.json +9 -0
  21. package/package.json +3 -3
  22. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +161 -0
  23. package/src/app/[variants]/(auth)/next-auth/signin/page.tsx +11 -0
  24. package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx +1 -1
  25. package/src/{app/[variants]/(main)/profile/(home)/features/SSOProvidersList → components/NextAuth}/AuthIcons.tsx +8 -6
  26. package/src/libs/agent-runtime/UniformRuntime/index.ts +114 -0
  27. package/src/libs/agent-runtime/anthropic/handleAnthropicError.ts +15 -0
  28. package/src/libs/agent-runtime/anthropic/index.test.ts +10 -1
  29. package/src/libs/agent-runtime/anthropic/index.ts +58 -40
  30. package/src/libs/agent-runtime/azureai/index.ts +7 -1
  31. package/src/libs/agent-runtime/github/index.ts +20 -25
  32. package/src/libs/agent-runtime/index.ts +2 -0
  33. package/src/libs/agent-runtime/openai/index.ts +2 -22
  34. package/src/libs/agent-runtime/types/type.ts +1 -1
  35. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +11 -7
  36. package/src/libs/agent-runtime/utils/openaiHelpers.ts +22 -0
  37. package/src/libs/next-auth/auth.config.ts +1 -0
  38. package/src/middleware.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.69.1",
3
+ "version": "1.69.3",
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",
@@ -133,7 +133,7 @@
133
133
  "@lobehub/chat-plugins-gateway": "^1.9.0",
134
134
  "@lobehub/icons": "^1.73.1",
135
135
  "@lobehub/tts": "^1.28.0",
136
- "@lobehub/ui": "^1.165.2",
136
+ "@lobehub/ui": "^1.165.5",
137
137
  "@neondatabase/serverless": "^0.10.4",
138
138
  "@next/third-parties": "^15.2.0",
139
139
  "@react-spring/web": "^9.7.5",
@@ -165,7 +165,7 @@
165
165
  "epub2": "^3.0.2",
166
166
  "fast-deep-equal": "^3.1.3",
167
167
  "file-type": "^20.0.0",
168
- "framer-motion": "^11.16.0",
168
+ "framer-motion": "^12.0.0",
169
169
  "gpt-tokenizer": "^2.8.1",
170
170
  "html-to-text": "^9.0.5",
171
171
  "i18next": "^24.2.1",
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+
3
+ import { LobeChat } from '@lobehub/ui/brand';
4
+ import { Button, Col, Flex, Row, Skeleton, Typography } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import { AuthError } from 'next-auth';
7
+ import { signIn } from 'next-auth/react';
8
+ import { useRouter, useSearchParams } from 'next/navigation';
9
+ import { memo } from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+
12
+ import BrandWatermark from '@/components/BrandWatermark';
13
+ import AuthIcons from '@/components/NextAuth/AuthIcons';
14
+ import { DOCUMENTS_REFER_URL, PRIVACY_URL, TERMS_URL } from '@/const/url';
15
+ import { useUserStore } from '@/store/user';
16
+
17
+ const { Title, Paragraph } = Typography;
18
+
19
+ const useStyles = createStyles(({ css, token }) => ({
20
+ button: css`
21
+ text-transform: capitalize;
22
+ `,
23
+ container: css`
24
+ min-width: 360px;
25
+ border: 1px solid ${token.colorBorder};
26
+ border-radius: ${token.borderRadiusLG}px;
27
+ background: ${token.colorBgContainer};
28
+ `,
29
+ contentCard: css`
30
+ padding-block: 2.5rem;
31
+ padding-inline: 2rem;
32
+ `,
33
+ description: css`
34
+ margin: 0;
35
+ color: ${token.colorTextSecondary};
36
+ `,
37
+ footer: css`
38
+ padding: 1rem;
39
+ border-block-start: 1px solid ${token.colorBorder};
40
+ border-radius: 0 0 8px 8px;
41
+
42
+ color: ${token.colorTextDescription};
43
+
44
+ background: ${token.colorBgElevated};
45
+ `,
46
+ text: css`
47
+ text-align: center;
48
+ `,
49
+ title: css`
50
+ margin: 0;
51
+ color: ${token.colorTextHeading};
52
+ `,
53
+ }));
54
+
55
+ const BtnListLoading = memo(() => {
56
+ return (
57
+ <Flex gap={'small'} vertical>
58
+ <Skeleton.Button active style={{ minWidth: 300 }} />
59
+ <Skeleton.Button active style={{ minWidth: 300 }} />
60
+ <Skeleton.Button active style={{ minWidth: 300 }} />
61
+ </Flex>
62
+ );
63
+ });
64
+
65
+ /**
66
+ * Follow the implementation from AuthJS official documentation,
67
+ * but using client components.
68
+ * ref: https://authjs.dev/guides/pages/signin
69
+ */
70
+ export default memo(() => {
71
+ const { styles } = useStyles();
72
+ const { t } = useTranslation('clerk');
73
+ const router = useRouter();
74
+
75
+ const oAuthSSOProviders = useUserStore((s) => s.oAuthSSOProviders);
76
+
77
+ const searchParams = useSearchParams();
78
+
79
+ // Redirect back to the page url
80
+ const callbackUrl = searchParams.get('callbackUrl') ?? '';
81
+
82
+ const handleSignIn = async (provider: string) => {
83
+ try {
84
+ await signIn(provider, { redirectTo: callbackUrl });
85
+ } catch (error) {
86
+ // Signin can fail for a number of reasons, such as the user
87
+ // not existing, or the user not having the correct role.
88
+ // In some cases, you may want to redirect to a custom error
89
+ if (error instanceof AuthError) {
90
+ return router.push(`/next-auth/?error=${error.type}`);
91
+ }
92
+
93
+ // Otherwise if a redirects happens Next.js can handle it
94
+ // so you can just re-thrown the error and let Next.js handle it.
95
+ // Docs: https://nextjs.org/docs/app/api-reference/functions/redirect#server-component
96
+ throw error;
97
+ }
98
+ };
99
+
100
+ const footerBtns = [
101
+ { href: DOCUMENTS_REFER_URL, id: 0, label: t('footerPageLink__help') },
102
+ { href: PRIVACY_URL, id: 1, label: t('footerPageLink__privacy') },
103
+ { href: TERMS_URL, id: 2, label: t('footerPageLink__terms') },
104
+ ];
105
+
106
+ return (
107
+ <div className={styles.container}>
108
+ <div className={styles.contentCard}>
109
+ {/* Card Body */}
110
+ <Flex gap="large" vertical>
111
+ {/* Header */}
112
+ <div className={styles.text}>
113
+ <Title className={styles.title} level={4}>
114
+ <div>
115
+ <LobeChat size={48} />
116
+ </div>
117
+ {t('signIn.start.title', { applicationName: 'LobeChat' })}
118
+ </Title>
119
+ <Paragraph className={styles.description}>{t('signIn.start.subtitle')}</Paragraph>
120
+ </div>
121
+ {/* Content */}
122
+ <Flex gap="small" vertical>
123
+ {oAuthSSOProviders ? (
124
+ oAuthSSOProviders.map((provider) => (
125
+ <Button
126
+ className={styles.button}
127
+ icon={AuthIcons(provider, 16)}
128
+ key={provider}
129
+ onClick={() => handleSignIn(provider)}
130
+ >
131
+ {provider}
132
+ </Button>
133
+ ))
134
+ ) : (
135
+ <BtnListLoading />
136
+ )}
137
+ </Flex>
138
+ </Flex>
139
+ </div>
140
+ <div className={styles.footer}>
141
+ {/* Footer */}
142
+ <Row>
143
+ <Col span={12}>
144
+ <Flex justify="left" style={{ height: '100%' }}>
145
+ <BrandWatermark />
146
+ </Flex>
147
+ </Col>
148
+ <Col offset={4} span={8}>
149
+ <Flex justify="right">
150
+ {footerBtns.map((btn) => (
151
+ <Button key={btn.id} onClick={() => router.push(btn.href)} size="small" type="text">
152
+ {btn.label}
153
+ </Button>
154
+ ))}
155
+ </Flex>
156
+ </Col>
157
+ </Row>
158
+ </div>
159
+ </div>
160
+ );
161
+ });
@@ -0,0 +1,11 @@
1
+ import { Suspense } from 'react';
2
+
3
+ import Loading from '@/components/Loading/BrandTextLoading';
4
+
5
+ import AuthSignInBox from './AuthSignInBox';
6
+
7
+ export default () => (
8
+ <Suspense fallback={<Loading />}>
9
+ <AuthSignInBox />
10
+ </Suspense>
11
+ );
@@ -10,7 +10,7 @@ import { userService } from '@/services/user';
10
10
  import { useUserStore } from '@/store/user';
11
11
  import { userProfileSelectors } from '@/store/user/selectors';
12
12
 
13
- import AuthIcons from './AuthIcons';
13
+ import AuthIcons from '@/components/NextAuth/AuthIcons';
14
14
 
15
15
  const { Item } = List;
16
16
 
@@ -12,10 +12,6 @@ import {
12
12
  } from '@lobehub/ui/icons';
13
13
  import React from 'react';
14
14
 
15
- const iconProps = {
16
- size: 32,
17
- };
18
-
19
15
  const iconComponents: { [key: string]: React.ElementType } = {
20
16
  'auth0': Auth0,
21
17
  'authelia': Authelia.Color,
@@ -29,9 +25,15 @@ const iconComponents: { [key: string]: React.ElementType } = {
29
25
  'zitadel': Zitadel.Color,
30
26
  };
31
27
 
32
- const AuthIcons = (id: string) => {
28
+ /**
29
+ * Get the auth icons component for the given id
30
+ * @param id
31
+ * @param size default is 36
32
+ * @returns
33
+ */
34
+ const AuthIcons = (id: string, size = 36) => {
33
35
  const IconComponent = iconComponents[id] || iconComponents.default;
34
- return <IconComponent {...iconProps} />;
36
+ return <IconComponent size={size}/>;
35
37
  };
36
38
 
37
39
  export default AuthIcons;
@@ -0,0 +1,114 @@
1
+ import { LobeRuntimeAI } from '../BaseAI';
2
+ import { LobeOpenAI } from '../openai';
3
+ import { providerRuntimeMap } from '../runtimeMap';
4
+ import {
5
+ ChatCompetitionOptions,
6
+ type ChatCompletionErrorPayload,
7
+ ChatStreamPayload,
8
+ EmbeddingsOptions,
9
+ EmbeddingsPayload,
10
+ TextToImagePayload,
11
+ TextToSpeechPayload,
12
+ } from '../types';
13
+
14
+ export interface RuntimeItem {
15
+ id: string;
16
+ models?: string[];
17
+ runtime: LobeRuntimeAI;
18
+ }
19
+
20
+ interface ProviderInitParams extends Record<string, any> {
21
+ accessKeyId?: string;
22
+ accessKeySecret?: string;
23
+ apiKey?: string;
24
+ apiVersion?: string;
25
+ baseURL?: string;
26
+ baseURLOrAccountID?: string;
27
+ dangerouslyAllowBrowser?: boolean;
28
+ region?: string;
29
+ sessionToken?: string;
30
+ }
31
+
32
+ interface ProviderInstance {
33
+ apiType: keyof typeof providerRuntimeMap;
34
+ models?: string[];
35
+ params: ProviderInitParams;
36
+ runtime?: typeof LobeOpenAI;
37
+ }
38
+
39
+ interface UniformRuntimeOptions {
40
+ chat?: {
41
+ handleError?: (error: any) => Omit<ChatCompletionErrorPayload, 'provider'> | undefined;
42
+ };
43
+ }
44
+
45
+ class UniformRuntime {
46
+ private _runtimes: RuntimeItem[];
47
+ private _options: UniformRuntimeOptions;
48
+
49
+ constructor(id: string, providers: ProviderInstance[], options: UniformRuntimeOptions) {
50
+ if (providers.length === 0) {
51
+ throw new Error('empty providers');
52
+ }
53
+
54
+ this._runtimes = providers.map((options) => {
55
+ const providerAI = options.runtime ?? providerRuntimeMap[options.apiType] ?? LobeOpenAI;
56
+ const runtime: LobeRuntimeAI = new providerAI({ ...options.params, id });
57
+
58
+ return { id: options.apiType, models: options.models, runtime };
59
+ });
60
+
61
+ this._options = options;
62
+ }
63
+
64
+ // 检查下是否能匹配到特定模型,否则默认使用第一个 runtime
65
+ getRuntimeByModel(model: string) {
66
+ const runtimeItem =
67
+ this._runtimes.find((runtime) => runtime.models && runtime.models.includes(model)) ||
68
+ this._runtimes[0];
69
+
70
+ return runtimeItem.runtime;
71
+ }
72
+
73
+ async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
74
+ try {
75
+ const runtime = this.getRuntimeByModel(payload.model);
76
+
77
+ return await runtime.chat(payload, options);
78
+ } catch (e) {
79
+ if (this._options.chat?.handleError) {
80
+ const error = this._options.chat.handleError(e);
81
+
82
+ if (error) {
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ throw e;
88
+ }
89
+ }
90
+
91
+ async textToImage(payload: TextToImagePayload) {
92
+ const runtime = this.getRuntimeByModel(payload.model);
93
+
94
+ return runtime.textToImage?.(payload);
95
+ }
96
+
97
+ async models() {
98
+ return this._runtimes[0].runtime.models?.();
99
+ }
100
+
101
+ async embeddings(payload: EmbeddingsPayload, options?: EmbeddingsOptions) {
102
+ const runtime = this.getRuntimeByModel(payload.model);
103
+
104
+ return runtime.embeddings?.(payload, options);
105
+ }
106
+
107
+ async textToSpeech(payload: TextToSpeechPayload, options?: EmbeddingsOptions) {
108
+ const runtime = this.getRuntimeByModel(payload.model);
109
+
110
+ return runtime.textToSpeech?.(payload, options);
111
+ }
112
+ }
113
+
114
+ export default UniformRuntime;
@@ -0,0 +1,15 @@
1
+ export const handleAnthropicError = (error: any) => {
2
+ let errorResult: any = error;
3
+
4
+ if (error.error) {
5
+ errorResult = error.error;
6
+
7
+ if ('error' in errorResult) {
8
+ errorResult = errorResult.error;
9
+ }
10
+ } else {
11
+ errorResult = { headers: error.headers, stack: error.stack, status: error.status };
12
+ }
13
+
14
+ return { errorResult };
15
+ };
@@ -44,6 +44,15 @@ describe('LobeAnthropicAI', () => {
44
44
  expect(instance).toBeInstanceOf(LobeAnthropicAI);
45
45
  expect(instance.baseURL).toBe('https://api.anthropic.proxy');
46
46
  });
47
+
48
+ it('should correctly initialize with different id', async () => {
49
+ const instance = new LobeAnthropicAI({
50
+ apiKey: 'test_api_key',
51
+ id: 'abc',
52
+ });
53
+ expect(instance).toBeInstanceOf(LobeAnthropicAI);
54
+ expect(instance['id']).toBe('abc');
55
+ });
47
56
  });
48
57
 
49
58
  describe('chat', () => {
@@ -347,7 +356,7 @@ describe('LobeAnthropicAI', () => {
347
356
  // Assert
348
357
  expect(e).toEqual({
349
358
  endpoint: 'https://api.anthropic.com',
350
- error: apiError,
359
+ error: apiError.error.error,
351
360
  errorType: bizErrorType,
352
361
  provider,
353
362
  });
@@ -2,18 +2,23 @@
2
2
  import '@anthropic-ai/sdk/shims/web';
3
3
  import Anthropic from '@anthropic-ai/sdk';
4
4
  import { ClientOptions } from 'openai';
5
+ import type { ChatModelCard } from '@/types/llm';
5
6
 
6
7
  import { LobeRuntimeAI } from '../BaseAI';
7
8
  import { AgentRuntimeErrorType } from '../error';
8
- import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
9
+ import {
10
+ ChatCompetitionOptions,
11
+ type ChatCompletionErrorPayload,
12
+ ChatStreamPayload,
13
+ ModelProvider,
14
+ } from '../types';
9
15
  import { AgentRuntimeError } from '../utils/createError';
10
16
  import { debugStream } from '../utils/debugStream';
11
17
  import { desensitizeUrl } from '../utils/desensitizeUrl';
12
18
  import { buildAnthropicMessages, buildAnthropicTools } from '../utils/anthropicHelpers';
13
19
  import { StreamingResponse } from '../utils/response';
14
20
  import { AnthropicStream } from '../utils/streams';
15
-
16
- import type { ChatModelCard } from '@/types/llm';
21
+ import { handleAnthropicError } from './handleAnthropicError';
17
22
 
18
23
  export interface AnthropicModelCard {
19
24
  display_name: string;
@@ -22,18 +27,24 @@ export interface AnthropicModelCard {
22
27
 
23
28
  const DEFAULT_BASE_URL = 'https://api.anthropic.com';
24
29
 
30
+ interface AnthropicAIParams extends ClientOptions {
31
+ id?: string;
32
+ }
33
+
25
34
  export class LobeAnthropicAI implements LobeRuntimeAI {
26
35
  private client: Anthropic;
27
36
 
28
37
  baseURL: string;
29
38
  apiKey?: string;
39
+ private id: string;
30
40
 
31
- constructor({ apiKey, baseURL = DEFAULT_BASE_URL, ...res }: ClientOptions = {}) {
41
+ constructor({ apiKey, baseURL = DEFAULT_BASE_URL, id, ...res }: AnthropicAIParams = {}) {
32
42
  if (!apiKey) throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
33
43
 
34
44
  this.client = new Anthropic({ apiKey, baseURL, ...res });
35
45
  this.baseURL = this.client.baseURL;
36
46
  this.apiKey = apiKey;
47
+ this.id = id || ModelProvider.Anthropic;
37
48
  }
38
49
 
39
50
  async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
@@ -57,42 +68,7 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
57
68
  headers: options?.headers,
58
69
  });
59
70
  } catch (error) {
60
- let desensitizedEndpoint = this.baseURL;
61
-
62
- if (this.baseURL !== DEFAULT_BASE_URL) {
63
- desensitizedEndpoint = desensitizeUrl(this.baseURL);
64
- }
65
-
66
- if ('status' in (error as any)) {
67
- switch ((error as Response).status) {
68
- case 401: {
69
- throw AgentRuntimeError.chat({
70
- endpoint: desensitizedEndpoint,
71
- error: error as any,
72
- errorType: AgentRuntimeErrorType.InvalidProviderAPIKey,
73
- provider: ModelProvider.Anthropic,
74
- });
75
- }
76
-
77
- case 403: {
78
- throw AgentRuntimeError.chat({
79
- endpoint: desensitizedEndpoint,
80
- error: error as any,
81
- errorType: AgentRuntimeErrorType.LocationNotSupportError,
82
- provider: ModelProvider.Anthropic,
83
- });
84
- }
85
- default: {
86
- break;
87
- }
88
- }
89
- }
90
- throw AgentRuntimeError.chat({
91
- endpoint: desensitizedEndpoint,
92
- error: error as any,
93
- errorType: AgentRuntimeErrorType.ProviderBizError,
94
- provider: ModelProvider.Anthropic,
95
- });
71
+ throw this.handleError(error);
96
72
  }
97
73
  }
98
74
 
@@ -191,6 +167,48 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
191
167
  })
192
168
  .filter(Boolean) as ChatModelCard[];
193
169
  }
170
+
171
+ private handleError(error: any): ChatCompletionErrorPayload {
172
+ let desensitizedEndpoint = this.baseURL;
173
+
174
+ if (this.baseURL !== DEFAULT_BASE_URL) {
175
+ desensitizedEndpoint = desensitizeUrl(this.baseURL);
176
+ }
177
+
178
+ if ('status' in (error as any)) {
179
+ switch ((error as Response).status) {
180
+ case 401: {
181
+ throw AgentRuntimeError.chat({
182
+ endpoint: desensitizedEndpoint,
183
+ error: error as any,
184
+ errorType: AgentRuntimeErrorType.InvalidProviderAPIKey,
185
+ provider: this.id,
186
+ });
187
+ }
188
+
189
+ case 403: {
190
+ throw AgentRuntimeError.chat({
191
+ endpoint: desensitizedEndpoint,
192
+ error: error as any,
193
+ errorType: AgentRuntimeErrorType.LocationNotSupportError,
194
+ provider: this.id,
195
+ });
196
+ }
197
+ default: {
198
+ break;
199
+ }
200
+ }
201
+ }
202
+
203
+ const { errorResult } = handleAnthropicError(error);
204
+
205
+ throw AgentRuntimeError.chat({
206
+ endpoint: desensitizedEndpoint,
207
+ error: errorResult,
208
+ errorType: AgentRuntimeErrorType.ProviderBizError,
209
+ provider: this.id,
210
+ });
211
+ }
194
212
  }
195
213
 
196
214
  export default LobeAnthropicAI;
@@ -13,10 +13,16 @@ import { transformResponseToStream } from '../utils/openaiCompatibleFactory';
13
13
  import { StreamingResponse } from '../utils/response';
14
14
  import { OpenAIStream, createSSEDataExtractor } from '../utils/streams';
15
15
 
16
+ interface AzureAIParams {
17
+ apiKey?: string;
18
+ apiVersion?: string;
19
+ baseURL?: string;
20
+ }
21
+
16
22
  export class LobeAzureAI implements LobeRuntimeAI {
17
23
  client: ModelClient;
18
24
 
19
- constructor(params?: { apiKey?: string; apiVersion?: string; baseURL?: string }) {
25
+ constructor(params?: AzureAIParams) {
20
26
  if (!params?.apiKey || !params?.baseURL)
21
27
  throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
22
28
 
@@ -1,9 +1,9 @@
1
+ import type { ChatModelCard } from '@/types/llm';
2
+
1
3
  import { AgentRuntimeErrorType } from '../error';
2
- import { pruneReasoningPayload } from '../openai';
3
4
  import { ModelProvider } from '../types';
4
5
  import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
5
-
6
- import type { ChatModelCard } from '@/types/llm';
6
+ import { pruneReasoningPayload } from '../utils/openaiHelpers';
7
7
 
8
8
  export interface GithubModelCard {
9
9
  description: string;
@@ -39,27 +39,20 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
39
39
  models: async ({ client }) => {
40
40
  const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
41
41
 
42
- const functionCallKeywords = [
43
- 'function',
44
- 'tool',
45
- ];
42
+ const functionCallKeywords = ['function', 'tool'];
46
43
 
47
- const visionKeywords = [
48
- 'vision',
49
- ];
44
+ const visionKeywords = ['vision'];
50
45
 
51
- const reasoningKeywords = [
52
- 'deepseek-r1',
53
- 'o1',
54
- 'o3',
55
- ];
46
+ const reasoningKeywords = ['deepseek-r1', 'o1', 'o3'];
56
47
 
57
48
  const modelsPage = (await client.models.list()) as any;
58
49
  const modelList: GithubModelCard[] = modelsPage.body;
59
50
 
60
51
  return modelList
61
52
  .map((model) => {
62
- const knownModel = LOBE_DEFAULT_MODEL_LIST.find((m) => model.name.toLowerCase() === m.id.toLowerCase());
53
+ const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
54
+ (m) => model.name.toLowerCase() === m.id.toLowerCase(),
55
+ );
63
56
 
64
57
  return {
65
58
  contextWindowTokens: knownModel?.contextWindowTokens ?? undefined,
@@ -67,18 +60,20 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
67
60
  displayName: model.friendly_name,
68
61
  enabled: knownModel?.enabled || false,
69
62
  functionCall:
70
- functionCallKeywords.some(keyword => model.description.toLowerCase().includes(keyword))
71
- || knownModel?.abilities?.functionCall
72
- || false,
63
+ functionCallKeywords.some((keyword) =>
64
+ model.description.toLowerCase().includes(keyword),
65
+ ) ||
66
+ knownModel?.abilities?.functionCall ||
67
+ false,
73
68
  id: model.name,
74
69
  reasoning:
75
- reasoningKeywords.some(keyword => model.name.toLowerCase().includes(keyword))
76
- || knownModel?.abilities?.reasoning
77
- || false,
70
+ reasoningKeywords.some((keyword) => model.name.toLowerCase().includes(keyword)) ||
71
+ knownModel?.abilities?.reasoning ||
72
+ false,
78
73
  vision:
79
- visionKeywords.some(keyword => model.description.toLowerCase().includes(keyword))
80
- || knownModel?.abilities?.vision
81
- || false,
74
+ visionKeywords.some((keyword) => model.description.toLowerCase().includes(keyword)) ||
75
+ knownModel?.abilities?.vision ||
76
+ false,
82
77
  };
83
78
  })
84
79
  .filter(Boolean) as ChatModelCard[];
@@ -19,6 +19,8 @@ export { LobeQwenAI } from './qwen';
19
19
  export { LobeTogetherAI } from './togetherai';
20
20
  export * from './types';
21
21
  export { AgentRuntimeError } from './utils/createError';
22
+ export { LobeOpenAICompatibleFactory } from './utils/openaiCompatibleFactory';
23
+ export { pruneReasoningPayload } from './utils/openaiHelpers';
22
24
  export { LobeVolcengineAI } from './volcengine';
23
25
  export { LobeZeroOneAI } from './zeroone';
24
26
  export { LobeZhipuAI } from './zhipu';
@@ -1,33 +1,13 @@
1
- import { disableStreamModels, systemToUserModels } from '@/const/models';
2
1
  import type { ChatModelCard } from '@/types/llm';
3
2
 
4
- import { ChatStreamPayload, ModelProvider, OpenAIChatMessage } from '../types';
3
+ import { ModelProvider } from '../types';
5
4
  import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
5
+ import { pruneReasoningPayload } from '../utils/openaiHelpers';
6
6
 
7
7
  export interface OpenAIModelCard {
8
8
  id: string;
9
9
  }
10
10
 
11
- export const pruneReasoningPayload = (payload: ChatStreamPayload) => {
12
- return {
13
- ...payload,
14
- frequency_penalty: 0,
15
- messages: payload.messages.map((message: OpenAIChatMessage) => ({
16
- ...message,
17
- role:
18
- message.role === 'system'
19
- ? systemToUserModels.has(payload.model)
20
- ? 'user'
21
- : 'developer'
22
- : message.role,
23
- })),
24
- presence_penalty: 0,
25
- stream: !disableStreamModels.has(payload.model),
26
- temperature: 1,
27
- top_p: 1,
28
- };
29
- };
30
-
31
11
  export const LobeOpenAI = LobeOpenAICompatibleFactory({
32
12
  baseURL: 'https://api.openai.com/v1',
33
13
  chatCompletion: {