@lobehub/chat 1.134.6 → 1.134.7
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 +33 -0
- package/changelog/v1.json +9 -0
- package/docs/development/basic/feature-development-frontend.zh-CN.mdx +1 -1
- package/docs/development/basic/folder-structure.mdx +67 -16
- package/docs/development/basic/folder-structure.zh-CN.mdx +67 -16
- package/locales/ar/modelProvider.json +15 -0
- package/locales/bg-BG/modelProvider.json +15 -0
- package/locales/de-DE/modelProvider.json +15 -0
- package/locales/en-US/modelProvider.json +15 -0
- package/locales/es-ES/modelProvider.json +15 -0
- package/locales/fa-IR/modelProvider.json +15 -0
- package/locales/fr-FR/modelProvider.json +15 -0
- package/locales/it-IT/modelProvider.json +15 -0
- package/locales/ja-JP/modelProvider.json +15 -0
- package/locales/ko-KR/modelProvider.json +15 -0
- package/locales/nl-NL/modelProvider.json +15 -0
- package/locales/pl-PL/modelProvider.json +15 -0
- package/locales/pt-BR/modelProvider.json +15 -0
- package/locales/ru-RU/modelProvider.json +15 -0
- package/locales/tr-TR/modelProvider.json +15 -0
- package/locales/vi-VN/modelProvider.json +15 -0
- package/locales/zh-CN/modelProvider.json +15 -0
- package/locales/zh-TW/modelProvider.json +15 -0
- package/package.json +1 -1
- package/packages/model-runtime/src/providers/azureOpenai/index.ts +5 -1
- package/packages/model-runtime/src/providers/azureai/index.ts +5 -1
- package/packages/model-runtime/src/utils/sanitizeError.test.ts +109 -0
- package/packages/model-runtime/src/utils/sanitizeError.ts +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 1.134.7](https://github.com/lobehub/lobe-chat/compare/v1.134.6...v1.134.7)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-10-06**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **security**: Sanitize Azure provider error responses to prevent API key exposure.
|
|
12
|
+
|
|
13
|
+
#### 💄 Styles
|
|
14
|
+
|
|
15
|
+
- **misc**: Update i18n.
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
21
|
+
|
|
22
|
+
#### What's fixed
|
|
23
|
+
|
|
24
|
+
- **security**: Sanitize Azure provider error responses to prevent API key exposure, closes [#9583](https://github.com/lobehub/lobe-chat/issues/9583) ([af59bfe](https://github.com/lobehub/lobe-chat/commit/af59bfe))
|
|
25
|
+
|
|
26
|
+
#### Styles
|
|
27
|
+
|
|
28
|
+
- **misc**: Update i18n, closes [#9580](https://github.com/lobehub/lobe-chat/issues/9580) ([c0974ea](https://github.com/lobehub/lobe-chat/commit/c0974ea))
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<div align="right">
|
|
33
|
+
|
|
34
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
5
38
|
### [Version 1.134.6](https://github.com/lobehub/lobe-chat/compare/v1.134.5...v1.134.6)
|
|
6
39
|
|
|
7
40
|
<sup>Released on **2025-10-05**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -4,37 +4,88 @@ The directory structure of LobeChat is as follows:
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
src
|
|
7
|
-
├── app #
|
|
7
|
+
├── app # Next.js App Router implementation with route groups and API routes
|
|
8
8
|
├── components # Reusable UI components
|
|
9
9
|
├── config # Application configuration files, including client-side and server-side environment variables
|
|
10
|
-
├── const # Used to define constants, such as action types, route names, etc.
|
|
11
10
|
├── features # Function modules related to business functions, such as agent settings, plugin development pop-ups, etc.
|
|
12
11
|
├── hooks # Custom utility hooks reused throughout the application
|
|
13
12
|
├── layout # Application layout components, such as navigation bars, sidebars, etc.
|
|
13
|
+
├── libs # Third-party integrations (analytics, OIDC, etc.)
|
|
14
14
|
├── locales # Internationalization language files
|
|
15
|
+
├── server # Server-side modules and services
|
|
15
16
|
├── services # Encapsulated backend service interfaces, such as HTTP requests
|
|
16
17
|
├── store # Zustand store for state management
|
|
18
|
+
├── styles # Global styles and CSS-in-JS configurations
|
|
17
19
|
├── types # TypeScript type definition files
|
|
18
20
|
└── utils # Common utility functions
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
## app
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
The `app` directory follows Next.js 13+ App Router conventions with a sophisticated architecture using [Route Groups](https://nextjs.org/docs/app/building-your-application/routing/route-groups) to organize backend services, platform variants, and application routes:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
27
|
-
├── (
|
|
28
|
-
│ ├──
|
|
29
|
-
│ ├──
|
|
30
|
-
│ └──
|
|
31
|
-
├──
|
|
32
|
-
│ ├──
|
|
33
|
-
│ ├──
|
|
34
|
-
│
|
|
35
|
-
├──
|
|
36
|
-
│
|
|
37
|
-
|
|
28
|
+
app
|
|
29
|
+
├── (backend)/ # Backend API routes and services
|
|
30
|
+
│ ├── api/ # REST API endpoints
|
|
31
|
+
│ │ ├── auth/ # Authentication routes
|
|
32
|
+
│ │ └── webhooks/ # Webhook handlers
|
|
33
|
+
│ ├── middleware/ # Request middleware
|
|
34
|
+
│ ├── oidc/ # OpenID Connect routes
|
|
35
|
+
│ ├── trpc/ # tRPC API endpoints
|
|
36
|
+
│ │ ├── async/ # Async tRPC routes
|
|
37
|
+
│ │ ├── desktop/ # Desktop-specific tRPC routes
|
|
38
|
+
│ │ ├── edge/ # Edge runtime tRPC routes
|
|
39
|
+
│ │ ├── lambda/ # Lambda tRPC routes
|
|
40
|
+
│ │ └── tools/ # Tools tRPC routes
|
|
41
|
+
│ └── webapi/ # Web API endpoints
|
|
42
|
+
│ ├── chat/ # Chat-related APIs
|
|
43
|
+
│ ├── models/ # Model management APIs
|
|
44
|
+
│ ├── tts/ # Text-to-speech APIs
|
|
45
|
+
│ └── ...
|
|
46
|
+
├── [variants]/ # Platform and device variants
|
|
47
|
+
│ ├── (auth)/ # Authentication pages
|
|
48
|
+
│ │ ├── login/
|
|
49
|
+
│ │ ├── signup/
|
|
50
|
+
│ │ └── next-auth/
|
|
51
|
+
│ ├── (main)/ # Main application routes
|
|
52
|
+
│ │ ├── (mobile)/ # Mobile-specific routes
|
|
53
|
+
│ │ │ └── me/ # Mobile profile pages
|
|
54
|
+
│ │ ├── _layout/ # Layout components
|
|
55
|
+
│ │ ├── chat/ # Chat interface
|
|
56
|
+
│ │ ├── discover/ # Discovery pages
|
|
57
|
+
│ │ ├── files/ # File management
|
|
58
|
+
│ │ ├── image/ # Image generation
|
|
59
|
+
│ │ ├── profile/ # User profile
|
|
60
|
+
│ │ ├── repos/ # Repository management
|
|
61
|
+
│ │ └── settings/ # Application settings
|
|
62
|
+
│ └── @modal/ # Parallel modal routes
|
|
63
|
+
│ ├── (.)changelog/
|
|
64
|
+
│ └── _layout/
|
|
65
|
+
├── desktop/ # Desktop-specific routes
|
|
66
|
+
│ └── devtools/
|
|
67
|
+
├── manifest.ts # PWA manifest
|
|
68
|
+
├── robots.tsx # Robots.txt generation
|
|
69
|
+
├── sitemap.tsx # Sitemap generation
|
|
70
|
+
└── sw.ts # Service worker
|
|
38
71
|
```
|
|
39
72
|
|
|
40
|
-
|
|
73
|
+
### Architecture Explanation
|
|
74
|
+
|
|
75
|
+
**Route Groups:**
|
|
76
|
+
- `(backend)` - Contains all server-side API routes, middleware, and backend services
|
|
77
|
+
- `[variants]` - Dynamic route group handling different platform variants and main application pages
|
|
78
|
+
- `@modal` - Parallel routes for modal dialogs using Next.js parallel routing
|
|
79
|
+
|
|
80
|
+
**Platform Organization:**
|
|
81
|
+
- The architecture supports multiple platforms (web, desktop, mobile) through route organization
|
|
82
|
+
- Desktop-specific routes are in the `desktop/` directory
|
|
83
|
+
- Mobile-specific routes are organized under `(main)/(mobile)/`
|
|
84
|
+
- Shared layouts and components are in `_layout/` directories
|
|
85
|
+
|
|
86
|
+
**API Architecture:**
|
|
87
|
+
- REST APIs in `(backend)/api/` and `(backend)/webapi/`
|
|
88
|
+
- tRPC endpoints organized by runtime environment (edge, lambda, async, desktop)
|
|
89
|
+
- Authentication and OIDC handling in dedicated route groups
|
|
90
|
+
|
|
91
|
+
This architecture provides clear separation of concerns while maintaining flexibility for different deployment targets and runtime environments.
|
|
@@ -4,37 +4,88 @@ LobeChat 的文件夹目录架构如下:
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
src
|
|
7
|
-
├── app #
|
|
7
|
+
├── app # Next.js App Router 实现,包含路由组和 API 路由
|
|
8
8
|
├── components # 可复用的 UI 组件
|
|
9
9
|
├── config # 应用的配置文件,包含客户端环境变量与服务端环境变量
|
|
10
|
-
├── const # 用于定义常量,如 action 类型、路由名等
|
|
11
10
|
├── features # 与业务功能相关的功能模块,如 Agent 设置、插件开发弹窗等
|
|
12
11
|
├── hooks # 全应用复用自定义的工具 Hooks
|
|
13
12
|
├── layout # 应用的布局组件,如导航栏、侧边栏等
|
|
13
|
+
├── libs # 第三方集成(分析、OIDC 等)
|
|
14
14
|
├── locales # 国际化的语言文件
|
|
15
|
+
├── server # 服务端模块和服务
|
|
15
16
|
├── services # 封装的后端服务接口,如 HTTP 请求
|
|
16
17
|
├── store # 用于状态管理的 zustand store
|
|
18
|
+
├── styles # 全局样式和 CSS-in-JS 配置
|
|
17
19
|
├── types # TypeScript 的类型定义文件
|
|
18
20
|
└── utils # 通用的工具函数
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
## app
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
`app` 目录遵循 Next.js 13+ App Router 约定,采用复杂的架构,使用 [路由组](https://nextjs.org/docs/app/building-your-application/routing/route-groups) 来组织后端服务、平台变体和应用路由:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
27
|
-
├── (
|
|
28
|
-
│ ├──
|
|
29
|
-
│ ├──
|
|
30
|
-
│ └──
|
|
31
|
-
├──
|
|
32
|
-
│ ├──
|
|
33
|
-
│ ├──
|
|
34
|
-
│
|
|
35
|
-
├──
|
|
36
|
-
│
|
|
37
|
-
|
|
28
|
+
app
|
|
29
|
+
├── (backend)/ # 后端 API 路由和服务
|
|
30
|
+
│ ├── api/ # REST API 端点
|
|
31
|
+
│ │ ├── auth/ # 身份验证路由
|
|
32
|
+
│ │ └── webhooks/ # Webhook 处理器
|
|
33
|
+
│ ├── middleware/ # 请求中间件
|
|
34
|
+
│ ├── oidc/ # OpenID Connect 路由
|
|
35
|
+
│ ├── trpc/ # tRPC API 端点
|
|
36
|
+
│ │ ├── async/ # 异步 tRPC 路由
|
|
37
|
+
│ │ ├── desktop/ # 桌面端专用 tRPC 路由
|
|
38
|
+
│ │ ├── edge/ # Edge 运行时 tRPC 路由
|
|
39
|
+
│ │ ├── lambda/ # Lambda tRPC 路由
|
|
40
|
+
│ │ └── tools/ # 工具 tRPC 路由
|
|
41
|
+
│ └── webapi/ # Web API 端点
|
|
42
|
+
│ ├── chat/ # 聊天相关 API
|
|
43
|
+
│ ├── models/ # 模型管理 API
|
|
44
|
+
│ ├── tts/ # 文本转语音 API
|
|
45
|
+
│ └── ...
|
|
46
|
+
├── [variants]/ # 平台和设备变体
|
|
47
|
+
│ ├── (auth)/ # 身份验证页面
|
|
48
|
+
│ │ ├── login/
|
|
49
|
+
│ │ ├── signup/
|
|
50
|
+
│ │ └── next-auth/
|
|
51
|
+
│ ├── (main)/ # 主应用路由
|
|
52
|
+
│ │ ├── (mobile)/ # 移动端专用路由
|
|
53
|
+
│ │ │ └── me/ # 移动端个人资料页面
|
|
54
|
+
│ │ ├── _layout/ # 布局组件
|
|
55
|
+
│ │ ├── chat/ # 聊天界面
|
|
56
|
+
│ │ ├── discover/ # 发现页面
|
|
57
|
+
│ │ ├── files/ # 文件管理
|
|
58
|
+
│ │ ├── image/ # 图像生成
|
|
59
|
+
│ │ ├── profile/ # 用户资料
|
|
60
|
+
│ │ ├── repos/ # 仓库管理
|
|
61
|
+
│ │ └── settings/ # 应用设置
|
|
62
|
+
│ └── @modal/ # 并行模态框路由
|
|
63
|
+
│ ├── (.)changelog/
|
|
64
|
+
│ └── _layout/
|
|
65
|
+
├── desktop/ # 桌面端专用路由
|
|
66
|
+
│ └── devtools/
|
|
67
|
+
├── manifest.ts # PWA 清单
|
|
68
|
+
├── robots.tsx # Robots.txt 生成
|
|
69
|
+
├── sitemap.tsx # 站点地图生成
|
|
70
|
+
└── sw.ts # Service Worker
|
|
38
71
|
```
|
|
39
72
|
|
|
40
|
-
|
|
73
|
+
### 架构说明
|
|
74
|
+
|
|
75
|
+
**路由组:**
|
|
76
|
+
- `(backend)` - 包含所有服务端 API 路由、中间件和后端服务
|
|
77
|
+
- `[variants]` - 处理不同平台变体和主应用页面的动态路由组
|
|
78
|
+
- `@modal` - 使用 Next.js 并行路由的模态框对话框并行路由
|
|
79
|
+
|
|
80
|
+
**平台组织:**
|
|
81
|
+
- 架构通过路由组织支持多个平台(Web、桌面端、移动端)
|
|
82
|
+
- 桌面端专用路由位于 `desktop/` 目录中
|
|
83
|
+
- 移动端专用路由组织在 `(main)/(mobile)/` 下
|
|
84
|
+
- 共享布局和组件位于 `_layout/` 目录中
|
|
85
|
+
|
|
86
|
+
**API 架构:**
|
|
87
|
+
- `(backend)/api/` 和 `(backend)/webapi/` 中的 REST API
|
|
88
|
+
- 按运行时环境组织的 tRPC 端点(edge、lambda、async、desktop)
|
|
89
|
+
- 专用路由组中的身份验证和 OIDC 处理
|
|
90
|
+
|
|
91
|
+
这种架构在保持不同部署目标和运行时环境灵活性的同时,提供了清晰的关注点分离。
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "أقصى نافذة سياق",
|
|
295
295
|
"unlimited": "غير محدود"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "أنواع النماذج المختلفة تمتلك سيناريوهات استخدام وقدرات مميزة",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "محادثة",
|
|
301
|
+
"embedding": "تضمين",
|
|
302
|
+
"image": "توليد الصور",
|
|
303
|
+
"realtime": "محادثة فورية",
|
|
304
|
+
"stt": "تحويل الصوت إلى نص",
|
|
305
|
+
"text2music": "تحويل النص إلى موسيقى",
|
|
306
|
+
"text2video": "تحويل النص إلى فيديو",
|
|
307
|
+
"tts": "تحويل النص إلى كلام"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "يرجى اختيار نوع النموذج",
|
|
310
|
+
"title": "نوع النموذج"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "سيؤدي هذا التكوين إلى فتح إعدادات تحميل الصور في التطبيق، ما إذا كان يدعم التعرف يعتمد بالكامل على النموذج نفسه، يرجى اختبار قابلية استخدام التعرف البصري لهذا النموذج بنفسك",
|
|
299
314
|
"title": "دعم التعرف البصري"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Максимален контекстуален прозорец",
|
|
295
295
|
"unlimited": "Без ограничения"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Различните типове модели имат различни сценарии на използване и възможности",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Чат",
|
|
301
|
+
"embedding": "Векторизация",
|
|
302
|
+
"image": "Генериране на изображения",
|
|
303
|
+
"realtime": "Реално време чат",
|
|
304
|
+
"stt": "Гласов текст",
|
|
305
|
+
"text2music": "Текст към музика",
|
|
306
|
+
"text2video": "Текст към видео",
|
|
307
|
+
"tts": "Гласово синтезиране"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Моля, изберете тип модел",
|
|
310
|
+
"title": "Тип модел"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Тази конфигурация ще активира само конфигурацията за качване на изображения в приложението, дали поддържа разпознаване зависи изцяло от самия модел, моля, тествайте наличността на визуалната разпознаваемост на този модел.",
|
|
299
314
|
"title": "Поддръжка на визуално разпознаване"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Maximales Kontextfenster",
|
|
295
295
|
"unlimited": "Unbegrenzt"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Verschiedene Modelltypen haben unterschiedliche Anwendungsbereiche und Fähigkeiten",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Chat",
|
|
301
|
+
"embedding": "Vektorisierung",
|
|
302
|
+
"image": "Bildgenerierung",
|
|
303
|
+
"realtime": "Echtzeit-Chat",
|
|
304
|
+
"stt": "Spracherkennung",
|
|
305
|
+
"text2music": "Text zu Musik",
|
|
306
|
+
"text2video": "Text zu Video",
|
|
307
|
+
"tts": "Sprachsynthese"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Bitte Modelltyp auswählen",
|
|
310
|
+
"title": "Modelltyp"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Diese Konfiguration aktiviert nur die Bild-Upload-Funktionalität in der Anwendung. Ob die Erkennung unterstützt wird, hängt vollständig vom Modell selbst ab. Bitte testen Sie die Verwendbarkeit der visuellen Erkennungsfähigkeit des Modells selbst.",
|
|
299
314
|
"title": "Visuelle Erkennung unterstützen"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Maximum Context Window",
|
|
295
295
|
"unlimited": "Unlimited"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Different model types have distinct use cases and capabilities",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Chat",
|
|
301
|
+
"embedding": "Embedding",
|
|
302
|
+
"image": "Image Generation",
|
|
303
|
+
"realtime": "Real-time Chat",
|
|
304
|
+
"stt": "Speech-to-Text",
|
|
305
|
+
"text2music": "Text-to-Music",
|
|
306
|
+
"text2video": "Text-to-Video",
|
|
307
|
+
"tts": "Text-to-Speech"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Please select a model type",
|
|
310
|
+
"title": "Model Type"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "This configuration will only enable image upload capabilities in the application. Whether recognition is supported depends entirely on the model itself. Please test the visual recognition capabilities of the model yourself.",
|
|
299
314
|
"title": "Support Vision"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Máximo de ventana de contexto",
|
|
295
295
|
"unlimited": "Sin límite"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Diferentes tipos de modelos tienen escenarios de uso y capacidades diferenciadas",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Chat",
|
|
301
|
+
"embedding": "Vectorización",
|
|
302
|
+
"image": "Generación de imágenes",
|
|
303
|
+
"realtime": "Chat en tiempo real",
|
|
304
|
+
"stt": "Reconocimiento de voz a texto",
|
|
305
|
+
"text2music": "Texto a música",
|
|
306
|
+
"text2video": "Texto a video",
|
|
307
|
+
"tts": "Síntesis de voz"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Por favor, seleccione el tipo de modelo",
|
|
310
|
+
"title": "Tipo de modelo"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Esta configuración solo habilitará la configuración de carga de imágenes en la aplicación, si se admite el reconocimiento depende completamente del modelo en sí, prueba la disponibilidad de la capacidad de reconocimiento visual de este modelo.",
|
|
299
314
|
"title": "Soporte para reconocimiento visual"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "حداکثر پنجره زمینه",
|
|
295
295
|
"unlimited": "بدون محدودیت"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "انواع مختلف مدلها دارای سناریوها و قابلیتهای متفاوتی هستند",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "گفتگو",
|
|
301
|
+
"embedding": "بردارسازی",
|
|
302
|
+
"image": "تولید تصویر",
|
|
303
|
+
"realtime": "گفتگوی زنده",
|
|
304
|
+
"stt": "تبدیل گفتار به متن",
|
|
305
|
+
"text2music": "تبدیل متن به موسیقی",
|
|
306
|
+
"text2video": "تبدیل متن به ویدئو",
|
|
307
|
+
"tts": "تبدیل متن به گفتار"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "لطفاً نوع مدل را انتخاب کنید",
|
|
310
|
+
"title": "نوع مدل"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "این پیکربندی تنها قابلیت بارگذاری تصویر در برنامه را فعال میکند، اینکه آیا شناسایی پشتیبانی میشود به خود مدل بستگی دارد، لطفاً قابلیت استفاده از شناسایی بصری این مدل را آزمایش کنید",
|
|
299
314
|
"title": "پشتیبانی از شناسایی بصری"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Fenêtre de contexte maximale",
|
|
295
295
|
"unlimited": "Illimité"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Différents types de modèles possèdent des scénarios d'utilisation et des capacités distincts",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Conversation",
|
|
301
|
+
"embedding": "Vectorisation",
|
|
302
|
+
"image": "Génération d'images",
|
|
303
|
+
"realtime": "Dialogue en temps réel",
|
|
304
|
+
"stt": "Reconnaissance vocale",
|
|
305
|
+
"text2music": "Texte en musique",
|
|
306
|
+
"text2video": "Texte en vidéo",
|
|
307
|
+
"tts": "Synthèse vocale"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Veuillez sélectionner un type de modèle",
|
|
310
|
+
"title": "Type de modèle"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Cette configuration n'activera que la configuration de téléchargement d'images dans l'application, la prise en charge de la reconnaissance dépend entièrement du modèle lui-même, veuillez tester la disponibilité des capacités de reconnaissance visuelle de ce modèle.",
|
|
299
314
|
"title": "Reconnaissance visuelle prise en charge"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Finestra di contesto massima",
|
|
295
295
|
"unlimited": "Illimitato"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Diversi tipi di modelli hanno scenari d'uso e capacità differenziate",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Conversazione",
|
|
301
|
+
"embedding": "Vettorializzazione",
|
|
302
|
+
"image": "Generazione di immagini",
|
|
303
|
+
"realtime": "Conversazione in tempo reale",
|
|
304
|
+
"stt": "Trascrizione vocale",
|
|
305
|
+
"text2music": "Testo in musica",
|
|
306
|
+
"text2video": "Testo in video",
|
|
307
|
+
"tts": "Sintesi vocale"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Seleziona il tipo di modello",
|
|
310
|
+
"title": "Tipo di modello"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Questa configurazione abiliterà solo la configurazione di caricamento immagini nell'app, la disponibilità di riconoscimento dipende interamente dal modello stesso, testare autonomamente la disponibilità di riconoscimento visivo di questo modello.",
|
|
299
314
|
"title": "Supporto per riconoscimento visivo"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "最大コンテキストウィンドウ",
|
|
295
295
|
"unlimited": "無制限"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "異なるモデルタイプは、それぞれ異なる使用シーンと能力を持っています",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "チャット",
|
|
301
|
+
"embedding": "ベクトル化",
|
|
302
|
+
"image": "画像生成",
|
|
303
|
+
"realtime": "リアルタイムチャット",
|
|
304
|
+
"stt": "音声からテキストへ",
|
|
305
|
+
"text2music": "テキストから音楽へ",
|
|
306
|
+
"text2video": "テキストから動画へ",
|
|
307
|
+
"tts": "音声合成"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "モデルタイプを選択してください",
|
|
310
|
+
"title": "モデルタイプ"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "この設定はアプリ内の画像アップロード設定のみを有効にします。認識のサポートはモデル自体に依存しますので、そのモデルの視覚認識機能の可用性を自分でテストしてください",
|
|
299
314
|
"title": "視覚認識をサポート"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "최대 컨텍스트 창",
|
|
295
295
|
"unlimited": "제한 없음"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "다양한 모델 유형은 차별화된 사용 시나리오와 기능을 제공합니다",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "대화",
|
|
301
|
+
"embedding": "벡터화",
|
|
302
|
+
"image": "이미지 생성",
|
|
303
|
+
"realtime": "실시간 대화",
|
|
304
|
+
"stt": "음성 인식",
|
|
305
|
+
"text2music": "텍스트에서 음악으로",
|
|
306
|
+
"text2video": "텍스트에서 비디오로",
|
|
307
|
+
"tts": "음성 합성"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "모델 유형을 선택하세요",
|
|
310
|
+
"title": "모델 유형"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "이 설정은 애플리케이션 내에서 이미지 업로드 기능만 활성화합니다. 인식 지원 여부는 모델 자체에 따라 다르므로, 해당 모델의 시각 인식 가능성을 스스로 테스트하세요.",
|
|
299
314
|
"title": "시각 인식 지원"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Maximale contextvenster",
|
|
295
295
|
"unlimited": "Onbeperkt"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Verschillende modeltypen hebben verschillende toepassingsscenario's en mogelijkheden",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Gesprek",
|
|
301
|
+
"embedding": "Vectorisatie",
|
|
302
|
+
"image": "Afbeeldingsgeneratie",
|
|
303
|
+
"realtime": "Realtime gesprek",
|
|
304
|
+
"stt": "Spraak naar tekst",
|
|
305
|
+
"text2music": "Tekst naar muziek",
|
|
306
|
+
"text2video": "Tekst naar video",
|
|
307
|
+
"tts": "Spraaksynthese"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Selecteer een modeltype",
|
|
310
|
+
"title": "Modeltype"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Deze configuratie zal alleen de afbeeldinguploadcapaciteit in de applicatie inschakelen, of herkenning wordt ondersteund hangt volledig af van het model zelf, test de beschikbaarheid van de visuele herkenningscapaciteit van dit model zelf.",
|
|
299
314
|
"title": "Ondersteuning voor visuele herkenning"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Maksymalne okno kontekstu",
|
|
295
295
|
"unlimited": "Bez ograniczeń"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Różne typy modeli mają różne scenariusze użycia i możliwości",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Czat",
|
|
301
|
+
"embedding": "Wektoryzacja",
|
|
302
|
+
"image": "Generowanie obrazów",
|
|
303
|
+
"realtime": "Czat w czasie rzeczywistym",
|
|
304
|
+
"stt": "Rozpoznawanie mowy",
|
|
305
|
+
"text2music": "Tekst na muzykę",
|
|
306
|
+
"text2video": "Tekst na wideo",
|
|
307
|
+
"tts": "Synteza mowy"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Wybierz typ modelu",
|
|
310
|
+
"title": "Typ modelu"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Ta konfiguracja włączy tylko możliwość przesyłania obrazów w aplikacji, czy model obsługuje rozpoznawanie zależy od samego modelu, proszę samodzielnie przetestować dostępność rozpoznawania wizualnego tego modelu.",
|
|
299
314
|
"title": "Wsparcie dla rozpoznawania wizualnego"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Janela de contexto máxima",
|
|
295
295
|
"unlimited": "Ilimitado"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Diferentes tipos de modelos possuem cenários de uso e capacidades diferenciadas",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Conversa",
|
|
301
|
+
"embedding": "Vetorização",
|
|
302
|
+
"image": "Geração de imagem",
|
|
303
|
+
"realtime": "Conversa em tempo real",
|
|
304
|
+
"stt": "Reconhecimento de voz para texto",
|
|
305
|
+
"text2music": "Texto para música",
|
|
306
|
+
"text2video": "Texto para vídeo",
|
|
307
|
+
"tts": "Síntese de voz"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Por favor, selecione o tipo de modelo",
|
|
310
|
+
"title": "Tipo de modelo"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Esta configuração apenas habilitará a configuração de upload de imagens no aplicativo, se o reconhecimento for suportado depende do modelo em si, teste a capacidade de reconhecimento visual desse modelo.",
|
|
299
314
|
"title": "Suporte a Reconhecimento Visual"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Максимальное окно контекста",
|
|
295
295
|
"unlimited": "Без ограничений"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Различные типы моделей имеют разные сценарии использования и возможности",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Диалог",
|
|
301
|
+
"embedding": "Векторизация",
|
|
302
|
+
"image": "Генерация изображений",
|
|
303
|
+
"realtime": "Реальное время",
|
|
304
|
+
"stt": "Распознавание речи",
|
|
305
|
+
"text2music": "Текст в музыку",
|
|
306
|
+
"text2video": "Текст в видео",
|
|
307
|
+
"tts": "Синтез речи"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Пожалуйста, выберите тип модели",
|
|
310
|
+
"title": "Тип модели"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Эта настройка только активирует возможность загрузки изображений в приложении, поддержка распознавания полностью зависит от самой модели, пожалуйста, протестируйте доступность визуального распознавания этой модели.",
|
|
299
314
|
"title": "Поддержка визуального распознавания"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Maksimum bağlam penceresi",
|
|
295
295
|
"unlimited": "Sınırsız"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Farklı model türleri, farklı kullanım senaryoları ve yeteneklere sahiptir",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Sohbet",
|
|
301
|
+
"embedding": "Vektörleştirme",
|
|
302
|
+
"image": "Görüntü oluşturma",
|
|
303
|
+
"realtime": "Gerçek zamanlı sohbet",
|
|
304
|
+
"stt": "Ses metne dönüştürme",
|
|
305
|
+
"text2music": "Metinden müziğe",
|
|
306
|
+
"text2video": "Metinden videoya",
|
|
307
|
+
"tts": "Ses sentezi"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Lütfen model türünü seçin",
|
|
310
|
+
"title": "Model Türü"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Bu yapılandırma yalnızca uygulamadaki resim yükleme yapılandırmasını açacaktır, tanıma desteği tamamen modele bağlıdır, lütfen bu modelin görsel tanıma yeteneğini test edin.",
|
|
299
314
|
"title": "Görsel Tanımayı Destekle"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "Cửa sổ ngữ cảnh tối đa",
|
|
295
295
|
"unlimited": "Không giới hạn"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "Các loại mô hình khác nhau có các kịch bản sử dụng và khả năng khác biệt",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "Đối thoại",
|
|
301
|
+
"embedding": "Vector hóa",
|
|
302
|
+
"image": "Tạo hình ảnh",
|
|
303
|
+
"realtime": "Đối thoại thời gian thực",
|
|
304
|
+
"stt": "Chuyển giọng nói thành văn bản",
|
|
305
|
+
"text2music": "Chuyển văn bản thành nhạc",
|
|
306
|
+
"text2video": "Chuyển văn bản thành video",
|
|
307
|
+
"tts": "Tổng hợp giọng nói"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "Vui lòng chọn loại mô hình",
|
|
310
|
+
"title": "Loại mô hình"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "Cấu hình này chỉ mở khả năng tải lên hình ảnh trong ứng dụng, việc hỗ trợ nhận diện hoàn toàn phụ thuộc vào mô hình, xin hãy tự kiểm tra khả năng nhận diện hình ảnh của mô hình này.",
|
|
299
314
|
"title": "Hỗ trợ nhận diện hình ảnh"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "最大上下文窗口",
|
|
295
295
|
"unlimited": "无限制"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "不同模型类型拥有差异化的使用场景与能力",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "对话",
|
|
301
|
+
"embedding": "向量化",
|
|
302
|
+
"image": "图片生成",
|
|
303
|
+
"realtime": "实时对话",
|
|
304
|
+
"stt": "语音转文本",
|
|
305
|
+
"text2music": "文本转音乐",
|
|
306
|
+
"text2video": "文本转视频",
|
|
307
|
+
"tts": "语音合成"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "请选择模型类型",
|
|
310
|
+
"title": "模型类型"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "此配置将仅开启应用中的图片上传配置,是否支持识别完全取决于模型本身,请自行测试该模型的视觉识别能力可用性",
|
|
299
314
|
"title": "支持视觉识别"
|
|
@@ -294,6 +294,21 @@
|
|
|
294
294
|
"title": "最大上下文窗口",
|
|
295
295
|
"unlimited": "無限制"
|
|
296
296
|
},
|
|
297
|
+
"type": {
|
|
298
|
+
"extra": "不同模型類型擁有差異化的使用場景與能力",
|
|
299
|
+
"options": {
|
|
300
|
+
"chat": "對話",
|
|
301
|
+
"embedding": "向量化",
|
|
302
|
+
"image": "圖片生成",
|
|
303
|
+
"realtime": "即時對話",
|
|
304
|
+
"stt": "語音轉文字",
|
|
305
|
+
"text2music": "文本轉音樂",
|
|
306
|
+
"text2video": "文本轉影片",
|
|
307
|
+
"tts": "語音合成"
|
|
308
|
+
},
|
|
309
|
+
"placeholder": "請選擇模型類型",
|
|
310
|
+
"title": "模型類型"
|
|
311
|
+
},
|
|
297
312
|
"vision": {
|
|
298
313
|
"extra": "此配置將僅開啟應用中的圖片上傳配置,是否支持識別完全取決於模型本身,請自行測試該模型的視覺識別能力可用性",
|
|
299
314
|
"title": "支持視覺識別"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.134.
|
|
3
|
+
"version": "1.134.7",
|
|
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",
|
|
@@ -20,6 +20,7 @@ import { AgentRuntimeError } from '../../utils/createError';
|
|
|
20
20
|
import { debugStream } from '../../utils/debugStream';
|
|
21
21
|
import { convertImageUrlToFile, convertOpenAIMessages } from '../../utils/openaiHelpers';
|
|
22
22
|
import { StreamingResponse } from '../../utils/response';
|
|
23
|
+
import { sanitizeError } from '../../utils/sanitizeError';
|
|
23
24
|
|
|
24
25
|
const azureImageLogger = debug('lobe-image:azure');
|
|
25
26
|
export class LobeAzureOpenAI implements LobeRuntimeAI {
|
|
@@ -253,9 +254,12 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
|
|
253
254
|
? AgentRuntimeErrorType.ProviderBizError
|
|
254
255
|
: AgentRuntimeErrorType.AgentRuntimeError;
|
|
255
256
|
|
|
257
|
+
// Sanitize error to remove sensitive information like API keys from headers
|
|
258
|
+
const sanitizedError = sanitizeError(error);
|
|
259
|
+
|
|
256
260
|
throw AgentRuntimeError.chat({
|
|
257
261
|
endpoint: this.maskSensitiveUrl(this.baseURL),
|
|
258
|
-
error,
|
|
262
|
+
error: sanitizedError,
|
|
259
263
|
errorType,
|
|
260
264
|
provider: ModelProvider.Azure,
|
|
261
265
|
});
|
|
@@ -12,6 +12,7 @@ import { AgentRuntimeErrorType } from '../../types/error';
|
|
|
12
12
|
import { AgentRuntimeError } from '../../utils/createError';
|
|
13
13
|
import { debugStream } from '../../utils/debugStream';
|
|
14
14
|
import { StreamingResponse } from '../../utils/response';
|
|
15
|
+
import { sanitizeError } from '../../utils/sanitizeError';
|
|
15
16
|
|
|
16
17
|
interface AzureAIParams {
|
|
17
18
|
apiKey?: string;
|
|
@@ -112,9 +113,12 @@ export class LobeAzureAI implements LobeRuntimeAI {
|
|
|
112
113
|
? AgentRuntimeErrorType.ProviderBizError
|
|
113
114
|
: AgentRuntimeErrorType.AgentRuntimeError;
|
|
114
115
|
|
|
116
|
+
// Sanitize error to remove sensitive information like API keys from headers
|
|
117
|
+
const sanitizedError = sanitizeError(error);
|
|
118
|
+
|
|
115
119
|
throw AgentRuntimeError.chat({
|
|
116
120
|
endpoint: this.maskSensitiveUrl(this.baseURL),
|
|
117
|
-
error,
|
|
121
|
+
error: sanitizedError,
|
|
118
122
|
errorType,
|
|
119
123
|
provider: ModelProvider.Azure,
|
|
120
124
|
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { sanitizeError } from './sanitizeError';
|
|
4
|
+
|
|
5
|
+
describe('sanitizeError', () => {
|
|
6
|
+
it('should remove sensitive request headers', () => {
|
|
7
|
+
const errorWithHeaders = {
|
|
8
|
+
message: 'API Error',
|
|
9
|
+
code: 401,
|
|
10
|
+
request: {
|
|
11
|
+
headers: {
|
|
12
|
+
authorization: 'Bearer sk-1234567890',
|
|
13
|
+
'content-type': 'application/json',
|
|
14
|
+
'ocp-apim-subscription-key': 'azure-key-123',
|
|
15
|
+
},
|
|
16
|
+
url: 'https://api.example.com',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const sanitized = sanitizeError(errorWithHeaders);
|
|
21
|
+
|
|
22
|
+
expect(sanitized).toEqual({
|
|
23
|
+
message: 'API Error',
|
|
24
|
+
code: 401,
|
|
25
|
+
});
|
|
26
|
+
expect(sanitized.request).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should remove sensitive fields at any level', () => {
|
|
30
|
+
const errorWithNestedSensitive = {
|
|
31
|
+
message: 'Error',
|
|
32
|
+
data: {
|
|
33
|
+
config: {
|
|
34
|
+
apikey: 'secret-key',
|
|
35
|
+
headers: {
|
|
36
|
+
authorization: 'Bearer token',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
response: {
|
|
40
|
+
status: 401,
|
|
41
|
+
data: 'Unauthorized',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const sanitized = sanitizeError(errorWithNestedSensitive);
|
|
47
|
+
|
|
48
|
+
expect(sanitized).toEqual({
|
|
49
|
+
message: 'Error',
|
|
50
|
+
data: {
|
|
51
|
+
response: {
|
|
52
|
+
status: 401,
|
|
53
|
+
data: 'Unauthorized',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
expect(sanitized.data.config).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle primitive values', () => {
|
|
61
|
+
expect(sanitizeError('string')).toBe('string');
|
|
62
|
+
expect(sanitizeError(123)).toBe(123);
|
|
63
|
+
expect(sanitizeError(true)).toBe(true);
|
|
64
|
+
expect(sanitizeError(null)).toBe(null);
|
|
65
|
+
expect(sanitizeError(undefined)).toBe(undefined);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle arrays', () => {
|
|
69
|
+
const errorArray = [
|
|
70
|
+
{ message: 'Error 1', apikey: 'secret' },
|
|
71
|
+
{ message: 'Error 2', status: 500 },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const sanitized = sanitizeError(errorArray);
|
|
75
|
+
|
|
76
|
+
expect(sanitized).toEqual([{ message: 'Error 1' }, { message: 'Error 2', status: 500 }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should be case insensitive for sensitive field detection', () => {
|
|
80
|
+
const errorWithMixedCase = {
|
|
81
|
+
message: 'Error',
|
|
82
|
+
Authorization: 'Bearer token',
|
|
83
|
+
'API-KEY': 'secret',
|
|
84
|
+
Headers: { token: 'secret' },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const sanitized = sanitizeError(errorWithMixedCase);
|
|
88
|
+
|
|
89
|
+
expect(sanitized).toEqual({
|
|
90
|
+
message: 'Error',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should preserve safe nested structures', () => {
|
|
95
|
+
const errorWithSafeNested = {
|
|
96
|
+
message: 'Error occurred',
|
|
97
|
+
status: 401,
|
|
98
|
+
details: {
|
|
99
|
+
code: 'UNAUTHORIZED',
|
|
100
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
101
|
+
path: '/api/chat',
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const sanitized = sanitizeError(errorWithSafeNested);
|
|
106
|
+
|
|
107
|
+
expect(sanitized).toEqual(errorWithSafeNested);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes error objects by removing sensitive information that could expose API keys or other credentials.
|
|
3
|
+
* This is particularly important for errors from Azure/OpenAI SDKs that may include request headers.
|
|
4
|
+
*/
|
|
5
|
+
export function sanitizeError(error: any): any {
|
|
6
|
+
if (!error || typeof error !== 'object') {
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Handle array of errors
|
|
11
|
+
if (Array.isArray(error)) {
|
|
12
|
+
return error.map(sanitizeError);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Create a sanitized copy
|
|
16
|
+
const sanitized: any = {};
|
|
17
|
+
|
|
18
|
+
// List of sensitive fields that should be removed or masked
|
|
19
|
+
const sensitiveFields = [
|
|
20
|
+
'request',
|
|
21
|
+
'headers',
|
|
22
|
+
'authorization',
|
|
23
|
+
'apikey',
|
|
24
|
+
'api-key',
|
|
25
|
+
'ocp-apim-subscription-key',
|
|
26
|
+
'x-api-key',
|
|
27
|
+
'bearer',
|
|
28
|
+
'token',
|
|
29
|
+
'auth',
|
|
30
|
+
'credential',
|
|
31
|
+
'key',
|
|
32
|
+
'secret',
|
|
33
|
+
'password',
|
|
34
|
+
'config',
|
|
35
|
+
'options',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Copy safe fields and recursively sanitize nested objects
|
|
39
|
+
for (const key in error) {
|
|
40
|
+
if (error.hasOwnProperty(key)) {
|
|
41
|
+
const value = error[key];
|
|
42
|
+
const lowerKey = key.toLowerCase();
|
|
43
|
+
|
|
44
|
+
// Skip sensitive fields entirely
|
|
45
|
+
if (sensitiveFields.indexOf(lowerKey) !== -1) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Recursively sanitize nested objects
|
|
50
|
+
if (value && typeof value === 'object') {
|
|
51
|
+
sanitized[key] = sanitizeError(value);
|
|
52
|
+
} else {
|
|
53
|
+
sanitized[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return sanitized;
|
|
59
|
+
}
|