my-ai-chat-framework 1.1.0 → 2.7.0

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.
@@ -0,0 +1,280 @@
1
+ /**
2
+ * OpenAI 兼容 API 适配器
3
+ * 支持 DeepSeek 等完全兼容 OpenAI 接口的服务
4
+ */
5
+
6
+ import { joinUrl } from "../utils/url.js";
7
+ import { isString } from "../utils/typeCheck.js";
8
+ import { APIError, NetworkError, ParsingError } from '../core/Errors.js';
9
+ import { MessageFormatter } from '../utils/MessageFormatter.js';
10
+
11
+ export const openaiAdapter = {
12
+ name: 'openai',
13
+
14
+ install(chatService) {
15
+ chatService.setAdapter(this);
16
+ },
17
+
18
+ /**
19
+ * 构建 OpenAI 格式的请求体
20
+ * @param {Array} messages - 内部消息列表
21
+ * @param {Object} config - 完整配置对象
22
+ * @param {Array} [systemPrompts=[]] - 启用的 system prompt 列表
23
+ * @returns {Object} 请求体
24
+ */
25
+ buildRequest(messages, config, systemPrompts = []) {
26
+ const model = config.model || config.modelParams?.model;
27
+ if (!model) {
28
+ throw new Error('Missing required config: model (either at top level or in modelParams)');
29
+ }
30
+
31
+ const temperature = config.modelParams?.temperature ?? config.temperature ?? 0.7;
32
+ const maxTokens = config.modelParams?.maxTokens ?? config.maxTokens ?? 2000;
33
+ const reasoningEffort = config.modelParams?.reasoningEffort ?? config.reasoningEffort;
34
+
35
+ // 用 MessageFormatter 统一处理:system 置顶、消息过滤、字段映射、图片解析
36
+ // format 由 config.messageFormat 指定(默认 'openai')
37
+ const apiMessages = MessageFormatter.format({
38
+ messages,
39
+ systemPrompts,
40
+ capabilities: config.capabilities || {},
41
+ resolveImage: config.resolveImage,
42
+ format: config.messageFormat
43
+ });
44
+
45
+ const requestBody = {
46
+ model,
47
+ messages: apiMessages,
48
+ temperature,
49
+ max_tokens: maxTokens,
50
+ stream: false
51
+ };
52
+
53
+ if (config.tools && Array.isArray(config.tools) && config.tools.length > 0) {
54
+ requestBody.tools = config.tools;
55
+ requestBody.tool_choice = 'auto';
56
+ }
57
+
58
+ if (reasoningEffort && model === 'deepseek-reasoner') {
59
+ requestBody.reasoning_effort = reasoningEffort;
60
+ }
61
+
62
+ return requestBody;
63
+ },
64
+
65
+ /**
66
+ * 处理URL相关配置,将其转换为完整请求URL
67
+ * @param {Object} config - 配置参数
68
+ * @returns {String} 实际请求地址
69
+ */
70
+ _getUrl(config) {
71
+ config = config || {};
72
+ const { apiUrl, baseUrl, path } = config;
73
+ // 默认 path 没有 v1 因为 baseUrl 一般包括版本号
74
+ const defaultPath = '/chat/completions';
75
+
76
+ if (apiUrl && isString(apiUrl)) {
77
+ return apiUrl;
78
+ }
79
+
80
+ if (baseUrl && isString(baseUrl)) {
81
+ const finalPath = (path && isString(path)) ? path : defaultPath;
82
+ return joinUrl(baseUrl, finalPath);
83
+ }
84
+
85
+ return 'https://api.openai.com/v1/chat/completions';
86
+ },
87
+
88
+ /**
89
+ * 非流式发送
90
+ * @param {Object} requestBody
91
+ * @param {Object} config
92
+ * @param {Object} [options] — { signal, headers: {...} }
93
+ */
94
+ async send(requestBody, config, options = {}) {
95
+ try {
96
+ const url = this._getUrl(config);
97
+
98
+ const headers = {
99
+ 'Content-Type': 'application/json',
100
+ 'Authorization': `Bearer ${config.apiKey}`,
101
+ ...(config.headers || {}),
102
+ ...(options.headers || {})
103
+ };
104
+
105
+ const response = await fetch(url, {
106
+ method: 'POST',
107
+ headers,
108
+ body: JSON.stringify(requestBody),
109
+ signal: options.signal
110
+ });
111
+
112
+ if (!response.ok) {
113
+ await this.handleErrorResponse(response);
114
+ }
115
+
116
+ const data = await response.json();
117
+ return data;
118
+
119
+ } catch (error) {
120
+ if (error instanceof APIError) throw error;
121
+ if (error.name === 'AbortError') throw error;
122
+
123
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
124
+ throw new NetworkError(`Request failed: ${error.message}`, error);
125
+ }
126
+
127
+ throw error;
128
+ }
129
+ },
130
+
131
+ /**
132
+ * 解析 API 响应为内部消息格式
133
+ */
134
+ parseResponse(apiResponse) {
135
+ // 检查响应结构是否有效
136
+ if (!apiResponse || !apiResponse.choices || !apiResponse.choices[0] || !apiResponse.choices[0].message) {
137
+ throw new ParsingError('Invalid API response: missing choices or message', apiResponse, JSON.stringify(apiResponse));
138
+ }
139
+ // 将 API 响应转换为内部消息格式
140
+ const msg = apiResponse.choices[0].message;
141
+ const internal = {
142
+ role: msg.role,
143
+ content: msg.content || '',
144
+ };
145
+ if (msg.tool_calls) internal.toolCalls = msg.tool_calls;
146
+ if (msg.reasoning_content) internal.reasoningContent = msg.reasoning_content;
147
+ return internal;
148
+ },
149
+
150
+ /**
151
+ * 流式发送
152
+ * @param {Object} requestBody
153
+ * @param {Object} config
154
+ * @param {Function} onProgress — 接收累积快照 { content, reasoningContent, toolCalls }
155
+ * @param {Function} onDone — 接收最终消息对象
156
+ * @param {Object} [options] — { signal, headers: {...} }
157
+ */
158
+ async stream(requestBody, config, onProgress, onDone, options = {}) {
159
+ const streamBody = { ...requestBody, stream: true };
160
+ const url = this._getUrl(config);
161
+ let response;
162
+
163
+ const headers = {
164
+ 'Content-Type': 'application/json',
165
+ 'Authorization': `Bearer ${config.apiKey}`,
166
+ 'Accept': 'text/event-stream',
167
+ ...(config.headers || {}),
168
+ ...(options.headers || {})
169
+ };
170
+
171
+ try {
172
+ response = await fetch(url, {
173
+ method: 'POST',
174
+ headers,
175
+ body: JSON.stringify(streamBody),
176
+ signal: options.signal
177
+ });
178
+
179
+ } catch (error) {
180
+ if (error.name === 'AbortError') throw error;
181
+
182
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
183
+ throw new NetworkError(`Stream request failed: ${error.message}`, error);
184
+ }
185
+
186
+ throw error;
187
+ }
188
+
189
+ // 处理非 2xx 响应
190
+ if (!response.ok) {
191
+ await this.handleErrorResponse(response);
192
+ }
193
+
194
+ // 解析流式响应
195
+ const reader = response.body.getReader();
196
+ const decoder = new TextDecoder();
197
+ let buffer = '';
198
+ let accumulated = { role: 'assistant', content: '', reasoningContent: '' };
199
+ let reachedDone = false; // 是否收到 [DONE]
200
+
201
+ while (true) {
202
+ // 用户调用 abort() 后 signal.aborted 为 true,跳出循环
203
+ if (options.signal?.aborted) break;
204
+
205
+ const { done, value } = await reader.read();
206
+ if (done) break;
207
+ buffer += decoder.decode(value, { stream: true });
208
+ const lines = buffer.split(/\n\n/);
209
+ buffer = lines.pop();
210
+
211
+ for (const line of lines) {
212
+ const dataLine = line.replace(/^data: /, '').trim();
213
+ if (!dataLine) continue;
214
+ if (dataLine === '[DONE]') { reachedDone = true; break; }
215
+ try {
216
+ const chunk = JSON.parse(dataLine);
217
+ const delta = chunk.choices?.[0]?.delta;
218
+ if (!delta) continue;
219
+
220
+ if (delta.content) accumulated.content += delta.content;
221
+ if (delta.tool_calls) {
222
+ if (!accumulated.toolCalls) accumulated.toolCalls = [];
223
+ for (const toolCallDelta of delta.tool_calls) {
224
+ const index = toolCallDelta.index;
225
+ if (!accumulated.toolCalls[index]) {
226
+ accumulated.toolCalls[index] = {
227
+ id: '',
228
+ type: 'function',
229
+ function: { name: '', arguments: '' }
230
+ };
231
+ }
232
+ if (toolCallDelta.id) accumulated.toolCalls[index].id = toolCallDelta.id;
233
+ if (toolCallDelta.type) accumulated.toolCalls[index].type = toolCallDelta.type;
234
+ if (toolCallDelta.function?.name) {
235
+ accumulated.toolCalls[index].function.name += toolCallDelta.function.name;
236
+ }
237
+ if (toolCallDelta.function?.arguments) {
238
+ accumulated.toolCalls[index].function.arguments += toolCallDelta.function.arguments;
239
+ }
240
+ }
241
+ }
242
+ if (delta.reasoning_content) accumulated.reasoningContent += delta.reasoning_content;
243
+
244
+ // 传出累积快照,ChatService 自行处理占位消息更新
245
+ onProgress({ ...accumulated });
246
+ } catch (e) {
247
+ console.warn('stream parsing failed :', e, dataLine);
248
+ }
249
+ }
250
+ }
251
+
252
+ const finalMessage = {
253
+ role: 'assistant',
254
+ content: accumulated.content,
255
+ toolCalls: accumulated.toolCalls,
256
+ reasoningContent: accumulated.reasoningContent
257
+ };
258
+ onDone(finalMessage);
259
+ },
260
+
261
+ /**
262
+ * 处理对话请求的非 2xx 响应,抛出 APIError,包含状态码、错误消息和原始响应文本
263
+ * @param {Response} response - fetch API 的 Response 对象
264
+ * @returns {Promise<never>} 抛出错误
265
+ */
266
+ async handleErrorResponse(response) {
267
+ let errorText = await response.text();
268
+ let errorMessage = `HTTP ${response.status}`;
269
+ // 尝试从响应体中解析 JSON 格式的错误信息
270
+ try {
271
+ const errorJson = JSON.parse(errorText);
272
+ errorMessage = errorJson.error?.message || errorJson.message || errorText.slice(0, 200);
273
+ } catch (e) {
274
+ // 如果解析失败,使用原始文本(已截断)
275
+ errorMessage = errorText.length > 200 ? errorText.slice(0, 200) + '...' : errorText;
276
+ }
277
+ throw new APIError(errorMessage, response.status, null, errorText);
278
+ }
279
+
280
+ };
@@ -0,0 +1,298 @@
1
+ import { EventEmitter } from './EventEmitter.js';
2
+ import { MessageStore } from './MessageStore.js';
3
+ import { SystemPromptStore } from './SystemPromptStore.js';
4
+ import { ConfigurationError, NetworkError } from './Errors.js';
5
+
6
+ export class ChatService extends EventEmitter {
7
+ constructor(config = {}) {
8
+ super();
9
+ this.config = { ...config };
10
+ this.messages = new MessageStore();
11
+ this.systemPrompts = new SystemPromptStore();
12
+ this._adapter = null;
13
+ this._abortController = null;
14
+ this._isGenerating = false;
15
+ this._hooks = new EventEmitter();
16
+ this._processResponse = null;
17
+
18
+ const model = this.config.model || this.config.modelParams?.model;
19
+ if (!model || typeof model !== 'string' || !model.trim()) {
20
+ throw new ConfigurationError('缺少 model 配置');
21
+ }
22
+
23
+ const temperature = this.config.modelParams?.temperature ?? this.config.temperature;
24
+ if (temperature !== undefined && (typeof temperature !== 'number' || temperature < 0 || temperature > 2)) {
25
+ throw new ConfigurationError(`temperature 必须在 0-2 之间,当前值: ${temperature}`);
26
+ }
27
+
28
+ const maxTokens = this.config.modelParams?.maxTokens ?? this.config.maxTokens;
29
+ if (maxTokens !== undefined && (typeof maxTokens !== 'number' || maxTokens < 1 || !Number.isInteger(maxTokens))) {
30
+ throw new ConfigurationError(`maxTokens 必须为正整数,当前值: ${maxTokens}`);
31
+ }
32
+
33
+ if (typeof config.system === 'string' && config.system.trim()) {
34
+ this.systemPrompts.set(config.system);
35
+ }
36
+ }
37
+
38
+ use(plugin) {
39
+ plugin.install(this);
40
+ return this;
41
+ }
42
+
43
+ setAdapter(adapter) {
44
+ this._adapter = adapter;
45
+ }
46
+
47
+ abort() {
48
+ if (this._abortController) this._abortController.abort();
49
+ }
50
+
51
+ get isGenerating() { return this._isGenerating; }
52
+
53
+ async continueLast() {
54
+ const target = this._prepareContinue();
55
+ this.messages.update(target.id, { prefix: true });
56
+ try {
57
+ await this._request({ addUser: false, isStream: false, mergeToEntry: target.id });
58
+ this.messages.update(target.id, {
59
+ _complete: true, prefix: undefined, _ephemeral: undefined
60
+ });
61
+ return target;
62
+ } catch (err) {
63
+ this.messages.update(target.id, { prefix: undefined });
64
+ throw err;
65
+ }
66
+ }
67
+
68
+ async continueLastStream(onProgress, onDone) {
69
+ const target = this._prepareContinue();
70
+ console.log('[DEBUG] continueLastStream target.id:', target.id);
71
+ this.messages.update(target.id, { prefix: true });
72
+ const baseLen = (target.content || '').length;
73
+
74
+ try {
75
+ await this._request({
76
+ addUser: false, isStream: true, mergeToEntry: target.id,
77
+ onProgress: (chunk) => {
78
+ if (onProgress) {
79
+ onProgress({ ...chunk, content: (chunk.content || '').slice(baseLen) });
80
+ }
81
+ },
82
+ onDone: (final) => { if (onDone) onDone(final); }
83
+ });
84
+ this.messages.update(target.id, {
85
+ _complete: true, prefix: undefined, _ephemeral: undefined
86
+ });
87
+ return target;
88
+ } catch (err) {
89
+ this.messages.update(target.id, { prefix: undefined });
90
+ throw err;
91
+ }
92
+ }
93
+
94
+ _prepareContinue() {
95
+ const msgs = this.messages.getAll();
96
+ const last = msgs[msgs.length - 1];
97
+ if (last && (last.role === 'assistant' || last._ephemeral)) return last;
98
+ throw new Error('最后一条消息不是 assistant,无法续写');
99
+ }
100
+
101
+ updateConfig(partial) {
102
+ Object.assign(this.config, partial);
103
+ if (typeof partial.system === 'string') this.systemPrompts.set(partial.system);
104
+ this.emit('config-updated', { changes: partial, timestamp: Date.now() });
105
+ }
106
+
107
+ // ========== 编排角色 ==========
108
+ async _request(options) {
109
+ let { userInput, addUser = true, isStream = false, onProgress, onDone, mergeToEntry } = options;
110
+
111
+ this.emit('sending', { addUser, userInput, timestamp: Date.now() });
112
+ this._addUserMessage(userInput, addUser);
113
+
114
+ // 发送前钩子:在 adapter 转换 messages 之前执行,可修改 messages/config
115
+ await this._hooks.emitAsync('beforeRequest', {
116
+ messages: this.messages,
117
+ config: this.config,
118
+ options
119
+ });
120
+
121
+ // 自动续写:启用 autoContinue 时,检测底部是否有 prefix 标记的消息,自动设为续写目标
122
+ // 用于 ephemeral 注入场景:注入一条带 prefix 的引导消息后,自动合并结果回原消息
123
+ // 不启用时 prefix 仅透传给 API,结果仍作为新消息追加
124
+ if (this.config.ephemeralContinue && !mergeToEntry) {
125
+ const msgs = this.messages.getAll();
126
+ const last = msgs[msgs.length - 1];
127
+ if (last && last.prefix && (last.role === 'assistant' || last._ephemeral)) {
128
+ mergeToEntry = last.id;
129
+ }
130
+ }
131
+
132
+ const body = this._adapter.buildRequest(
133
+ this.messages.getAll(), this.config, this.systemPrompts.getEnabled()
134
+ );
135
+
136
+ const retryCfg = this.config.retry || {};
137
+ try {
138
+ return await this._withRetry(body, {
139
+ isStream, onProgress, onDone, mergeToEntry,
140
+ maxRetries: retryCfg.maxRetries ?? 0,
141
+ retryDelay: retryCfg.retryDelay ?? 1000
142
+ });
143
+ } catch (error) {
144
+ this.emit('error', { error, timestamp: Date.now() });
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ _addUserMessage(userInput, addUser) {
150
+ if (addUser && userInput !== undefined) {
151
+ const msg = typeof userInput === 'string'
152
+ ? { role: 'user', content: userInput }
153
+ : { role: 'user', ...userInput };
154
+ this.messages.add(msg);
155
+ }
156
+ }
157
+
158
+ /** retry 循环,占位消息只 push 一次 */
159
+ async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, maxRetries, retryDelay }) {
160
+ let lastError = null;
161
+ const adapterOptions = { signal: this._abortController?.signal };
162
+
163
+ let placeholder = null;
164
+ let base = null;
165
+ if (isStream) {
166
+ if (mergeToEntry) {
167
+ placeholder = this.messages.getAll().find(m => m.id === mergeToEntry);
168
+ if (placeholder) base = { content: placeholder.content || '', reasoning: placeholder.reasoningContent || '' };
169
+ }
170
+ if (!placeholder) {
171
+ placeholder = this.messages.add({ role: 'assistant', content: '', _complete: false });
172
+ }
173
+ }
174
+
175
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
176
+ if (attempt > 0) {
177
+ this.emit('retry', { attempt, maxRetries, lastError, timestamp: Date.now() });
178
+ await new Promise(r => setTimeout(r, retryDelay));
179
+ }
180
+ try {
181
+ this._isGenerating = true;
182
+ if (isStream) {
183
+ return await this._stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone });
184
+ } else {
185
+ const resp = await this._adapter.send(body, this.config, adapterOptions);
186
+ let result = this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
187
+ // 响应后处理钩子(tool-calling 用),仅非续写时
188
+ if (this._processResponse && !mergeToEntry) {
189
+ result = await this._processResponse(result, { isStream: false });
190
+ }
191
+ return result;
192
+ }
193
+ } catch (error) {
194
+ lastError = error;
195
+ if (error.name === 'AbortError' || this._abortController?.signal.aborted) {
196
+ this._isGenerating = false; throw error;
197
+ }
198
+ if (error instanceof NetworkError && attempt < maxRetries) continue;
199
+ this._isGenerating = false; throw error;
200
+ }
201
+ }
202
+ this._isGenerating = false;
203
+ throw lastError;
204
+ }
205
+
206
+ /** 流式:ChatService 维护占位,adapter 只管解析 */
207
+ async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone }) {
208
+ let finalMsg = null;
209
+
210
+ await this._adapter.stream(body, this.config,
211
+ (snap) => {
212
+ placeholder.content = snap.content || '';
213
+ if (snap.reasoningContent) placeholder.reasoningContent = snap.reasoningContent;
214
+ if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
215
+ this.emit('stream-progress', snap);
216
+ if (onProgress) onProgress(snap);
217
+ },
218
+ async (final) => {
219
+ finalMsg = final;
220
+
221
+ if (this._processResponse && !mergeToEntry) {
222
+ finalMsg = await this._processResponse(finalMsg, {
223
+ isStream: true, onProgress, onDone
224
+ });
225
+ }
226
+
227
+ if (mergeToEntry && base) {
228
+ const newContent = base.content + (final.content || '');
229
+ const changes = { content: newContent, _complete: true };
230
+ if (final.reasoningContent) changes.reasoningContent = base.reasoning + final.reasoningContent;
231
+ if (final.toolCalls) changes.toolCalls = final.toolCalls;
232
+ this.messages.update(mergeToEntry, changes);
233
+ const updated = this.messages.getAll().find(m => m.id === mergeToEntry);
234
+ this.emit('message', updated);
235
+ if (onDone) onDone(updated);
236
+ } else {
237
+ placeholder._complete = true;
238
+ this.emit('message', finalMsg);
239
+ if (onDone) onDone(finalMsg);
240
+ }
241
+ },
242
+ adapterOptions
243
+ );
244
+ return mergeToEntry || finalMsg;
245
+ }
246
+
247
+ /** 非流式结果落位 */
248
+ _handleResult(assistantMsg, mergeToEntry) {
249
+ if (mergeToEntry) {
250
+ const entry = this.messages.getAll().find(m => m.id === mergeToEntry);
251
+ if (!entry) {
252
+ this.messages.add(assistantMsg);
253
+ this.emit('message', assistantMsg);
254
+ return assistantMsg;
255
+ }
256
+ const changes = {
257
+ content: (entry.content || '') + (assistantMsg.content || ''),
258
+ _complete: true
259
+ };
260
+ if (assistantMsg.reasoningContent) {
261
+ changes.reasoningContent = (entry.reasoningContent || '') + assistantMsg.reasoningContent;
262
+ }
263
+ if (assistantMsg.toolCalls) changes.toolCalls = assistantMsg.toolCalls;
264
+ this.messages.update(mergeToEntry, changes);
265
+ const updated = this.messages.getAll().find(m => m.id === mergeToEntry);
266
+ this.emit('message', updated);
267
+ return updated;
268
+ }
269
+ this.messages.add(assistantMsg);
270
+ this.emit('message', assistantMsg);
271
+ return assistantMsg;
272
+ }
273
+
274
+ // ========== 公共 API ==========
275
+ async send(userInput) {
276
+ this._abortController = new AbortController();
277
+ try { return await this._request({ userInput, addUser: true, isStream: false }); }
278
+ finally { this._abortController = null; this._isGenerating = false; }
279
+ }
280
+
281
+ async stream(userInput, onProgress, onDone) {
282
+ this._abortController = new AbortController();
283
+ try { return await this._request({ userInput, addUser: true, isStream: true, onProgress, onDone }); }
284
+ finally { this._abortController = null; this._isGenerating = false; }
285
+ }
286
+
287
+ async sendExisting() {
288
+ this._abortController = new AbortController();
289
+ try { return await this._request({ addUser: false, isStream: false }); }
290
+ finally { this._abortController = null; this._isGenerating = false; }
291
+ }
292
+
293
+ async sendExistingStream(onProgress, onDone) {
294
+ this._abortController = new AbortController();
295
+ try { return await this._request({ addUser: false, isStream: true, onProgress, onDone }); }
296
+ finally { this._abortController = null; this._isGenerating = false; }
297
+ }
298
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 自定义错误类
3
+ * 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理
4
+ */
5
+
6
+ // API 错误 (如 401 认证失败、400 参数错误、429 限流等)
7
+ export class APIError extends Error {
8
+ /**
9
+ * @param {string} message - 错误消息(通常来自 API 响应)
10
+ * @param {number} statusCode - HTTP 状态码
11
+ * @param {any} originalError - 原始错误对象或相关信息
12
+ * @param {string} responseText - 原始响应文本(如果有)
13
+ */
14
+ constructor(message, statusCode, originalError, responseText) {
15
+ super(message)
16
+ this.name = 'APIError'
17
+ this.statusCode = statusCode
18
+ this.originalError = originalError
19
+ this.responseText = responseText
20
+ }
21
+ }
22
+
23
+ // 网络错误 (fetch 失败、连接超时等)
24
+ export class NetworkError extends Error {
25
+ /**
26
+ * @param {string} message - 错误消息
27
+ * @param {any} originalError - 原始错误对象或相关信息
28
+ */
29
+ constructor(message, originalError) {
30
+ super(message)
31
+ this.name = 'NetworkError'
32
+ this.originalError = originalError
33
+ }
34
+ }
35
+
36
+ // 配置错误 (缺少必要配置项、配置项类型错误等)
37
+ export class ConfigurationError extends Error {
38
+ /**
39
+ * @param {string} message - 错误消息
40
+ */
41
+ constructor(message) {
42
+ super(message)
43
+ this.name = 'ConfigurationError'
44
+ }
45
+ }
46
+
47
+ // 解析错误 (解析 API 响应失败、响应格式不正确,不符合预期等)
48
+ export class ParsingError extends Error {
49
+ /**
50
+ * @param {string} message - 错误消息
51
+ * @param {any} originalError - 原始错误对象或相关信息
52
+ * @param {string} responseText - 原始响应文本(如果有)
53
+ **/
54
+ constructor(message, originalError, responseText) {
55
+ super(message)
56
+ this.name = 'ParsingError'
57
+ this.originalError = originalError
58
+ this.responseText = responseText
59
+ }
60
+ }