@opentiny/tiny-robot-kit 0.4.0-alpha.1 → 0.4.0-alpha.10

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/dist/index.d.mts CHANGED
@@ -1,27 +1,57 @@
1
1
  import { Ref, ComputedRef } from 'vue';
2
2
 
3
- type MaybePromise<T> = T | Promise<T>;
4
-
5
3
  /**
6
- * 工具函数模块
7
- * 提供一些实用的辅助函数
4
+ * 模型Provider基类
8
5
  */
6
+ declare abstract class BaseModelProvider {
7
+ protected config: AIModelConfig;
8
+ /**
9
+ * @param config AI模型配置
10
+ */
11
+ constructor(config: AIModelConfig);
12
+ /**
13
+ * 发送聊天请求并获取响应
14
+ * @param request 聊天请求参数
15
+ * @returns 聊天响应
16
+ */
17
+ abstract chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
18
+ /**
19
+ * 发送流式聊天请求并通过处理器处理响应
20
+ * @param request 聊天请求参数
21
+ * @param handler 流式响应处理器
22
+ */
23
+ abstract chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
24
+ /**
25
+ * 更新配置
26
+ * @param config 新的AI模型配置
27
+ */
28
+ updateConfig(config: AIModelConfig): void;
29
+ /**
30
+ * 获取当前配置
31
+ * @returns AI模型配置
32
+ */
33
+ getConfig(): AIModelConfig;
34
+ /**
35
+ * 验证请求参数
36
+ * @param request 聊天请求参数
37
+ */
38
+ protected validateRequest(request: ChatCompletionRequest): void;
39
+ }
9
40
 
10
- declare function createSSEStreamGenerator<T = any>(response: Response, options?: {
11
- signal?: AbortSignal;
12
- }): AsyncGenerator<T, void, unknown>;
13
-
14
- interface Tool {
41
+ type MaybePromise<T> = T | Promise<T>;
42
+ /**
43
+ * 消息角色类型
44
+ */
45
+ type MessageRole = 'system' | 'user' | 'assistant';
46
+ interface ToolCall {
47
+ index: number;
48
+ id: string;
15
49
  type: 'function';
16
50
  function: {
17
51
  name: string;
18
- description: string;
19
- /**
20
- * function 的输入参数,以 JSON Schema 对象描述
21
- */
22
- parameters: any;
52
+ arguments: string;
53
+ result?: string;
23
54
  };
24
- [key: string]: any;
25
55
  }
26
56
  interface MessageMetadata {
27
57
  createdAt?: number;
@@ -30,31 +60,254 @@ interface MessageMetadata {
30
60
  model?: string;
31
61
  [key: string]: any;
32
62
  }
63
+ /**
64
+ * 聊天消息接口
65
+ */
33
66
  interface ChatMessage {
34
67
  role: string;
35
68
  content: string;
69
+ reasoning_content?: string;
36
70
  metadata?: MessageMetadata;
37
71
  tool_calls?: ToolCall[];
38
72
  tool_call_id?: string;
39
73
  [key: string]: any;
40
74
  [key: symbol]: any;
41
75
  }
42
- interface MessageRequestBody {
43
- messages: Partial<ChatMessage>[];
44
- [key: string]: any;
76
+ /**
77
+ * 聊天历史记录
78
+ */
79
+ type ChatHistory = ChatMessage[];
80
+ /**
81
+ * 聊天完成请求选项
82
+ */
83
+ interface ChatCompletionOptions {
84
+ model?: string;
85
+ temperature?: number;
86
+ top_p?: number;
87
+ n?: number;
88
+ stream?: boolean;
89
+ max_tokens?: number;
90
+ signal?: AbortSignal;
45
91
  }
46
- type RequestState = 'idle' | 'processing' | 'completed' | 'aborted' | 'error';
47
- type RequestProcessingState = 'requesting' | 'completing' | string;
48
- interface ToolCall {
92
+ /**
93
+ * 聊天完成请求参数
94
+ */
95
+ interface ChatCompletionRequest {
96
+ messages: ChatMessage[];
97
+ options?: ChatCompletionOptions;
98
+ }
99
+ /**
100
+ * 聊天完成响应消息
101
+ */
102
+ interface ChatCompletionResponseMessage {
103
+ role: MessageRole;
104
+ content: string;
105
+ [x: string]: unknown;
106
+ }
107
+ /**
108
+ * 聊天完成响应选择
109
+ */
110
+ interface ChatCompletionResponseChoice {
111
+ index: number;
112
+ message: ChatCompletionResponseMessage;
113
+ finish_reason: string;
114
+ }
115
+ /**
116
+ * 聊天完成响应使用情况
117
+ */
118
+ interface ChatCompletionResponseUsage {
119
+ prompt_tokens: number;
120
+ completion_tokens: number;
121
+ total_tokens: number;
122
+ }
123
+ /**
124
+ * 聊天完成响应
125
+ */
126
+ interface ChatCompletionResponse {
127
+ id: string;
128
+ object: string;
129
+ created: number;
130
+ model: string;
131
+ choices: ChatCompletionResponseChoice[];
132
+ usage: ChatCompletionResponseUsage;
133
+ }
134
+ /**
135
+ * 流式聊天完成响应增量
136
+ */
137
+ interface ChatCompletionStreamResponseDelta {
138
+ content?: string;
139
+ role?: MessageRole;
140
+ [x: string]: unknown;
141
+ }
142
+ /**
143
+ * 流式聊天完成响应选择
144
+ */
145
+ interface ChatCompletionStreamResponseChoice {
49
146
  index: number;
147
+ delta: ChatCompletionStreamResponseDelta;
148
+ finish_reason: string | null;
149
+ }
150
+ /**
151
+ * 流式聊天完成响应
152
+ */
153
+ interface ChatCompletionStreamResponse {
50
154
  id: string;
155
+ object: string;
156
+ created: number;
157
+ model: string;
158
+ choices: ChatCompletionStreamResponseChoice[];
159
+ }
160
+ /**
161
+ * AI模型提供商类型
162
+ */
163
+ type AIProvider = 'openai' | 'deepseek' | 'custom';
164
+ /**
165
+ * AI模型配置接口
166
+ */
167
+ interface AIModelConfig {
168
+ provider: AIProvider;
169
+ providerImplementation?: BaseModelProvider;
170
+ apiKey?: string;
171
+ apiUrl?: string;
172
+ apiVersion?: string;
173
+ defaultModel?: string;
174
+ defaultOptions?: ChatCompletionOptions;
175
+ }
176
+ /**
177
+ * 错误类型
178
+ */
179
+ declare enum ErrorType {
180
+ NETWORK_ERROR = "network_error",
181
+ AUTHENTICATION_ERROR = "authentication_error",
182
+ RATE_LIMIT_ERROR = "rate_limit_error",
183
+ SERVER_ERROR = "server_error",
184
+ MODEL_ERROR = "model_error",
185
+ TIMEOUT_ERROR = "timeout_error",
186
+ UNKNOWN_ERROR = "unknown_error"
187
+ }
188
+ /**
189
+ * AI适配器错误
190
+ */
191
+ interface AIAdapterError {
192
+ type: ErrorType;
193
+ message: string;
194
+ statusCode?: number;
195
+ originalError?: object;
196
+ }
197
+ /**
198
+ * 流式响应事件类型
199
+ */
200
+ declare enum StreamEventType {
201
+ DATA = "data",
202
+ ERROR = "error",
203
+ DONE = "done"
204
+ }
205
+ /**
206
+ * 流式响应处理器
207
+ */
208
+ interface StreamHandler {
209
+ onData: (data: ChatCompletionStreamResponse) => void;
210
+ onError: (error: AIAdapterError) => void;
211
+ onDone: (finishReason?: string) => void;
212
+ }
213
+
214
+ /**
215
+ * AI客户端类
216
+ * 负责根据配置选择合适的提供商并处理请求
217
+ */
218
+
219
+ /**
220
+ * @deprecated
221
+ * AI客户端类
222
+ */
223
+ declare class AIClient {
224
+ private provider;
225
+ private config;
226
+ /**
227
+ * 构造函数
228
+ * @param config AI模型配置
229
+ */
230
+ constructor(config: AIModelConfig);
231
+ /**
232
+ * 创建提供商实例
233
+ * @param config AI模型配置
234
+ * @returns 提供商实例
235
+ */
236
+ private createProvider;
237
+ /**
238
+ * 发送聊天请求并获取响应
239
+ * @param request 聊天请求参数
240
+ * @returns 聊天响应
241
+ */
242
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
243
+ /**
244
+ * 发送流式聊天请求并通过处理器处理响应
245
+ * @param request 聊天请求参数
246
+ * @param handler 流式响应处理器
247
+ */
248
+ chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
249
+ /**
250
+ * 获取当前配置
251
+ * @returns AI模型配置
252
+ */
253
+ getConfig(): AIModelConfig;
254
+ /**
255
+ * 更新配置
256
+ * @param config 新的AI模型配置
257
+ */
258
+ updateConfig(config: Partial<AIModelConfig>): void;
259
+ }
260
+
261
+ /**
262
+ * OpenAI提供商
263
+ * 用于与OpenAI API进行交互
264
+ */
265
+
266
+ declare class OpenAIProvider extends BaseModelProvider {
267
+ private baseURL;
268
+ private apiKey;
269
+ private defaultModel;
270
+ /**
271
+ * @param config AI模型配置
272
+ */
273
+ constructor(config: AIModelConfig);
274
+ /**
275
+ * 发送聊天请求并获取响应
276
+ * @param request 聊天请求参数
277
+ * @returns 聊天响应
278
+ */
279
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
280
+ /**
281
+ * 发送流式聊天请求并通过处理器处理响应
282
+ * @param request 聊天请求参数
283
+ * @param handler 流式响应处理器
284
+ */
285
+ chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
286
+ /**
287
+ * 更新配置
288
+ * @param config 新的AI模型配置
289
+ */
290
+ updateConfig(config: AIModelConfig): void;
291
+ }
292
+
293
+ interface Tool {
51
294
  type: 'function';
52
295
  function: {
53
296
  name: string;
54
- arguments: string;
55
- result?: string;
297
+ description: string;
298
+ /**
299
+ * function 的输入参数,以 JSON Schema 对象描述
300
+ */
301
+ parameters: any;
56
302
  };
303
+ [key: string]: any;
304
+ }
305
+ interface MessageRequestBody {
306
+ messages: Partial<ChatMessage>[];
307
+ [key: string]: any;
57
308
  }
309
+ type RequestState = 'idle' | 'processing' | 'completed' | 'aborted' | 'error';
310
+ type RequestProcessingState = 'requesting' | 'completing' | string;
58
311
  interface Usage {
59
312
  prompt_tokens: number;
60
313
  completion_tokens: number;
@@ -101,7 +354,16 @@ interface ChatCompletion {
101
354
  }
102
355
  interface UseMessageOptions {
103
356
  initialMessages?: ChatMessage[];
104
- requestMessageFields?: (keyof ChatMessage)[];
357
+ /**
358
+ * 请求消息时,要包含的字段(白名单)。默认包含所有字段。
359
+ * 如果 `requestMessageFieldsExclude` 存在,会先取 `requestMessageFields` 中的字段,再排除 `requestMessageFieldsExclude` 中的字段
360
+ */
361
+ requestMessageFields?: string[];
362
+ /**
363
+ * 请求消息时,要排除的字段(黑名单)。默认会排除 `state`、`metadata`、`loading` 字段(这几个字段是给UI展示用的)。
364
+ * 如果 `requestMessageFields` 存在,会先取 `requestMessageFields` 中的字段,再排除 `requestMessageFieldsExclude` 中的字段
365
+ */
366
+ requestMessageFieldsExclude?: string[];
105
367
  plugins?: UseMessagePlugin[];
106
368
  responseProvider: <T = ChatCompletion>(requestBody: MessageRequestBody, abortSignal: AbortSignal) => Promise<T> | AsyncGenerator<T> | Promise<AsyncGenerator<T>>;
107
369
  /**
@@ -111,6 +373,7 @@ interface UseMessageOptions {
111
373
  */
112
374
  onCompletionChunk?: (context: BasePluginContext & {
113
375
  currentMessage: ChatMessage;
376
+ choice: CompletionChoice;
114
377
  chunk: ChatCompletion;
115
378
  }, runDefault: () => void) => void;
116
379
  }
@@ -129,7 +392,6 @@ interface BasePluginContext {
129
392
  currentTurn: ChatMessage[];
130
393
  requestState: RequestState;
131
394
  processingState?: RequestProcessingState;
132
- requestMessageFields: (keyof ChatMessage)[];
133
395
  plugins: UseMessagePlugin[];
134
396
  setRequestState: (state: RequestState, processingState?: RequestProcessingState) => void;
135
397
  abortSignal: AbortSignal;
@@ -150,7 +412,7 @@ interface UseMessagePlugin {
150
412
  /**
151
413
  * 是否禁用插件。useMessage 可能会内置一些默认插件,如果需要禁用,可以设置为 true。
152
414
  */
153
- disabled?: boolean;
415
+ disabled?: boolean | ((context: BasePluginContext) => boolean);
154
416
  /**
155
417
  * 一次对话回合(turn)开始钩子:用户消息入队后、正式发起请求之前触发。
156
418
  * 按插件注册顺序串行执行,便于做有序初始化/校验;出错则中断流程。
@@ -195,6 +457,7 @@ interface UseMessagePlugin {
195
457
  onError?: (context: BasePluginContext & {
196
458
  error: unknown;
197
459
  }) => void;
460
+ onFinally?: (context: BasePluginContext) => void;
198
461
  }
199
462
 
200
463
  interface ConversationInfo {
@@ -215,28 +478,6 @@ interface Conversation extends ConversationInfo {
215
478
  */
216
479
  engine: UseMessageReturn;
217
480
  }
218
- interface ConversationStorageStrategy {
219
- /**
220
- * Load all conversations (id and title only).
221
- */
222
- loadConversations?: () => MaybePromise<ConversationInfo[]>;
223
- /**
224
- * Load all messages for a given conversation.
225
- */
226
- loadMessages?: (conversationId: string) => MaybePromise<ChatMessage[]>;
227
- /**
228
- * Persist conversation metadata (create or update).
229
- */
230
- saveConversation?: (conversation: ConversationInfo) => MaybePromise<void>;
231
- /**
232
- * Persist messages for a given conversation.
233
- */
234
- saveMessages?: (conversationId: string, messages: ChatMessage[]) => MaybePromise<void>;
235
- /**
236
- * Optional method to delete a conversation and its messages.
237
- */
238
- deleteConversation?: (conversationId: string) => MaybePromise<void>;
239
- }
240
481
  interface UseConversationOptions {
241
482
  /**
242
483
  * Base useMessage options for all conversations.
@@ -260,6 +501,11 @@ interface UseConversationOptions {
260
501
  * When provided, conversation list and messages can be loaded and persisted.
261
502
  */
262
503
  storage?: ConversationStorageStrategy;
504
+ /**
505
+ * Called when the initial conversation list has been loaded from storage.
506
+ * Only fired when storage.loadConversations is available and has completed.
507
+ */
508
+ onLoad?: (conversations: ConversationInfo[]) => void;
263
509
  }
264
510
  interface UseConversationReturn {
265
511
  conversations: Ref<ConversationInfo[]>;
@@ -277,12 +523,144 @@ interface UseConversationReturn {
277
523
  }) => Conversation;
278
524
  switchConversation: (id: string) => Promise<Conversation | null>;
279
525
  deleteConversation: (id: string) => Promise<void>;
526
+ clear: () => void;
280
527
  updateConversationTitle: (id: string, title?: string) => void;
281
528
  saveMessages: (id?: string) => void;
282
529
  sendMessage: (content: string) => void;
283
530
  abortActiveRequest: () => Promise<void>;
284
531
  }
285
532
 
533
+ /**
534
+ * 存储策略接口
535
+ */
536
+ interface ConversationStorageStrategy {
537
+ /**
538
+ * Load all conversations (id and title only).
539
+ */
540
+ loadConversations: () => MaybePromise<ConversationInfo[]>;
541
+ /**
542
+ * Load all messages for a given conversation.
543
+ */
544
+ loadMessages: (conversationId: string) => MaybePromise<ChatMessage[]>;
545
+ /**
546
+ * Persist conversation metadata (create or update).
547
+ */
548
+ saveConversation: (conversation: ConversationInfo) => MaybePromise<void>;
549
+ /**
550
+ * Persist messages for a given conversation.
551
+ */
552
+ saveMessages: (conversationId: string, messages: ChatMessage[]) => MaybePromise<void>;
553
+ /**
554
+ * Optional method to delete a conversation and its messages.
555
+ */
556
+ deleteConversation?: (conversationId: string) => MaybePromise<void>;
557
+ }
558
+
559
+ /**
560
+ * 本地存储策略
561
+ */
562
+ declare class LocalStorageStrategy implements ConversationStorageStrategy {
563
+ private storageKey;
564
+ constructor(storageKey?: string);
565
+ saveConversation(conversation: ConversationInfo): void;
566
+ loadConversations(): ConversationInfo[];
567
+ saveMessages(conversationId: string, messages: ChatMessage[]): void;
568
+ loadMessages(conversationId: string): ChatMessage[];
569
+ deleteConversation(conversationId: string): void;
570
+ }
571
+
572
+ /**
573
+ * IndexedDB 存储策略
574
+ */
575
+ declare class IndexedDBStrategy implements ConversationStorageStrategy {
576
+ private dbName;
577
+ private dbVersion;
578
+ private db;
579
+ constructor(dbName?: string, dbVersion?: number);
580
+ /**
581
+ * 获取或初始化数据库连接
582
+ */
583
+ private getDB;
584
+ /**
585
+ * 加载所有会话(只包含元数据)
586
+ */
587
+ loadConversations(): Promise<ConversationInfo[]>;
588
+ /**
589
+ * 加载指定会话的所有消息
590
+ */
591
+ loadMessages(conversationId: string): Promise<ChatMessage[]>;
592
+ /**
593
+ * 保存或更新会话元数据
594
+ */
595
+ saveConversation(conversation: ConversationInfo): Promise<void>;
596
+ /**
597
+ * 保存指定会话的消息
598
+ */
599
+ saveMessages(conversationId: string, messages: ChatMessage[]): Promise<void>;
600
+ /**
601
+ * 删除会话及其所有消息
602
+ */
603
+ deleteConversation(conversationId: string): Promise<void>;
604
+ }
605
+
606
+ interface LocalStorageConfig {
607
+ /** 存储键名 (default: 'tiny-robot-ai-conversations') */
608
+ key?: string;
609
+ }
610
+ interface IndexedDBConfig {
611
+ /** 数据库名称 (default: 'tiny-robot-ai-db') */
612
+ dbName?: string;
613
+ /** 数据库版本 (default: 1) */
614
+ dbVersion?: number;
615
+ }
616
+ /**
617
+ * LocalStorage 策略工厂函数
618
+ */
619
+ declare function localStorageStrategyFactory(config?: LocalStorageConfig): ConversationStorageStrategy;
620
+ /**
621
+ * IndexedDB 策略工厂函数
622
+ */
623
+ declare function indexedDBStorageStrategyFactory(config?: IndexedDBConfig): ConversationStorageStrategy;
624
+
625
+ /**
626
+ * 工具函数模块
627
+ * 提供一些实用的辅助函数
628
+ */
629
+
630
+ /**
631
+ * 处理SSE流式响应
632
+ * @param response fetch响应对象
633
+ * @param handler 流处理器
634
+ */
635
+ declare function handleSSEStream(response: Response, handler: StreamHandler, signal?: AbortSignal): Promise<void>;
636
+ /**
637
+ * 格式化消息
638
+ * 将各种格式的消息转换为标准的ChatMessage格式
639
+ * @param messages 消息数组
640
+ * @returns 标准格式的消息数组
641
+ */
642
+ declare function formatMessages(messages: Array<ChatMessage | string>): ChatMessage[];
643
+ /**
644
+ * 从响应中提取文本内容
645
+ * @param response 聊天完成响应
646
+ * @returns 文本内容
647
+ */
648
+ declare function extractTextFromResponse(response: ChatCompletionResponse): string;
649
+ /**
650
+ * 将 SSE 流转换为异步生成器。
651
+ * 将服务器发送事件(SSE)流式响应转换为异步生成器,逐个产出解析后的数据
652
+ *
653
+ * 当取消信号被触发时,会抛出 name 为 'AbortError' 的错误
654
+ * @param response fetch 响应对象
655
+ * @param options 配置选项
656
+ * @param options.signal 可选的取消信号,用于中断流处理
657
+ * @returns 异步生成器,产出类型为 T 的数据
658
+ * @template T 生成器产出的数据类型,默认为 any
659
+ */
660
+ declare function sseStreamToGenerator<T = any>(response: Response, options?: {
661
+ signal?: AbortSignal;
662
+ }): AsyncGenerator<T, void, unknown>;
663
+
286
664
  declare const useConversation: (options: UseConversationOptions) => UseConversationReturn;
287
665
 
288
666
  declare const fallbackRolePlugin: (options?: UseMessagePlugin & {
@@ -366,4 +744,4 @@ declare const toolPlugin: (options: UseMessagePlugin & {
366
744
 
367
745
  declare const useMessage: (options: UseMessageOptions) => UseMessageReturn;
368
746
 
369
- export { type BasePluginContext, type ChatCompletion, type ChatMessage, type Choice, type CompletionChoice, type Conversation, type ConversationInfo, type ConversationStorageStrategy, type DeltaChoice, EXCLUDE_MODE_REMOVE, type MessageMetadata, type MessageRequestBody, type RequestProcessingState, type RequestState, type Tool, type ToolCall, type Usage, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessagePlugin, type UseMessageReturn, createSSEStreamGenerator, fallbackRolePlugin, lengthPlugin, thinkingPlugin, toolPlugin, useConversation, useMessage };
747
+ export { type AIAdapterError, AIClient, type AIModelConfig, type AIProvider, BaseModelProvider, type BasePluginContext, type ChatCompletion, type ChatCompletionOptions, type ChatCompletionRequest, type ChatCompletionResponse, type ChatCompletionResponseChoice, type ChatCompletionResponseMessage, type ChatCompletionResponseUsage, type ChatCompletionStreamResponse, type ChatCompletionStreamResponseChoice, type ChatCompletionStreamResponseDelta, type ChatHistory, type ChatMessage, type Choice, type CompletionChoice, type Conversation, type ConversationInfo, type ConversationStorageStrategy, type DeltaChoice, EXCLUDE_MODE_REMOVE, ErrorType, type IndexedDBConfig, IndexedDBStrategy, type LocalStorageConfig, LocalStorageStrategy, type MaybePromise, type MessageMetadata, type MessageRequestBody, type MessageRole, OpenAIProvider, type RequestProcessingState, type RequestState, StreamEventType, type StreamHandler, type Tool, type ToolCall, type Usage, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessagePlugin, type UseMessageReturn, extractTextFromResponse, fallbackRolePlugin, formatMessages, handleSSEStream, indexedDBStorageStrategyFactory, lengthPlugin, localStorageStrategyFactory, sseStreamToGenerator, thinkingPlugin, toolPlugin, useConversation, useMessage };
package/dist/index.d.ts CHANGED
@@ -1,27 +1,57 @@
1
1
  import { Ref, ComputedRef } from 'vue';
2
2
 
3
- type MaybePromise<T> = T | Promise<T>;
4
-
5
3
  /**
6
- * 工具函数模块
7
- * 提供一些实用的辅助函数
4
+ * 模型Provider基类
8
5
  */
6
+ declare abstract class BaseModelProvider {
7
+ protected config: AIModelConfig;
8
+ /**
9
+ * @param config AI模型配置
10
+ */
11
+ constructor(config: AIModelConfig);
12
+ /**
13
+ * 发送聊天请求并获取响应
14
+ * @param request 聊天请求参数
15
+ * @returns 聊天响应
16
+ */
17
+ abstract chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
18
+ /**
19
+ * 发送流式聊天请求并通过处理器处理响应
20
+ * @param request 聊天请求参数
21
+ * @param handler 流式响应处理器
22
+ */
23
+ abstract chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
24
+ /**
25
+ * 更新配置
26
+ * @param config 新的AI模型配置
27
+ */
28
+ updateConfig(config: AIModelConfig): void;
29
+ /**
30
+ * 获取当前配置
31
+ * @returns AI模型配置
32
+ */
33
+ getConfig(): AIModelConfig;
34
+ /**
35
+ * 验证请求参数
36
+ * @param request 聊天请求参数
37
+ */
38
+ protected validateRequest(request: ChatCompletionRequest): void;
39
+ }
9
40
 
10
- declare function createSSEStreamGenerator<T = any>(response: Response, options?: {
11
- signal?: AbortSignal;
12
- }): AsyncGenerator<T, void, unknown>;
13
-
14
- interface Tool {
41
+ type MaybePromise<T> = T | Promise<T>;
42
+ /**
43
+ * 消息角色类型
44
+ */
45
+ type MessageRole = 'system' | 'user' | 'assistant';
46
+ interface ToolCall {
47
+ index: number;
48
+ id: string;
15
49
  type: 'function';
16
50
  function: {
17
51
  name: string;
18
- description: string;
19
- /**
20
- * function 的输入参数,以 JSON Schema 对象描述
21
- */
22
- parameters: any;
52
+ arguments: string;
53
+ result?: string;
23
54
  };
24
- [key: string]: any;
25
55
  }
26
56
  interface MessageMetadata {
27
57
  createdAt?: number;
@@ -30,31 +60,254 @@ interface MessageMetadata {
30
60
  model?: string;
31
61
  [key: string]: any;
32
62
  }
63
+ /**
64
+ * 聊天消息接口
65
+ */
33
66
  interface ChatMessage {
34
67
  role: string;
35
68
  content: string;
69
+ reasoning_content?: string;
36
70
  metadata?: MessageMetadata;
37
71
  tool_calls?: ToolCall[];
38
72
  tool_call_id?: string;
39
73
  [key: string]: any;
40
74
  [key: symbol]: any;
41
75
  }
42
- interface MessageRequestBody {
43
- messages: Partial<ChatMessage>[];
44
- [key: string]: any;
76
+ /**
77
+ * 聊天历史记录
78
+ */
79
+ type ChatHistory = ChatMessage[];
80
+ /**
81
+ * 聊天完成请求选项
82
+ */
83
+ interface ChatCompletionOptions {
84
+ model?: string;
85
+ temperature?: number;
86
+ top_p?: number;
87
+ n?: number;
88
+ stream?: boolean;
89
+ max_tokens?: number;
90
+ signal?: AbortSignal;
45
91
  }
46
- type RequestState = 'idle' | 'processing' | 'completed' | 'aborted' | 'error';
47
- type RequestProcessingState = 'requesting' | 'completing' | string;
48
- interface ToolCall {
92
+ /**
93
+ * 聊天完成请求参数
94
+ */
95
+ interface ChatCompletionRequest {
96
+ messages: ChatMessage[];
97
+ options?: ChatCompletionOptions;
98
+ }
99
+ /**
100
+ * 聊天完成响应消息
101
+ */
102
+ interface ChatCompletionResponseMessage {
103
+ role: MessageRole;
104
+ content: string;
105
+ [x: string]: unknown;
106
+ }
107
+ /**
108
+ * 聊天完成响应选择
109
+ */
110
+ interface ChatCompletionResponseChoice {
111
+ index: number;
112
+ message: ChatCompletionResponseMessage;
113
+ finish_reason: string;
114
+ }
115
+ /**
116
+ * 聊天完成响应使用情况
117
+ */
118
+ interface ChatCompletionResponseUsage {
119
+ prompt_tokens: number;
120
+ completion_tokens: number;
121
+ total_tokens: number;
122
+ }
123
+ /**
124
+ * 聊天完成响应
125
+ */
126
+ interface ChatCompletionResponse {
127
+ id: string;
128
+ object: string;
129
+ created: number;
130
+ model: string;
131
+ choices: ChatCompletionResponseChoice[];
132
+ usage: ChatCompletionResponseUsage;
133
+ }
134
+ /**
135
+ * 流式聊天完成响应增量
136
+ */
137
+ interface ChatCompletionStreamResponseDelta {
138
+ content?: string;
139
+ role?: MessageRole;
140
+ [x: string]: unknown;
141
+ }
142
+ /**
143
+ * 流式聊天完成响应选择
144
+ */
145
+ interface ChatCompletionStreamResponseChoice {
49
146
  index: number;
147
+ delta: ChatCompletionStreamResponseDelta;
148
+ finish_reason: string | null;
149
+ }
150
+ /**
151
+ * 流式聊天完成响应
152
+ */
153
+ interface ChatCompletionStreamResponse {
50
154
  id: string;
155
+ object: string;
156
+ created: number;
157
+ model: string;
158
+ choices: ChatCompletionStreamResponseChoice[];
159
+ }
160
+ /**
161
+ * AI模型提供商类型
162
+ */
163
+ type AIProvider = 'openai' | 'deepseek' | 'custom';
164
+ /**
165
+ * AI模型配置接口
166
+ */
167
+ interface AIModelConfig {
168
+ provider: AIProvider;
169
+ providerImplementation?: BaseModelProvider;
170
+ apiKey?: string;
171
+ apiUrl?: string;
172
+ apiVersion?: string;
173
+ defaultModel?: string;
174
+ defaultOptions?: ChatCompletionOptions;
175
+ }
176
+ /**
177
+ * 错误类型
178
+ */
179
+ declare enum ErrorType {
180
+ NETWORK_ERROR = "network_error",
181
+ AUTHENTICATION_ERROR = "authentication_error",
182
+ RATE_LIMIT_ERROR = "rate_limit_error",
183
+ SERVER_ERROR = "server_error",
184
+ MODEL_ERROR = "model_error",
185
+ TIMEOUT_ERROR = "timeout_error",
186
+ UNKNOWN_ERROR = "unknown_error"
187
+ }
188
+ /**
189
+ * AI适配器错误
190
+ */
191
+ interface AIAdapterError {
192
+ type: ErrorType;
193
+ message: string;
194
+ statusCode?: number;
195
+ originalError?: object;
196
+ }
197
+ /**
198
+ * 流式响应事件类型
199
+ */
200
+ declare enum StreamEventType {
201
+ DATA = "data",
202
+ ERROR = "error",
203
+ DONE = "done"
204
+ }
205
+ /**
206
+ * 流式响应处理器
207
+ */
208
+ interface StreamHandler {
209
+ onData: (data: ChatCompletionStreamResponse) => void;
210
+ onError: (error: AIAdapterError) => void;
211
+ onDone: (finishReason?: string) => void;
212
+ }
213
+
214
+ /**
215
+ * AI客户端类
216
+ * 负责根据配置选择合适的提供商并处理请求
217
+ */
218
+
219
+ /**
220
+ * @deprecated
221
+ * AI客户端类
222
+ */
223
+ declare class AIClient {
224
+ private provider;
225
+ private config;
226
+ /**
227
+ * 构造函数
228
+ * @param config AI模型配置
229
+ */
230
+ constructor(config: AIModelConfig);
231
+ /**
232
+ * 创建提供商实例
233
+ * @param config AI模型配置
234
+ * @returns 提供商实例
235
+ */
236
+ private createProvider;
237
+ /**
238
+ * 发送聊天请求并获取响应
239
+ * @param request 聊天请求参数
240
+ * @returns 聊天响应
241
+ */
242
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
243
+ /**
244
+ * 发送流式聊天请求并通过处理器处理响应
245
+ * @param request 聊天请求参数
246
+ * @param handler 流式响应处理器
247
+ */
248
+ chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
249
+ /**
250
+ * 获取当前配置
251
+ * @returns AI模型配置
252
+ */
253
+ getConfig(): AIModelConfig;
254
+ /**
255
+ * 更新配置
256
+ * @param config 新的AI模型配置
257
+ */
258
+ updateConfig(config: Partial<AIModelConfig>): void;
259
+ }
260
+
261
+ /**
262
+ * OpenAI提供商
263
+ * 用于与OpenAI API进行交互
264
+ */
265
+
266
+ declare class OpenAIProvider extends BaseModelProvider {
267
+ private baseURL;
268
+ private apiKey;
269
+ private defaultModel;
270
+ /**
271
+ * @param config AI模型配置
272
+ */
273
+ constructor(config: AIModelConfig);
274
+ /**
275
+ * 发送聊天请求并获取响应
276
+ * @param request 聊天请求参数
277
+ * @returns 聊天响应
278
+ */
279
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
280
+ /**
281
+ * 发送流式聊天请求并通过处理器处理响应
282
+ * @param request 聊天请求参数
283
+ * @param handler 流式响应处理器
284
+ */
285
+ chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>;
286
+ /**
287
+ * 更新配置
288
+ * @param config 新的AI模型配置
289
+ */
290
+ updateConfig(config: AIModelConfig): void;
291
+ }
292
+
293
+ interface Tool {
51
294
  type: 'function';
52
295
  function: {
53
296
  name: string;
54
- arguments: string;
55
- result?: string;
297
+ description: string;
298
+ /**
299
+ * function 的输入参数,以 JSON Schema 对象描述
300
+ */
301
+ parameters: any;
56
302
  };
303
+ [key: string]: any;
304
+ }
305
+ interface MessageRequestBody {
306
+ messages: Partial<ChatMessage>[];
307
+ [key: string]: any;
57
308
  }
309
+ type RequestState = 'idle' | 'processing' | 'completed' | 'aborted' | 'error';
310
+ type RequestProcessingState = 'requesting' | 'completing' | string;
58
311
  interface Usage {
59
312
  prompt_tokens: number;
60
313
  completion_tokens: number;
@@ -101,7 +354,16 @@ interface ChatCompletion {
101
354
  }
102
355
  interface UseMessageOptions {
103
356
  initialMessages?: ChatMessage[];
104
- requestMessageFields?: (keyof ChatMessage)[];
357
+ /**
358
+ * 请求消息时,要包含的字段(白名单)。默认包含所有字段。
359
+ * 如果 `requestMessageFieldsExclude` 存在,会先取 `requestMessageFields` 中的字段,再排除 `requestMessageFieldsExclude` 中的字段
360
+ */
361
+ requestMessageFields?: string[];
362
+ /**
363
+ * 请求消息时,要排除的字段(黑名单)。默认会排除 `state`、`metadata`、`loading` 字段(这几个字段是给UI展示用的)。
364
+ * 如果 `requestMessageFields` 存在,会先取 `requestMessageFields` 中的字段,再排除 `requestMessageFieldsExclude` 中的字段
365
+ */
366
+ requestMessageFieldsExclude?: string[];
105
367
  plugins?: UseMessagePlugin[];
106
368
  responseProvider: <T = ChatCompletion>(requestBody: MessageRequestBody, abortSignal: AbortSignal) => Promise<T> | AsyncGenerator<T> | Promise<AsyncGenerator<T>>;
107
369
  /**
@@ -111,6 +373,7 @@ interface UseMessageOptions {
111
373
  */
112
374
  onCompletionChunk?: (context: BasePluginContext & {
113
375
  currentMessage: ChatMessage;
376
+ choice: CompletionChoice;
114
377
  chunk: ChatCompletion;
115
378
  }, runDefault: () => void) => void;
116
379
  }
@@ -129,7 +392,6 @@ interface BasePluginContext {
129
392
  currentTurn: ChatMessage[];
130
393
  requestState: RequestState;
131
394
  processingState?: RequestProcessingState;
132
- requestMessageFields: (keyof ChatMessage)[];
133
395
  plugins: UseMessagePlugin[];
134
396
  setRequestState: (state: RequestState, processingState?: RequestProcessingState) => void;
135
397
  abortSignal: AbortSignal;
@@ -150,7 +412,7 @@ interface UseMessagePlugin {
150
412
  /**
151
413
  * 是否禁用插件。useMessage 可能会内置一些默认插件,如果需要禁用,可以设置为 true。
152
414
  */
153
- disabled?: boolean;
415
+ disabled?: boolean | ((context: BasePluginContext) => boolean);
154
416
  /**
155
417
  * 一次对话回合(turn)开始钩子:用户消息入队后、正式发起请求之前触发。
156
418
  * 按插件注册顺序串行执行,便于做有序初始化/校验;出错则中断流程。
@@ -195,6 +457,7 @@ interface UseMessagePlugin {
195
457
  onError?: (context: BasePluginContext & {
196
458
  error: unknown;
197
459
  }) => void;
460
+ onFinally?: (context: BasePluginContext) => void;
198
461
  }
199
462
 
200
463
  interface ConversationInfo {
@@ -215,28 +478,6 @@ interface Conversation extends ConversationInfo {
215
478
  */
216
479
  engine: UseMessageReturn;
217
480
  }
218
- interface ConversationStorageStrategy {
219
- /**
220
- * Load all conversations (id and title only).
221
- */
222
- loadConversations?: () => MaybePromise<ConversationInfo[]>;
223
- /**
224
- * Load all messages for a given conversation.
225
- */
226
- loadMessages?: (conversationId: string) => MaybePromise<ChatMessage[]>;
227
- /**
228
- * Persist conversation metadata (create or update).
229
- */
230
- saveConversation?: (conversation: ConversationInfo) => MaybePromise<void>;
231
- /**
232
- * Persist messages for a given conversation.
233
- */
234
- saveMessages?: (conversationId: string, messages: ChatMessage[]) => MaybePromise<void>;
235
- /**
236
- * Optional method to delete a conversation and its messages.
237
- */
238
- deleteConversation?: (conversationId: string) => MaybePromise<void>;
239
- }
240
481
  interface UseConversationOptions {
241
482
  /**
242
483
  * Base useMessage options for all conversations.
@@ -260,6 +501,11 @@ interface UseConversationOptions {
260
501
  * When provided, conversation list and messages can be loaded and persisted.
261
502
  */
262
503
  storage?: ConversationStorageStrategy;
504
+ /**
505
+ * Called when the initial conversation list has been loaded from storage.
506
+ * Only fired when storage.loadConversations is available and has completed.
507
+ */
508
+ onLoad?: (conversations: ConversationInfo[]) => void;
263
509
  }
264
510
  interface UseConversationReturn {
265
511
  conversations: Ref<ConversationInfo[]>;
@@ -277,12 +523,144 @@ interface UseConversationReturn {
277
523
  }) => Conversation;
278
524
  switchConversation: (id: string) => Promise<Conversation | null>;
279
525
  deleteConversation: (id: string) => Promise<void>;
526
+ clear: () => void;
280
527
  updateConversationTitle: (id: string, title?: string) => void;
281
528
  saveMessages: (id?: string) => void;
282
529
  sendMessage: (content: string) => void;
283
530
  abortActiveRequest: () => Promise<void>;
284
531
  }
285
532
 
533
+ /**
534
+ * 存储策略接口
535
+ */
536
+ interface ConversationStorageStrategy {
537
+ /**
538
+ * Load all conversations (id and title only).
539
+ */
540
+ loadConversations: () => MaybePromise<ConversationInfo[]>;
541
+ /**
542
+ * Load all messages for a given conversation.
543
+ */
544
+ loadMessages: (conversationId: string) => MaybePromise<ChatMessage[]>;
545
+ /**
546
+ * Persist conversation metadata (create or update).
547
+ */
548
+ saveConversation: (conversation: ConversationInfo) => MaybePromise<void>;
549
+ /**
550
+ * Persist messages for a given conversation.
551
+ */
552
+ saveMessages: (conversationId: string, messages: ChatMessage[]) => MaybePromise<void>;
553
+ /**
554
+ * Optional method to delete a conversation and its messages.
555
+ */
556
+ deleteConversation?: (conversationId: string) => MaybePromise<void>;
557
+ }
558
+
559
+ /**
560
+ * 本地存储策略
561
+ */
562
+ declare class LocalStorageStrategy implements ConversationStorageStrategy {
563
+ private storageKey;
564
+ constructor(storageKey?: string);
565
+ saveConversation(conversation: ConversationInfo): void;
566
+ loadConversations(): ConversationInfo[];
567
+ saveMessages(conversationId: string, messages: ChatMessage[]): void;
568
+ loadMessages(conversationId: string): ChatMessage[];
569
+ deleteConversation(conversationId: string): void;
570
+ }
571
+
572
+ /**
573
+ * IndexedDB 存储策略
574
+ */
575
+ declare class IndexedDBStrategy implements ConversationStorageStrategy {
576
+ private dbName;
577
+ private dbVersion;
578
+ private db;
579
+ constructor(dbName?: string, dbVersion?: number);
580
+ /**
581
+ * 获取或初始化数据库连接
582
+ */
583
+ private getDB;
584
+ /**
585
+ * 加载所有会话(只包含元数据)
586
+ */
587
+ loadConversations(): Promise<ConversationInfo[]>;
588
+ /**
589
+ * 加载指定会话的所有消息
590
+ */
591
+ loadMessages(conversationId: string): Promise<ChatMessage[]>;
592
+ /**
593
+ * 保存或更新会话元数据
594
+ */
595
+ saveConversation(conversation: ConversationInfo): Promise<void>;
596
+ /**
597
+ * 保存指定会话的消息
598
+ */
599
+ saveMessages(conversationId: string, messages: ChatMessage[]): Promise<void>;
600
+ /**
601
+ * 删除会话及其所有消息
602
+ */
603
+ deleteConversation(conversationId: string): Promise<void>;
604
+ }
605
+
606
+ interface LocalStorageConfig {
607
+ /** 存储键名 (default: 'tiny-robot-ai-conversations') */
608
+ key?: string;
609
+ }
610
+ interface IndexedDBConfig {
611
+ /** 数据库名称 (default: 'tiny-robot-ai-db') */
612
+ dbName?: string;
613
+ /** 数据库版本 (default: 1) */
614
+ dbVersion?: number;
615
+ }
616
+ /**
617
+ * LocalStorage 策略工厂函数
618
+ */
619
+ declare function localStorageStrategyFactory(config?: LocalStorageConfig): ConversationStorageStrategy;
620
+ /**
621
+ * IndexedDB 策略工厂函数
622
+ */
623
+ declare function indexedDBStorageStrategyFactory(config?: IndexedDBConfig): ConversationStorageStrategy;
624
+
625
+ /**
626
+ * 工具函数模块
627
+ * 提供一些实用的辅助函数
628
+ */
629
+
630
+ /**
631
+ * 处理SSE流式响应
632
+ * @param response fetch响应对象
633
+ * @param handler 流处理器
634
+ */
635
+ declare function handleSSEStream(response: Response, handler: StreamHandler, signal?: AbortSignal): Promise<void>;
636
+ /**
637
+ * 格式化消息
638
+ * 将各种格式的消息转换为标准的ChatMessage格式
639
+ * @param messages 消息数组
640
+ * @returns 标准格式的消息数组
641
+ */
642
+ declare function formatMessages(messages: Array<ChatMessage | string>): ChatMessage[];
643
+ /**
644
+ * 从响应中提取文本内容
645
+ * @param response 聊天完成响应
646
+ * @returns 文本内容
647
+ */
648
+ declare function extractTextFromResponse(response: ChatCompletionResponse): string;
649
+ /**
650
+ * 将 SSE 流转换为异步生成器。
651
+ * 将服务器发送事件(SSE)流式响应转换为异步生成器,逐个产出解析后的数据
652
+ *
653
+ * 当取消信号被触发时,会抛出 name 为 'AbortError' 的错误
654
+ * @param response fetch 响应对象
655
+ * @param options 配置选项
656
+ * @param options.signal 可选的取消信号,用于中断流处理
657
+ * @returns 异步生成器,产出类型为 T 的数据
658
+ * @template T 生成器产出的数据类型,默认为 any
659
+ */
660
+ declare function sseStreamToGenerator<T = any>(response: Response, options?: {
661
+ signal?: AbortSignal;
662
+ }): AsyncGenerator<T, void, unknown>;
663
+
286
664
  declare const useConversation: (options: UseConversationOptions) => UseConversationReturn;
287
665
 
288
666
  declare const fallbackRolePlugin: (options?: UseMessagePlugin & {
@@ -366,4 +744,4 @@ declare const toolPlugin: (options: UseMessagePlugin & {
366
744
 
367
745
  declare const useMessage: (options: UseMessageOptions) => UseMessageReturn;
368
746
 
369
- export { type BasePluginContext, type ChatCompletion, type ChatMessage, type Choice, type CompletionChoice, type Conversation, type ConversationInfo, type ConversationStorageStrategy, type DeltaChoice, EXCLUDE_MODE_REMOVE, type MessageMetadata, type MessageRequestBody, type RequestProcessingState, type RequestState, type Tool, type ToolCall, type Usage, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessagePlugin, type UseMessageReturn, createSSEStreamGenerator, fallbackRolePlugin, lengthPlugin, thinkingPlugin, toolPlugin, useConversation, useMessage };
747
+ export { type AIAdapterError, AIClient, type AIModelConfig, type AIProvider, BaseModelProvider, type BasePluginContext, type ChatCompletion, type ChatCompletionOptions, type ChatCompletionRequest, type ChatCompletionResponse, type ChatCompletionResponseChoice, type ChatCompletionResponseMessage, type ChatCompletionResponseUsage, type ChatCompletionStreamResponse, type ChatCompletionStreamResponseChoice, type ChatCompletionStreamResponseDelta, type ChatHistory, type ChatMessage, type Choice, type CompletionChoice, type Conversation, type ConversationInfo, type ConversationStorageStrategy, type DeltaChoice, EXCLUDE_MODE_REMOVE, ErrorType, type IndexedDBConfig, IndexedDBStrategy, type LocalStorageConfig, LocalStorageStrategy, type MaybePromise, type MessageMetadata, type MessageRequestBody, type MessageRole, OpenAIProvider, type RequestProcessingState, type RequestState, StreamEventType, type StreamHandler, type Tool, type ToolCall, type Usage, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessagePlugin, type UseMessageReturn, extractTextFromResponse, fallbackRolePlugin, formatMessages, handleSSEStream, indexedDBStorageStrategyFactory, lengthPlugin, localStorageStrategyFactory, sseStreamToGenerator, thinkingPlugin, toolPlugin, useConversation, useMessage };
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
- "use strict";var K=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var ce=Object.getOwnPropertyNames;var ue=Object.prototype.hasOwnProperty;var ge=(e,t)=>{for(var n in t)K(e,n,{get:t[n],enumerable:!0})},fe=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ce(t))!ue.call(e,o)&&o!==n&&K(e,o,{get:()=>t[o],enumerable:!(s=le(t,o))||s.enumerable});return e};var de=e=>fe(K({},"__esModule",{value:!0}),e);var Te={};ge(Te,{EXCLUDE_MODE_REMOVE:()=>oe,createSSEStreamGenerator:()=>Y,fallbackRolePlugin:()=>J,lengthPlugin:()=>X,thinkingPlugin:()=>$,toolPlugin:()=>ye,useConversation:()=>be,useMessage:()=>L});module.exports=de(Te);function z(e="The operation was aborted"){let t=new Error(e);return t.name="AbortError",t}async function*Y(e,t={}){let n=e.body?.getReader();if(!n)throw new Error("ReadableStream not supported");let{signal:s}=t,o=new TextDecoder,a="",i=()=>{n.cancel()};s?.addEventListener("abort",i);try{for(;;){if(s?.aborted)throw z();let u;try{u=await n.read()}catch(w){throw s?.aborted?z():w}let{done:y,value:d}=u;if(y){if(s?.aborted)throw z();return}let M=o.decode(d,{stream:!0});a+=M;let A=a.split(`
2
- `);a=A.pop()||"";for(let w of A)if(w.trim()!==""&&w.startsWith("data: ")){let v=w.slice(6);if(v==="[DONE]")return;try{yield JSON.parse(v)}catch(T){console.warn("Failed to parse SSE data:",v,T)}}}}finally{s?.removeEventListener("abort",i),n.releaseLock()}}var F=require("vue");var q=require("vue");var J=(e={})=>{let{fallbackRole:t="assistant",...n}=e;return{name:"fallbackRole",...n,onBeforeRequest(s){let{requestBody:o,messages:a}=s;return o.messages=a.map(i=>({...i,role:i.role||t})),n.onBeforeRequest?.(s)}}};var X=(e={})=>{let{continueContent:t="Please continue with your previous answer.",...n}=e;return{name:"length",...n,onAfterRequest:async s=>{let{lastChoice:o,appendMessage:a,requestNext:i}=s;return o?.finish_reason==="length"&&(a({role:"user",content:t}),i()),n.onAfterRequest?.(s)}}};var $=(e={})=>({name:"thinking",...e,onCompletionChunk(t){let{choice:n,currentMessage:s}=t,a=typeof(n?.message?.reasoning_content||n?.delta?.reasoning_content)=="string";return s.state?s.state.thinking=a:s.state={thinking:a},e.onCompletionChunk?.(t)},onTurnEnd(t){let n=t.currentTurn.slice(-1)[0];return n?.state&&(n.state.thinking=void 0),e.onTurnEnd?.(t)}});var se=require("vue");var B=class extends Error{constructor(t){super(t),this.name="AbortError"}};function pe(e){if(e.aborted)return{promise:Promise.reject(new B(String(e.reason??"Aborted"))),cleanup:()=>{}};let t=null;return{promise:new Promise((o,a)=>{t=()=>{a(new B(String(e.reason??"Aborted")))},e.addEventListener("abort",t,{once:!0})}),cleanup:()=>{t&&(e.removeEventListener("abort",t),t=null)}}}function te(e,t){let{promise:n,cleanup:s}=pe(t);return Promise.race([e,n]).finally(s)}function ne(e,t){let n={};for(let s of t)s in e&&(n[s]=e[s]);return n}async function*W(e){if(Z(e)){yield*e;return}let t=await e;if(Z(t)){yield*t;return}yield t}function Z(e){return e&&typeof e=="object"&&typeof e[Symbol.asyncIterator]=="function"}var Q=e=>typeof e=="object"&&e!==null,ee=e=>Q(e)&&typeof e.index=="number",G=(e,t)=>{for(let[n,s]of Object.entries(t)){let o=e[n];if(o)if(typeof o=="string"&&typeof s=="string")n==="type"&&o||(e[n]=o+s);else if(Array.isArray(o)&&Array.isArray(s))if(o.every(a=>ee(a))&&s.every(a=>ee(a))){let a=new Map(o.map(d=>[d.index,d])),i=new Map(s.map(d=>[d.index,d]));for(let[d,M]of i)if(a.has(d)){let A=a.get(d);a.set(d,G(A,M))}else a.set(d,M);let u=Math.max(...Array.from(a.keys()),-1)+1,y=u>o.length?Array.from({length:u}):o;for(let[d,M]of a)y[d]=M;e[n]=y}else e[n]=[...o,...s];else Q(o)&&Q(s)&&(e[n]=G(o,s));else e[n]=s}return e};function me(e,t){let n=[];for(let s=0;s<e.length;s++){let o=e[s];if(o.role==="assistant"&&o.tool_calls&&o.tool_calls.length>0){let a=new Set(o.tool_calls.map(y=>y.id)),i=new Set;for(let y=s+1;y<e.length;y++){let d=e[y];d.role==="tool"&&d.tool_call_id&&a.has(d.tool_call_id)&&i.add(d.tool_call_id)}let u=o.tool_calls.map(y=>y.id).filter(y=>!i.has(y));u.length>0&&n.push({insertAfterIndex:s,missingToolCallIds:u})}}for(let s=n.length-1;s>=0;s--){let{insertAfterIndex:o,missingToolCallIds:a}=n[s],i=a.map(u=>({role:"tool",tool_call_id:u,content:t}));e.splice(o+1,0,...i)}}var oe="remove";function he(e,t,n,s){let o=e.filter(i=>i[n]),a=new Set(o.flatMap(i=>i.tool_calls?.map(u=>u.id)??[]));if(t===oe)for(let i=e.length-1;i>=0;i--){let u=e[i];(u[n]||u[s]||u.tool_call_id&&a.has(u.tool_call_id))&&e.splice(i,1)}else if(t===!0)for(let i of e)(i[n]||i.tool_call_id&&a.has(i.tool_call_id))&&(i[s]=!0,delete i[n])}var ye=e=>{let{getTools:t,beforeCallTools:n,callTool:s,onToolCallStart:o,onToolCallEnd:a,toolCallCancelledContent:i="Tool call cancelled.",toolCallFailedContent:u="Tool call failed.",autoFillMissingToolMessages:y=!1,excludeToolMessagesNextTurn:d=!1,...M}=e,A=Symbol("doNotSendNextTurn"),w=Symbol("doNotSend"),v=(...C)=>{let[p,{primaryMessage:r}]=C;r.state.toolCall[p.id].status="running",o?.(...C)},T=(...C)=>{let[p,{status:r,primaryMessage:l}]=C;l.state.toolCall[p.id].status=r,a?.(...C)};return{name:"tool",...M,onTurnStart:C=>{let{messages:p}=C;return y&&me(p,i),d&&he(p,d,A,w),M.onTurnStart?.(C)},onBeforeRequest:async C=>{let{messages:p,requestBody:r}=C;d===!0&&(r.messages=p.filter(g=>!g[w]));let l=await t?.();return l&&l.length>0&&(r.tools=l),M.onBeforeRequest?.(C)},onAfterRequest:async C=>{let{currentMessage:p,lastChoice:r,appendMessage:l,abortSignal:g,setRequestState:f,requestNext:x}=C;if(r?.finish_reason!=="tool_calls"||!p.tool_calls?.length)return;d&&(p[A]=!0),f("processing","calling-tools"),await n?.(p.tool_calls,{...C,currentMessage:p});let E=p.tool_calls.map(async S=>{let D=Math.floor(Date.now()/1e3),O=(0,se.reactive)({role:"tool",tool_call_id:S.id,content:"",metadata:{createdAt:D,updatedAt:D}});l(O);let c={...C,primaryMessage:p,toolMessage:O};v(S,c);try{let h=s(S,c),b=W(h);for await(let _ of b){if(typeof _=="string")O.content+=_;else{let m={};try{m=JSON.parse(O.content||"{}")}catch(R){console.warn(R)}O.content=JSON.stringify(G(m,_))}O.metadata.updatedAt=Math.floor(Date.now()/1e3)}T(S,{...c,status:"success"})}catch(h){let b=h instanceof Error?h:new Error(String(h));if(g.aborted){T(S,{...c,status:"cancelled",error:b});return}console.error(h),O.content.length===0&&(O.content=u),T(S,{...c,status:"failed",error:b})}});return await Promise.all(E),x(),M.onAfterRequest?.(C)},onCompletionChunk:C=>{var r,l,g;let{currentMessage:p}=C;if(Array.isArray(p.tool_calls))for(let f of p.tool_calls)p.state?.toolCall?.[f.id]?.status||(p.state??(p.state={}),(r=p.state).toolCall??(r.toolCall={}),(l=p.state.toolCall)[g=f.id]??(l[g]={}));return M.onCompletionChunk?.(C)}}};var Ce=e=>{let t=[];for(let n of e){if(n.name){let s=t.findIndex(o=>o.name===n.name);s!==-1&&t.splice(s,1)}t.push(n)}return t},L=e=>{let{initialMessages:t=[],requestMessageFields:n=["role","content","tool_calls","tool_call_id"],plugins:s=[],onCompletionChunk:o}=e,a=(0,q.ref)("idle"),i=(0,q.ref)(void 0),u=(0,q.ref)(t),y=(0,q.ref)(e.responseProvider),d=null,M=[],A={},w=[J(),$(),X()],v=Ce(w.concat(s)),T=(0,q.computed)(()=>a.value==="processing"),C=async c=>{if(!c||!c.trim()){console.warn("Cannot send empty message");return}if(T.value){console.warn("Cannot send message while processing is in progress");return}let h=Math.floor(Date.now()/1e3);u.value.push({role:"user",content:c.trim(),metadata:{createdAt:h,updatedAt:h}}),M.push(u.value[u.value.length-1]),await E()},p=async(...c)=>{if(T.value){console.warn("Cannot send message while processing is in progress");return}u.value.push(...c),M.push(...c),await E()},r=c=>c.map(h=>ne(h,n)),l=(c,h)=>{a.value=c,c==="processing"?i.value=h||"requesting":i.value=void 0},g=c=>{Object.assign(A,c)},f=()=>({messages:u.value,currentTurn:M,requestState:a.value,processingState:i.value,requestMessageFields:n,plugins:v,setRequestState:l,customContext:A,setCustomContext:g}),x=async(c,h)=>{l("processing","requesting");let b=new Proxy({messages:r(u.value)},{set(k,P,I){return P==="messages"?(k.messages=r(I),!0):(k[P]=I,!0)}}),_=f();for(let k of v.filter(P=>!P.disabled))await k.onBeforeRequest?.({..._,abortSignal:h,requestBody:b});let m=(0,q.reactive)({role:"",content:"",loading:!0});O(m);let R,V=c(b,h),U=W(V);for await(let k of U){l("processing","completing"),m.loading&&(m.loading=void 0);let P=k.choices?.find(N=>N.index===0);if(P){R=P;let N=()=>{m.metadata||(m.metadata={});let{created:j,...ie}=k;m.metadata.createdAt=j,m.metadata.updatedAt=Math.floor(Date.now()/1e3),Object.assign(m.metadata,ie),G(m,P.message||P.delta)};if(o){let j=f();o({...j,abortSignal:h,chunk:k,currentMessage:m},N)}else N()}let I=f();for(let N of v.filter(j=>!j.disabled))N.onCompletionChunk?.({...I,abortSignal:h,chunk:k,choice:P,currentMessage:m})}await D(m,c,h,R)},E=async()=>{let c=new AbortController;d=c;let h=y.value;A={};try{l("processing","requesting");let b=f();for(let m of v.filter(R=>!R.disabled))await m.onTurnStart?.({...b,abortSignal:c.signal});try{await x(h,c.signal),l("completed")}catch(m){if(c.signal.aborted||m instanceof B||m instanceof Error&&m.name==="AbortError")l("aborted");else throw m}let _=f();for(let m of v.filter(R=>!R.disabled))await m.onTurnEnd?.({..._,abortSignal:c.signal})}catch(b){l("error");let _=f();for(let m of v.filter(R=>!R.disabled))m.onError?.({..._,abortSignal:c.signal,error:b});throw b}finally{d=null,M.slice(-1)[0]&&(M.slice(-1)[0].loading=void 0),M=[]}},S=async()=>{d?.abort(),T.value&&await new Promise(c=>{let h=(0,q.watch)(T,b=>{b||(h(),c())},{immediate:!0})})},D=async(c,h,b,_)=>{let m=!1,R=f(),V=v.filter(U=>!U.disabled).map(U=>{if(!U.onAfterRequest)return null;let k=I=>{O(I)},P=()=>{m=!0};return U.onAfterRequest({...R,abortSignal:b,currentMessage:c,lastChoice:_,appendMessage:k,requestNext:P})}).filter(U=>U!==null);await te(Promise.all(V),b),m&&await x(h,b)},O=c=>{let h=Array.isArray(c)?c:[c];u.value.push(...h),M.push(...h)};return{requestState:a,processingState:i,messages:u,responseProvider:y,isProcessing:T,sendMessage:C,send:p,abortRequest:S}};var H=require("vue");function ae(e,t=200,n=!1,s=!0,o=!1){return Me(ve(t,n,s,o),e)}function Me(e,t){function n(...s){return new Promise((o,a)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(o).catch(a)})}return n}var re=()=>{};function ve(...e){let t=0,n,s=!0,o=re,a,i,u,y,d;!(0,H.isRef)(e[0])&&typeof e[0]=="object"?{delay:i,trailing:u=!0,leading:y=!0,rejectOnCancel:d=!1}=e[0]:[i,u=!0,y=!0,d=!1]=e;let M=()=>{n&&(clearTimeout(n),n=void 0,o(),o=re)};return w=>{let v=(0,H.toValue)(i),T=Date.now()-t,C=()=>a=w();return M(),v<=0?(t=Date.now(),C()):(T>v?(t=Date.now(),(y||!s)&&C()):u&&(a=new Promise((p,r)=>{o=d?r:p,n=setTimeout(()=>{t=Date.now(),s=!0,p(C()),M()},Math.max(0,v-T))})),!y&&!n&&(n=setTimeout(()=>s=!0,v)),s=!1,a)}}var be=e=>{let t=(0,F.ref)([]),n=new Map,s=new Map,o=(0,F.ref)(null),a=(0,F.computed)(()=>{let r=o.value;if(!r)return null;let l=t.value.find(f=>f.id===r);if(!l)return null;let g=n.get(r);return g?{...l,engine:g}:null}),i=r=>{if(!e.storage?.saveMessages)return;let l=r||o.value,g=t.value.find(x=>x.id===l);if(!g)return;g.updatedAt=Date.now(),e.storage?.saveConversation?.(g);let f=n.get(g.id);f&&e.storage.saveMessages(g.id,f.messages.value)},u=(r,l)=>{if(!e.autoSaveMessages||!e.storage?.saveMessages)return;let g=s.get(r);g&&g();let f=e.autoSaveThrottle??1e3,x=ae(()=>{i(r)},f,!0,!0),E=(0,F.watch)(l.messages,x,{deep:!0});s.set(r,E)},y=r=>{let l=s.get(r);l&&(l(),s.delete(r))};e.storage?.loadConversations&&Promise.resolve(e.storage.loadConversations()).then(r=>{t.value=r}).catch(r=>{console.error("[useConversation] loadConversations failed:",r)});let d=async(r,l)=>{let g=n.get(r);if(g)return g;let f=l?.initialMessages??e.useMessageOptions.initialMessages??[];if(e.storage?.loadMessages)try{f=await e.storage.loadMessages(r)}catch(E){console.error("[useConversation] loadMessages failed:",E)}let x=L({...e.useMessageOptions,...l,initialMessages:f});return n.set(r,x),u(r,x),x};function M(){return Date.now().toString(36)+Math.random().toString(36).substring(2,9)}let A=r=>{let{id:l=M(),title:g,metadata:f,useMessageOptions:x}=r||{},E=Date.now(),S={id:l,title:g,createdAt:E,updatedAt:E,metadata:f};t.value.unshift(S);let D=L({...e.useMessageOptions,...x});return n.set(l,D),u(l,D),e.storage?.saveConversation?.(S),e.storage?.saveMessages?.(l,D.messages.value),o.value=l,a.value},w=async r=>r?o.value===r?a.value:t.value.find(g=>g.id===r)?(await d(r),n.forEach((g,f)=>{if(f===r)return;g.isProcessing?.value||(y(f),n.delete(f))}),o.value=r,a.value):null:null;return{conversations:t,activeConversationId:o,activeConversation:a,createConversation:A,switchConversation:w,deleteConversation:async r=>{let l=t.value.findIndex(f=>f.id===r);if(l===-1)return;if(await n.get(r)?.abortRequest(),y(r),n.delete(r),t.value.splice(l,1),e.storage?.deleteConversation?.(r),o.value===r){let f=t.value[0];f?await w(f.id):o.value=null}},updateConversationTitle:(r,l)=>{let g=t.value.find(f=>f.id===r);g&&(g.title=l,g.updatedAt=Date.now(),e.storage?.saveConversation?.(g))},saveMessages:i,sendMessage:r=>{a.value?.engine.sendMessage(r)},abortActiveRequest:async()=>{await a.value?.engine.abortRequest()}}};0&&(module.exports={EXCLUDE_MODE_REMOVE,createSSEStreamGenerator,fallbackRolePlugin,lengthPlugin,thinkingPlugin,toolPlugin,useConversation,useMessage});
1
+ "use strict";var ie=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var De=Object.prototype.hasOwnProperty;var Ue=(o,t)=>{for(var e in t)ie(o,e,{get:t[e],enumerable:!0})},qe=(o,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of _e(t))!De.call(o,s)&&s!==e&&ie(o,s,{get:()=>t[s],enumerable:!(n=ke(t,s))||n.enumerable});return o};var Be=o=>qe(ie({},"__esModule",{value:!0}),o);var Ve={};Ue(Ve,{AIClient:()=>Z,BaseModelProvider:()=>H,EXCLUDE_MODE_REMOVE:()=>Pe,ErrorType:()=>fe,IndexedDBStrategy:()=>V,LocalStorageStrategy:()=>G,OpenAIProvider:()=>j,StreamEventType:()=>me,extractTextFromResponse:()=>ye,fallbackRolePlugin:()=>ue,formatMessages:()=>he,handleSSEStream:()=>Y,indexedDBStorageStrategyFactory:()=>be,lengthPlugin:()=>de,localStorageStrategyFactory:()=>te,sseStreamToGenerator:()=>Ce,thinkingPlugin:()=>ge,toolPlugin:()=>je,useConversation:()=>Ge,useMessage:()=>ne});module.exports=Be(Ve);var H=class{constructor(t){this.config=t}updateConfig(t){this.config={...this.config,...t}}getConfig(){return{...this.config}}validateRequest(t){if(!t.messages||!Array.isArray(t.messages)||t.messages.length===0)throw new Error("\u8BF7\u6C42\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u6761\u6D88\u606F");for(let e of t.messages)if(!e.role||!e.content)throw new Error("\u6BCF\u6761\u6D88\u606F\u5FC5\u987B\u5305\u542B\u89D2\u8272\u548C\u5185\u5BB9")}};var fe=(c=>(c.NETWORK_ERROR="network_error",c.AUTHENTICATION_ERROR="authentication_error",c.RATE_LIMIT_ERROR="rate_limit_error",c.SERVER_ERROR="server_error",c.MODEL_ERROR="model_error",c.TIMEOUT_ERROR="timeout_error",c.UNKNOWN_ERROR="unknown_error",c))(fe||{}),me=(n=>(n.DATA="data",n.ERROR="error",n.DONE="done",n))(me||{});function K(o){return{type:o.type||"unknown_error",message:o.message||"\u672A\u77E5\u9519\u8BEF",statusCode:o.statusCode,originalError:o.originalError}}function le(o){if(!o.response)return K({type:"network_error",message:"\u7F51\u7EDC\u8FDE\u63A5\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684\u7F51\u7EDC\u8FDE\u63A5",originalError:o});if(o.response){let{status:t,data:e}=o.response;return t===401||t===403?K({type:"authentication_error",message:"\u8EAB\u4EFD\u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684API\u5BC6\u94A5",statusCode:t,originalError:o}):t===429?K({type:"rate_limit_error",message:"\u8D85\u51FAAPI\u8C03\u7528\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:t,originalError:o}):t>=500?K({type:"server_error",message:"\u670D\u52A1\u5668\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:t,originalError:o}):K({type:"unknown_error",message:e?.error?.message||`\u8BF7\u6C42\u5931\u8D25\uFF0C\u72B6\u6001\u7801: ${t}`,statusCode:t,originalError:o})}return o.code==="ECONNABORTED"?K({type:"timeout_error",message:"\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",originalError:o}):K({type:"unknown_error",message:o.message||"\u53D1\u751F\u672A\u77E5\u9519\u8BEF",originalError:o})}async function Y(o,t,e){let n=o.body?.getReader();if(!n)throw new Error("Response body is null");let s=new TextDecoder,r="",a,c;e&&e.addEventListener("abort",()=>{n.cancel().catch(d=>console.error("Error cancelling reader:",d))},{once:!0});try{for(;;){if(e?.aborted){await n.cancel();break}let{done:d,value:f}=await n.read();if(d)break;let v=s.decode(f,{stream:!0});r+=v;let R=r.split(`
2
+
3
+ `);r=R.pop()||"";for(let x of R)if(x.trim()!==""){if(x.trim()==="data: [DONE]"){c&&(a=c),t.onDone(a);continue}try{let A=x.match(/^data: (.+)$/m);if(!A)continue;let M=JSON.parse(A[1]);t.onData(M),c=M.choices?.[0]?.finish_reason||void 0}catch(A){console.error("Error parsing SSE message:",A)}}}(r.trim()==="data: [DONE]"||e?.aborted)&&(e?.aborted&&(a="aborted"),t.onDone(a))}catch(d){if(e?.aborted)return;throw d}}function he(o){return o.map(t=>typeof t=="object"&&"role"in t&&"content"in t?{role:t.role,content:String(t.content),...t.name?{name:t.name}:{}}:typeof t=="string"?{role:"user",content:t}:{role:"user",content:String(t)})}function ye(o){return!o.choices||!o.choices.length?"":o.choices[0].message?.content||""}function ce(o="The operation was aborted"){let t=new Error(o);return t.name="AbortError",t}async function*Ce(o,t={}){let e=o.body?.getReader();if(!e)throw new Error("ReadableStream not supported");let{signal:n}=t,s=new TextDecoder,r="",a=()=>{e.cancel()};n?.addEventListener("abort",a);try{for(;;){if(n?.aborted)throw ce();let c;try{c=await e.read()}catch(x){throw n?.aborted?ce():x}let{done:d,value:f}=c;if(d){if(n?.aborted)throw ce();return}let v=s.decode(f,{stream:!0});r+=v;let R=r.split(`
4
+ `);r=R.pop()||"";for(let x of R)if(x.trim()!==""&&x.startsWith("data: ")){let A=x.slice(6);if(A==="[DONE]")return;try{yield JSON.parse(A)}catch(M){console.warn("Failed to parse SSE data:",A,M)}}}}finally{n?.removeEventListener("abort",a),e.releaseLock()}}var j=class extends H{constructor(e){super(e);this.defaultModel="gpt-3.5-turbo";this.baseURL=e.apiUrl||"https://api.openai.com/v1",this.apiKey=e.apiKey||"",e.defaultModel&&(this.defaultModel=e.defaultModel),this.apiKey||console.warn("API key is not provided. Authentication will likely fail.")}async chat(e){try{this.validateRequest(e);let n={model:e.options?.model||this.config.defaultModel||this.defaultModel,messages:e.messages,...e.options,stream:!1},s={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)};this.apiKey&&Object.assign(s.headers,{Authorization:`Bearer ${this.apiKey}`});let r=await fetch(`${this.baseURL}/chat/completions`,s);if(!r.ok){let a=await r.text();throw new Error(`HTTP error! status: ${r.status}, details: ${a}`)}return await r.json()}catch(n){throw le(n)}}async chatStream(e,n){let{signal:s,...r}=e.options||{};try{this.validateRequest(e);let a={model:e.options?.model||this.config.defaultModel||this.defaultModel,messages:e.messages,...r,stream:!0},c={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,Accept:"text/event-stream"},body:JSON.stringify(a),signal:s};this.apiKey&&Object.assign(c.headers,{Authorization:`Bearer ${this.apiKey}`});let d=await fetch(`${this.baseURL}/chat/completions`,c);if(!d.ok){let f=await d.text();throw new Error(`HTTP error! status: ${d.status}, details: ${f}`)}await Y(d,n,s)}catch(a){if(s?.aborted)return;n.onError(le(a))}}updateConfig(e){super.updateConfig(e),e.apiUrl&&(this.baseURL=e.apiUrl),e.apiKey&&(this.apiKey=e.apiKey),e.defaultModel&&(this.defaultModel=e.defaultModel)}};var Z=class{constructor(t){this.config=t,this.provider=this.createProvider(t)}createProvider(t){if(t.provider==="custom"&&"providerImplementation"in t)return t.providerImplementation;if(t.provider==="deepseek"){let e={defaultModel:"deepseek-chat",apiUrl:"https://api.deepseek.com/v1"};return new j({...e,...t})}else return new j(t)}async chat(t){return this.provider.chat(t)}async chatStream(t,e){let n={...t,options:{...t.options,stream:!0}};return this.provider.chatStream(n,e)}getConfig(){return{...this.config}}updateConfig(t){this.config={...this.config,...t},t.provider&&t.provider!==this.config.provider?this.provider=this.createProvider(this.config):this.provider.updateConfig(this.config)}};var ve=require("vue");function X(o,t=new WeakMap){if(o==null||typeof o!="object")return o;try{let e=(0,ve.toRaw)(o);if(t.has(e))return t.get(e);if(Array.isArray(e)){let s=[];return t.set(e,s),s.push(...e.map(r=>X(r,t))),s}if(e instanceof Date||e instanceof RegExp||e instanceof ArrayBuffer||e instanceof Blob)return e;let n={};t.set(e,n);for(let s of Object.keys(e)){let r=Object.getOwnPropertyDescriptor(e,s);if(!r||r.get||r.set)continue;let a=e[s];typeof a!="function"&&typeof a!="symbol"&&(n[s]=X(a,t))}return n}catch(e){return console.warn("unwrapProxy error:",e),Array.isArray(o)?[]:{}}}var ee=o=>o.map(t=>{let{renderContent:e,...n}=t;if(!Array.isArray(e))return t;let s=e.filter(a=>a.type==="collapsible-text"),r=e.filter(a=>a.type==="markdown"||a.type==="text");return s.length>0&&(n.reasoning_content=s.map(a=>a.content).join("")),r.length>0&&(n.content=r.map(a=>a.content).join("")),n});var Q=o=>{let t=localStorage.getItem(o);return t?JSON.parse(t):[]},G=class{constructor(t="tiny-robot-ai-conversations"){this.storageKey=t}saveConversation(t){try{let e=Q(this.storageKey),n=e.findIndex(s=>s.id===t.id);n!==-1?Object.assign(e[n],t):e.unshift({...t,messages:[]}),localStorage.setItem(this.storageKey,JSON.stringify(e))}catch(e){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",e)}}loadConversations(){try{return Q(this.storageKey).map(e=>({id:e.id,title:e.title,createdAt:e.createdAt,updatedAt:e.updatedAt,metadata:e.metadata}))}catch(t){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",t),[]}}saveMessages(t,e){try{let n=Q(this.storageKey),s=n.findIndex(r=>r.id===t);s!==-1&&(n[s].messages=e),localStorage.setItem(this.storageKey,JSON.stringify(n))}catch(n){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",n)}}loadMessages(t){try{let n=Q(this.storageKey).find(r=>r.id===t);return ee(n?.messages||[])}catch(e){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",e),[]}}deleteConversation(t){let e=Q(this.storageKey),n=e.findIndex(s=>s.id===t);n!==-1&&e.splice(n,1),localStorage.setItem(this.storageKey,JSON.stringify(e))}};var Me=require("idb");var V=class{constructor(t="tiny-robot-ai-db",e=3){this.db=null;this.dbName=t,this.dbVersion=e}async getDB(){return this.db||(this.db=await(0,Me.openDB)(this.dbName,this.dbVersion,{upgrade(t){t.objectStoreNames.contains("conversations")||t.createObjectStore("conversations",{keyPath:"id"}).createIndex("by-updated","updatedAt"),t.objectStoreNames.contains("messages")||t.createObjectStore("messages",{keyPath:"conversationId"})}})),this.db}async loadConversations(){try{return(await(await this.getDB()).getAllFromIndex("conversations","by-updated")).reverse()}catch(t){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",t),[]}}async loadMessages(t){try{let n=await(await this.getDB()).get("messages",t);return n?ee(n.messages):[]}catch(e){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",e),[]}}async saveConversation(t){try{let e=await this.getDB(),n=X(t);await e.put("conversations",n)}catch(e){throw console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",e),e}}async saveMessages(t,e){try{let n=await this.getDB(),s=X(e);await n.put("messages",{conversationId:t,messages:s})}catch(n){throw console.error("\u4FDD\u5B58\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",n),n}}async deleteConversation(t){try{let e=await this.getDB();await e.delete("conversations",t),await e.delete("messages",t)}catch(e){throw console.error("\u5220\u9664\u4F1A\u8BDD\u5931\u8D25:",e),e}}};function te(o={}){return new G(o.key||"tiny-robot-ai-conversations")}function be(o={}){return new V(o.dbName||"tiny-robot-ai-db",o.dbVersion||1)}var L=require("vue");var U=require("vue");var ue=(o={})=>{let{fallbackRole:t="assistant",...e}=o;return{name:"fallbackRole",...e,onBeforeRequest(n){let{requestBody:s}=n;return s.messages=s.messages.map(r=>({...r,role:r.role||t})),e.onBeforeRequest?.(n)}}};var de=(o={})=>{let{continueContent:t="Please continue with your previous answer.",...e}=o;return{name:"length",...e,onAfterRequest:async n=>{let{lastChoice:s,appendMessage:r,requestNext:a}=n;return s?.finish_reason==="length"&&(r({role:"user",content:t}),a()),e.onAfterRequest?.(n)}}};var ge=(o={})=>({name:"thinking",...o,onCompletionChunk(t){let{choice:e,currentMessage:n}=t,r=typeof(e?.message?.reasoning_content||e?.delta?.reasoning_content)=="string";return n.state?n.state.thinking=r:n.state={thinking:r},o.onCompletionChunk?.(t)},onTurnEnd(t){let e=t.currentTurn.slice(-1)[0];return e?.state&&(e.state.thinking=void 0),o.onTurnEnd?.(t)}});var Se=require("vue");var $=class extends Error{constructor(t){super(t),this.name="AbortError"}};function Ne(o){if(o.aborted)return{promise:Promise.reject(new $(String(o.reason??"Aborted"))),cleanup:()=>{}};let t=null;return{promise:new Promise((s,r)=>{t=()=>{r(new $(String(o.reason??"Aborted")))},o.addEventListener("abort",t,{once:!0})}),cleanup:()=>{t&&(o.removeEventListener("abort",t),t=null)}}}function Ae(o,t){let{promise:e,cleanup:n}=Ne(t);return Promise.race([o,e]).finally(n)}function we(o,t){let e={};for(let n in o)t.includes(n)&&(e[n]=o[n]);return e}function Te(o,t){let e={};for(let n in o)t.includes(n)||(e[n]=o[n]);return e}async function*oe(o){if(Re(o)){yield*o;return}let t=await o;if(Re(t)){yield*t;return}yield t}function Re(o){return o&&typeof o=="object"&&typeof o[Symbol.asyncIterator]=="function"}var pe=o=>typeof o=="object"&&o!==null,xe=o=>pe(o)&&typeof o.index=="number",J=(o,t)=>{for(let[e,n]of Object.entries(t)){let s=o[e];if(s)if(typeof s=="string"&&typeof n=="string")e==="type"&&s||(o[e]=s+n);else if(Array.isArray(s)&&Array.isArray(n))if(s.every(r=>xe(r))&&n.every(r=>xe(r))){let r=new Map(s.map(f=>[f.index,f])),a=new Map(n.map(f=>[f.index,f]));for(let[f,v]of a)if(r.has(f)){let R=r.get(f);r.set(f,J(R,v))}else r.set(f,v);let c=Math.max(...Array.from(r.keys()),-1)+1,d=c>s.length?Array.from({length:c}):s;for(let[f,v]of r)d[f]=v;o[e]=d}else o[e]=[...s,...n];else pe(s)&&pe(n)&&(o[e]=J(s,n));else o[e]=n}return o};function Fe(o,t){let e=[];for(let n=0;n<o.length;n++){let s=o[n];if(s.role==="assistant"&&s.tool_calls&&s.tool_calls.length>0){let r=new Set(s.tool_calls.map(d=>d.id)),a=new Set;for(let d=n+1;d<o.length;d++){let f=o[d];f.role==="tool"&&f.tool_call_id&&r.has(f.tool_call_id)&&a.add(f.tool_call_id)}let c=s.tool_calls.map(d=>d.id).filter(d=>!a.has(d));c.length>0&&e.push({insertAfterIndex:n,missingToolCallIds:c})}}for(let n=e.length-1;n>=0;n--){let{insertAfterIndex:s,missingToolCallIds:r}=e[n],a=r.map(c=>({role:"tool",tool_call_id:c,content:t}));o.splice(s+1,0,...a)}}var Pe="remove";function Ke(o,t,e,n){let s=o.filter(a=>a[e]),r=new Set(s.flatMap(a=>a.tool_calls?.map(c=>c.id)??[]));if(t===Pe)for(let a=o.length-1;a>=0;a--){let c=o[a];(c[e]||c[n]||c.tool_call_id&&r.has(c.tool_call_id))&&o.splice(a,1)}else if(t===!0)for(let a of o)(a[e]||a.tool_call_id&&r.has(a.tool_call_id))&&(a[n]=!0,delete a[e])}var je=o=>{let{getTools:t,beforeCallTools:e,callTool:n,onToolCallStart:s,onToolCallEnd:r,toolCallCancelledContent:a="Tool call cancelled.",toolCallFailedContent:c="Tool call failed.",autoFillMissingToolMessages:d=!1,excludeToolMessagesNextTurn:f=!1,...v}=o,R=Symbol("doNotSendNextTurn"),x=Symbol("doNotSend"),A=(...m)=>{let[h,{primaryMessage:T}]=m;T.state.toolCall[h.id].status="running",s?.(...m)},M=(...m)=>{let[h,{status:T,primaryMessage:O}]=m;O.state.toolCall[h.id].status=T,r?.(...m)};return{name:"tool",...v,onTurnStart:m=>{let{messages:h}=m;return d&&Fe(h,a),f&&Ke(h,f,R,x),v.onTurnStart?.(m)},onBeforeRequest:async m=>{let{messages:h,requestBody:T}=m;f===!0&&(T.messages=h.filter(E=>!E[x]));let O=await t?.();return O&&O.length>0&&(T.tools=O),v.onBeforeRequest?.(m)},onAfterRequest:async m=>{let{currentMessage:h,lastChoice:T,appendMessage:O,abortSignal:E,setRequestState:i,requestNext:l}=m;if(T?.finish_reason!=="tool_calls"||!h.tool_calls?.length)return;f&&(h[R]=!0),i("processing","calling-tools"),await e?.(h.tool_calls,{...m,currentMessage:h});let g=h.tool_calls.map(async y=>{let S=Math.floor(Date.now()/1e3),w=(0,Se.reactive)({role:"tool",tool_call_id:y.id,content:"",metadata:{createdAt:S,updatedAt:S}});O(w);let q={...m,primaryMessage:h,toolMessage:w};A(y,q);try{let k=n(y,q),N=oe(k);for await(let u of N){if(typeof u=="string")w.content+=u;else{let p={};try{p=JSON.parse(w.content||"{}")}catch(b){console.warn(b)}w.content=JSON.stringify(J(p,u))}w.metadata.updatedAt=Math.floor(Date.now()/1e3)}M(y,{...q,status:"success"})}catch(k){let N=k instanceof Error?k:new Error(String(k));if(E.aborted){M(y,{...q,status:"cancelled",error:N});return}console.error(k),w.content.length===0&&(w.content=c),M(y,{...q,status:"failed",error:N})}});return await Promise.all(g),l(),v.onAfterRequest?.(m)},onCompletionChunk:m=>{var T,O,E;let{currentMessage:h}=m;if(Array.isArray(h.tool_calls))for(let i of h.tool_calls)h.state?.toolCall?.[i.id]?.status||(h.state??(h.state={}),(T=h.state).toolCall??(T.toolCall={}),(O=h.state.toolCall)[E=i.id]??(O[E]={}));return v.onCompletionChunk?.(m)}}};var Le=o=>{let t=[];for(let e of o){if(e.name){let n=t.findIndex(s=>s.name===e.name);n!==-1&&t.splice(n,1)}t.push(e)}return t},ne=o=>{let{initialMessages:t=[],requestMessageFields:e=[],requestMessageFieldsExclude:n=["state","metadata","loading"],plugins:s=[],onCompletionChunk:r}=o,a=(0,U.ref)("idle"),c=(0,U.ref)(void 0),d=(0,U.ref)(t),f=(0,U.ref)(o.responseProvider),v=null,R=[],x={},A=[ue(),ge(),de()],M=Le(A.concat(s)),m=(0,U.computed)(()=>a.value==="processing"),h=async u=>{if(!u||!u.trim()){console.warn("Cannot send empty message");return}if(m.value){console.warn("Cannot send message while processing is in progress");return}let p=Math.floor(Date.now()/1e3);d.value.push({role:"user",content:u.trim(),metadata:{createdAt:p,updatedAt:p}}),R.push(d.value[d.value.length-1]),await w()},T=async(...u)=>{if(m.value){console.warn("Cannot send message while processing is in progress");return}d.value.push(...u),R.push(...u),await w()},O=u=>{let p=u;return e.length&&(p=p.map(b=>we(b,e))),n.length&&(p=p.map(b=>Te(b,n))),p},E=(u,p)=>{a.value=u,u==="processing"?c.value=p||"requesting":c.value=void 0},i=u=>{Object.assign(x,u)},l=u=>({messages:d.value,currentTurn:R,requestState:a.value,processingState:c.value,plugins:M,abortSignal:u,setRequestState:E,customContext:x,setCustomContext:i}),g=(u,p)=>typeof u.disabled=="function"?u.disabled(p):!!u.disabled,y=u=>!u||Object.keys(u).length===0?!1:Object.values(u).some(p=>!!p),S=async(u,p)=>{E("processing","requesting");let b=new Proxy({messages:O(d.value)},{set(D,P,F){return P==="messages"?(D.messages=O(F),!0):(D[P]=F,!0)}}),I=l(p);for(let D of M.filter(P=>!g(P,I)))await D.onBeforeRequest?.({...I,requestBody:b});let C=(0,U.reactive)({role:"",content:"",loading:!0});N(C);let _,re=u(b,p),B=oe(re);for await(let D of B){E("processing","completing"),C.loading&&(C.loading=void 0);let P=D.choices?.find(W=>W.index===0);if(P){_=P;let W=()=>{C.metadata||(C.metadata={});let{created:z,...Ie}=D;C.metadata.createdAt=z,C.metadata.updatedAt=Math.floor(Date.now()/1e3),Object.assign(C.metadata,Ie);let ae=y(P.delta)?P.delta:P.message;ae||(ae=P.delta||P.message||{}),J(C,ae)};if(r){let z=l(p);r({...z,chunk:D,choice:P,currentMessage:C},W)}else W()}let F=l(p);for(let W of M.filter(z=>!g(z,F)))W.onCompletionChunk?.({...F,abortSignal:p,chunk:D,choice:P,currentMessage:C})}await k(C,u,p,_)},w=async()=>{let u=new AbortController;v=u,x={};try{E("processing","requesting");let p=l(u.signal);for(let C of M.filter(_=>!g(_,p)))await C.onTurnStart?.(p);let b=f.value;try{await S(b,u.signal),E("completed")}catch(C){if(u.signal.aborted||C instanceof $||C instanceof Error&&C.name==="AbortError")E("aborted");else throw C}let I=l(u.signal);for(let C of M.filter(_=>!g(_,I)))await C.onTurnEnd?.(I)}catch(p){E("error");let b=!1,I=l(u.signal);for(let C of M.filter(_=>!g(_,I)))C.onError&&(b=!0,C.onError({...I,error:p}));if(!b)throw p}finally{let p=l(u.signal);for(let b of M.filter(I=>!g(I,p)))try{b.onFinally?.(p)}catch(I){console.error(`Error in onFinally hook for plugin ${b.name||"Anonymous"}:`,I)}v=null,R.slice(-1)[0]&&(R.slice(-1)[0].loading=void 0),R=[]}},q=async()=>{v?.abort(),m.value&&await new Promise(u=>{let p=(0,U.watch)(m,b=>{b||(p(),u())},{immediate:!0})})},k=async(u,p,b,I)=>{let C=!1,_=l(b),re=M.filter(B=>!g(B,_)).map(B=>{if(!B.onAfterRequest)return null;let D=F=>{N(F)},P=()=>{C=!0};return B.onAfterRequest({..._,currentMessage:u,lastChoice:I,appendMessage:D,requestNext:P})}).filter(B=>B!==null);await Ae(Promise.all(re),b),C&&await S(p,b)},N=u=>{let p=Array.isArray(u)?u:[u];d.value.push(...p),R.push(...p)};return{requestState:a,processingState:c,messages:d,responseProvider:f,isProcessing:m,sendMessage:h,send:T,abortRequest:q}};var se=require("vue");function Oe(o,t=200,e=!1,n=!0,s=!1){return We(He(t,e,n,s),o)}function We(o,t){function e(...n){return new Promise((s,r)=>{Promise.resolve(o(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(s).catch(r)})}return e}var Ee=()=>{};function He(...o){let t=0,e,n=!0,s=Ee,r,a,c,d,f;!(0,se.isRef)(o[0])&&typeof o[0]=="object"?{delay:a,trailing:c=!0,leading:d=!0,rejectOnCancel:f=!1}=o[0]:[a,c=!0,d=!0,f=!1]=o;let v=()=>{e&&(clearTimeout(e),e=void 0,s(),s=Ee)};return x=>{let A=(0,se.toValue)(a),M=Date.now()-t,m=()=>r=x();return v(),A<=0?(t=Date.now(),m()):(M>A?(t=Date.now(),(d||!n)&&m()):c&&(r=new Promise((h,T)=>{s=f?T:h,e=setTimeout(()=>{t=Date.now(),n=!0,h(m()),v()},Math.max(0,A-M))})),!d&&!e&&(e=setTimeout(()=>n=!0,A)),n=!1,r)}}var Ge=o=>{let t=o.storage||te(),e=(0,L.ref)([]),n=new Map,s=new Map,r=(0,L.ref)(null),a=(0,L.computed)(()=>{let i=r.value;if(!i)return null;let l=e.value.find(y=>y.id===i);if(!l)return null;let g=n.get(i);return g?{...l,engine:g}:null}),c=i=>{if(!t?.saveMessages)return;let l=i||r.value,g=e.value.find(S=>S.id===l);if(!g)return;g.updatedAt=Date.now(),t?.saveConversation?.(g);let y=n.get(g.id);y&&t.saveMessages(g.id,y.messages.value)},d=(i,l)=>{if(!o.autoSaveMessages||!t?.saveMessages)return;let g=s.get(i);g&&g();let y=o.autoSaveThrottle??1e3,S=Oe(()=>{c(i)},y,!0,!0),w=(0,L.watch)(l.messages,S,{deep:!0});s.set(i,w)},f=i=>{let l=s.get(i);l&&(l(),s.delete(i))};t?.loadConversations&&Promise.resolve(t.loadConversations()).then(i=>{if(!i?.length)return[];if(e.value.length===0)return e.value=i,e.value;let l=new Map(e.value.map(g=>[g.id,g]));return i.forEach(g=>{l.has(g.id)||l.set(g.id,g)}),e.value=Array.from(l.values()),e.value}).then(i=>{o.onLoad?.(i)}).catch(i=>{console.error("[useConversation] loadConversations failed:",i)});let v=async(i,l)=>{let g=n.get(i);if(g)return g;let y=l?.initialMessages??o.useMessageOptions.initialMessages??[];if(t?.loadMessages)try{y=await t.loadMessages(i)}catch(w){console.error("[useConversation] loadMessages failed:",w)}let S=ne({...o.useMessageOptions,...l,initialMessages:y});return n.set(i,S),d(i,S),S};function R(){return Date.now().toString(36)+Math.random().toString(36).substring(2,9)}let x=i=>{let{id:l=R(),title:g,metadata:y,useMessageOptions:S}=i||{},w=Date.now(),q={id:l,title:g,createdAt:w,updatedAt:w,metadata:y};e.value.unshift(q);let k=ne({...o.useMessageOptions,...S});return n.set(l,k),d(l,k),t?.saveConversation?.(q),t?.saveMessages?.(l,k.messages.value),r.value=l,a.value},A=i=>{let{excludeId:l}=i||{};n.forEach((g,y)=>{if(l&&y===l)return;g.isProcessing?.value||(f(y),n.delete(y))})};return{conversations:e,activeConversationId:r,activeConversation:a,createConversation:x,switchConversation:async i=>i?r.value===i?a.value:e.value.find(g=>g.id===i)?(await v(i),A({excludeId:i}),r.value=i,a.value):null:null,deleteConversation:async i=>{let l=e.value.findIndex(y=>y.id===i);if(l===-1)return;await n.get(i)?.abortRequest(),f(i),n.delete(i),e.value.splice(l,1),t?.deleteConversation?.(i),r.value===i&&(r.value=null,A())},clear:()=>{e.value.map(l=>l.id).forEach(l=>{t?.deleteConversation?.(l)}),n.forEach(l=>{l.abortRequest()}),s.forEach(l=>{l()}),e.value=[],n.clear(),s.clear(),r.value=null},updateConversationTitle:(i,l)=>{let g=e.value.find(y=>y.id===i);g&&(g.title=l,g.updatedAt=Date.now(),t?.saveConversation?.(g))},saveMessages:c,sendMessage:i=>{a.value?.engine.sendMessage(i)},abortActiveRequest:async()=>{await a.value?.engine.abortRequest()}}};0&&(module.exports={AIClient,BaseModelProvider,EXCLUDE_MODE_REMOVE,ErrorType,IndexedDBStrategy,LocalStorageStrategy,OpenAIProvider,StreamEventType,extractTextFromResponse,fallbackRolePlugin,formatMessages,handleSSEStream,indexedDBStorageStrategyFactory,lengthPlugin,localStorageStrategyFactory,sseStreamToGenerator,thinkingPlugin,toolPlugin,useConversation,useMessage});
package/dist/index.mjs CHANGED
@@ -1,2 +1,4 @@
1
- function L(e="The operation was aborted"){let n=new Error(e);return n.name="AbortError",n}async function*se(e,n={}){let t=e.body?.getReader();if(!t)throw new Error("ReadableStream not supported");let{signal:s}=n,r=new TextDecoder,a="",i=()=>{t.cancel()};s?.addEventListener("abort",i);try{for(;;){if(s?.aborted)throw L();let u;try{u=await t.read()}catch(w){throw s?.aborted?L():w}let{done:y,value:d}=u;if(y){if(s?.aborted)throw L();return}let M=r.decode(d,{stream:!0});a+=M;let A=a.split(`
2
- `);a=A.pop()||"";for(let w of A)if(w.trim()!==""&&w.startsWith("data: ")){let v=w.slice(6);if(v==="[DONE]")return;try{yield JSON.parse(v)}catch(T){console.warn("Failed to parse SSE data:",v,T)}}}}finally{s?.removeEventListener("abort",i),t.releaseLock()}}import{computed as ye,ref as te,watch as Ce}from"vue";import{computed as ce,reactive as ue,ref as j,watch as ge}from"vue";var K=(e={})=>{let{fallbackRole:n="assistant",...t}=e;return{name:"fallbackRole",...t,onBeforeRequest(s){let{requestBody:r,messages:a}=s;return r.messages=a.map(i=>({...i,role:i.role||n})),t.onBeforeRequest?.(s)}}};var z=(e={})=>{let{continueContent:n="Please continue with your previous answer.",...t}=e;return{name:"length",...t,onAfterRequest:async s=>{let{lastChoice:r,appendMessage:a,requestNext:i}=s;return r?.finish_reason==="length"&&(a({role:"user",content:n}),i()),t.onAfterRequest?.(s)}}};var J=(e={})=>({name:"thinking",...e,onCompletionChunk(n){let{choice:t,currentMessage:s}=n,a=typeof(t?.message?.reasoning_content||t?.delta?.reasoning_content)=="string";return s.state?s.state.thinking=a:s.state={thinking:a},e.onCompletionChunk?.(n)},onTurnEnd(n){let t=n.currentTurn.slice(-1)[0];return t?.state&&(t.state.thinking=void 0),e.onTurnEnd?.(n)}});import{reactive as re}from"vue";var I=class extends Error{constructor(n){super(n),this.name="AbortError"}};function oe(e){if(e.aborted)return{promise:Promise.reject(new I(String(e.reason??"Aborted"))),cleanup:()=>{}};let n=null;return{promise:new Promise((r,a)=>{n=()=>{a(new I(String(e.reason??"Aborted")))},e.addEventListener("abort",n,{once:!0})}),cleanup:()=>{n&&(e.removeEventListener("abort",n),n=null)}}}function Q(e,n){let{promise:t,cleanup:s}=oe(n);return Promise.race([e,t]).finally(s)}function Y(e,n){let t={};for(let s of n)s in e&&(t[s]=e[s]);return t}async function*G(e){if(X(e)){yield*e;return}let n=await e;if(X(n)){yield*n;return}yield n}function X(e){return e&&typeof e=="object"&&typeof e[Symbol.asyncIterator]=="function"}var H=e=>typeof e=="object"&&e!==null,$=e=>H(e)&&typeof e.index=="number",N=(e,n)=>{for(let[t,s]of Object.entries(n)){let r=e[t];if(r)if(typeof r=="string"&&typeof s=="string")t==="type"&&r||(e[t]=r+s);else if(Array.isArray(r)&&Array.isArray(s))if(r.every(a=>$(a))&&s.every(a=>$(a))){let a=new Map(r.map(d=>[d.index,d])),i=new Map(s.map(d=>[d.index,d]));for(let[d,M]of i)if(a.has(d)){let A=a.get(d);a.set(d,N(A,M))}else a.set(d,M);let u=Math.max(...Array.from(a.keys()),-1)+1,y=u>r.length?Array.from({length:u}):r;for(let[d,M]of a)y[d]=M;e[t]=y}else e[t]=[...r,...s];else H(r)&&H(s)&&(e[t]=N(r,s));else e[t]=s}return e};function ae(e,n){let t=[];for(let s=0;s<e.length;s++){let r=e[s];if(r.role==="assistant"&&r.tool_calls&&r.tool_calls.length>0){let a=new Set(r.tool_calls.map(y=>y.id)),i=new Set;for(let y=s+1;y<e.length;y++){let d=e[y];d.role==="tool"&&d.tool_call_id&&a.has(d.tool_call_id)&&i.add(d.tool_call_id)}let u=r.tool_calls.map(y=>y.id).filter(y=>!i.has(y));u.length>0&&t.push({insertAfterIndex:s,missingToolCallIds:u})}}for(let s=t.length-1;s>=0;s--){let{insertAfterIndex:r,missingToolCallIds:a}=t[s],i=a.map(u=>({role:"tool",tool_call_id:u,content:n}));e.splice(r+1,0,...i)}}var ie="remove";function le(e,n,t,s){let r=e.filter(i=>i[t]),a=new Set(r.flatMap(i=>i.tool_calls?.map(u=>u.id)??[]));if(n===ie)for(let i=e.length-1;i>=0;i--){let u=e[i];(u[t]||u[s]||u.tool_call_id&&a.has(u.tool_call_id))&&e.splice(i,1)}else if(n===!0)for(let i of e)(i[t]||i.tool_call_id&&a.has(i.tool_call_id))&&(i[s]=!0,delete i[t])}var Re=e=>{let{getTools:n,beforeCallTools:t,callTool:s,onToolCallStart:r,onToolCallEnd:a,toolCallCancelledContent:i="Tool call cancelled.",toolCallFailedContent:u="Tool call failed.",autoFillMissingToolMessages:y=!1,excludeToolMessagesNextTurn:d=!1,...M}=e,A=Symbol("doNotSendNextTurn"),w=Symbol("doNotSend"),v=(...C)=>{let[p,{primaryMessage:o}]=C;o.state.toolCall[p.id].status="running",r?.(...C)},T=(...C)=>{let[p,{status:o,primaryMessage:l}]=C;l.state.toolCall[p.id].status=o,a?.(...C)};return{name:"tool",...M,onTurnStart:C=>{let{messages:p}=C;return y&&ae(p,i),d&&le(p,d,A,w),M.onTurnStart?.(C)},onBeforeRequest:async C=>{let{messages:p,requestBody:o}=C;d===!0&&(o.messages=p.filter(g=>!g[w]));let l=await n?.();return l&&l.length>0&&(o.tools=l),M.onBeforeRequest?.(C)},onAfterRequest:async C=>{let{currentMessage:p,lastChoice:o,appendMessage:l,abortSignal:g,setRequestState:f,requestNext:x}=C;if(o?.finish_reason!=="tool_calls"||!p.tool_calls?.length)return;d&&(p[A]=!0),f("processing","calling-tools"),await t?.(p.tool_calls,{...C,currentMessage:p});let E=p.tool_calls.map(async S=>{let q=Math.floor(Date.now()/1e3),O=re({role:"tool",tool_call_id:S.id,content:"",metadata:{createdAt:q,updatedAt:q}});l(O);let c={...C,primaryMessage:p,toolMessage:O};v(S,c);try{let h=s(S,c),b=G(h);for await(let _ of b){if(typeof _=="string")O.content+=_;else{let m={};try{m=JSON.parse(O.content||"{}")}catch(R){console.warn(R)}O.content=JSON.stringify(N(m,_))}O.metadata.updatedAt=Math.floor(Date.now()/1e3)}T(S,{...c,status:"success"})}catch(h){let b=h instanceof Error?h:new Error(String(h));if(g.aborted){T(S,{...c,status:"cancelled",error:b});return}console.error(h),O.content.length===0&&(O.content=u),T(S,{...c,status:"failed",error:b})}});return await Promise.all(E),x(),M.onAfterRequest?.(C)},onCompletionChunk:C=>{var o,l,g;let{currentMessage:p}=C;if(Array.isArray(p.tool_calls))for(let f of p.tool_calls)p.state?.toolCall?.[f.id]?.status||(p.state??(p.state={}),(o=p.state).toolCall??(o.toolCall={}),(l=p.state.toolCall)[g=f.id]??(l[g]={}));return M.onCompletionChunk?.(C)}}};var fe=e=>{let n=[];for(let t of e){if(t.name){let s=n.findIndex(r=>r.name===t.name);s!==-1&&n.splice(s,1)}n.push(t)}return n},V=e=>{let{initialMessages:n=[],requestMessageFields:t=["role","content","tool_calls","tool_call_id"],plugins:s=[],onCompletionChunk:r}=e,a=j("idle"),i=j(void 0),u=j(n),y=j(e.responseProvider),d=null,M=[],A={},w=[K(),J(),z()],v=fe(w.concat(s)),T=ce(()=>a.value==="processing"),C=async c=>{if(!c||!c.trim()){console.warn("Cannot send empty message");return}if(T.value){console.warn("Cannot send message while processing is in progress");return}let h=Math.floor(Date.now()/1e3);u.value.push({role:"user",content:c.trim(),metadata:{createdAt:h,updatedAt:h}}),M.push(u.value[u.value.length-1]),await E()},p=async(...c)=>{if(T.value){console.warn("Cannot send message while processing is in progress");return}u.value.push(...c),M.push(...c),await E()},o=c=>c.map(h=>Y(h,t)),l=(c,h)=>{a.value=c,c==="processing"?i.value=h||"requesting":i.value=void 0},g=c=>{Object.assign(A,c)},f=()=>({messages:u.value,currentTurn:M,requestState:a.value,processingState:i.value,requestMessageFields:t,plugins:v,setRequestState:l,customContext:A,setCustomContext:g}),x=async(c,h)=>{l("processing","requesting");let b=new Proxy({messages:o(u.value)},{set(k,P,U){return P==="messages"?(k.messages=o(U),!0):(k[P]=U,!0)}}),_=f();for(let k of v.filter(P=>!P.disabled))await k.onBeforeRequest?.({..._,abortSignal:h,requestBody:b});let m=ue({role:"",content:"",loading:!0});O(m);let R,W=c(b,h),D=G(W);for await(let k of D){l("processing","completing"),m.loading&&(m.loading=void 0);let P=k.choices?.find(F=>F.index===0);if(P){R=P;let F=()=>{m.metadata||(m.metadata={});let{created:B,...ne}=k;m.metadata.createdAt=B,m.metadata.updatedAt=Math.floor(Date.now()/1e3),Object.assign(m.metadata,ne),N(m,P.message||P.delta)};if(r){let B=f();r({...B,abortSignal:h,chunk:k,currentMessage:m},F)}else F()}let U=f();for(let F of v.filter(B=>!B.disabled))F.onCompletionChunk?.({...U,abortSignal:h,chunk:k,choice:P,currentMessage:m})}await q(m,c,h,R)},E=async()=>{let c=new AbortController;d=c;let h=y.value;A={};try{l("processing","requesting");let b=f();for(let m of v.filter(R=>!R.disabled))await m.onTurnStart?.({...b,abortSignal:c.signal});try{await x(h,c.signal),l("completed")}catch(m){if(c.signal.aborted||m instanceof I||m instanceof Error&&m.name==="AbortError")l("aborted");else throw m}let _=f();for(let m of v.filter(R=>!R.disabled))await m.onTurnEnd?.({..._,abortSignal:c.signal})}catch(b){l("error");let _=f();for(let m of v.filter(R=>!R.disabled))m.onError?.({..._,abortSignal:c.signal,error:b});throw b}finally{d=null,M.slice(-1)[0]&&(M.slice(-1)[0].loading=void 0),M=[]}},S=async()=>{d?.abort(),T.value&&await new Promise(c=>{let h=ge(T,b=>{b||(h(),c())},{immediate:!0})})},q=async(c,h,b,_)=>{let m=!1,R=f(),W=v.filter(D=>!D.disabled).map(D=>{if(!D.onAfterRequest)return null;let k=U=>{O(U)},P=()=>{m=!0};return D.onAfterRequest({...R,abortSignal:b,currentMessage:c,lastChoice:_,appendMessage:k,requestNext:P})}).filter(D=>D!==null);await Q(Promise.all(W),b),m&&await x(h,b)},O=c=>{let h=Array.isArray(c)?c:[c];u.value.push(...h),M.push(...h)};return{requestState:a,processingState:i,messages:u,responseProvider:y,isProcessing:T,sendMessage:C,send:p,abortRequest:S}};import{isRef as de,toValue as pe}from"vue";function ee(e,n=200,t=!1,s=!0,r=!1){return me(he(n,t,s,r),e)}function me(e,n){function t(...s){return new Promise((r,a)=>{Promise.resolve(e(()=>n.apply(this,s),{fn:n,thisArg:this,args:s})).then(r).catch(a)})}return t}var Z=()=>{};function he(...e){let n=0,t,s=!0,r=Z,a,i,u,y,d;!de(e[0])&&typeof e[0]=="object"?{delay:i,trailing:u=!0,leading:y=!0,rejectOnCancel:d=!1}=e[0]:[i,u=!0,y=!0,d=!1]=e;let M=()=>{t&&(clearTimeout(t),t=void 0,r(),r=Z)};return w=>{let v=pe(i),T=Date.now()-n,C=()=>a=w();return M(),v<=0?(n=Date.now(),C()):(T>v?(n=Date.now(),(y||!s)&&C()):u&&(a=new Promise((p,o)=>{r=d?o:p,t=setTimeout(()=>{n=Date.now(),s=!0,p(C()),M()},Math.max(0,v-T))})),!y&&!t&&(t=setTimeout(()=>s=!0,v)),s=!1,a)}}var Be=e=>{let n=te([]),t=new Map,s=new Map,r=te(null),a=ye(()=>{let o=r.value;if(!o)return null;let l=n.value.find(f=>f.id===o);if(!l)return null;let g=t.get(o);return g?{...l,engine:g}:null}),i=o=>{if(!e.storage?.saveMessages)return;let l=o||r.value,g=n.value.find(x=>x.id===l);if(!g)return;g.updatedAt=Date.now(),e.storage?.saveConversation?.(g);let f=t.get(g.id);f&&e.storage.saveMessages(g.id,f.messages.value)},u=(o,l)=>{if(!e.autoSaveMessages||!e.storage?.saveMessages)return;let g=s.get(o);g&&g();let f=e.autoSaveThrottle??1e3,x=ee(()=>{i(o)},f,!0,!0),E=Ce(l.messages,x,{deep:!0});s.set(o,E)},y=o=>{let l=s.get(o);l&&(l(),s.delete(o))};e.storage?.loadConversations&&Promise.resolve(e.storage.loadConversations()).then(o=>{n.value=o}).catch(o=>{console.error("[useConversation] loadConversations failed:",o)});let d=async(o,l)=>{let g=t.get(o);if(g)return g;let f=l?.initialMessages??e.useMessageOptions.initialMessages??[];if(e.storage?.loadMessages)try{f=await e.storage.loadMessages(o)}catch(E){console.error("[useConversation] loadMessages failed:",E)}let x=V({...e.useMessageOptions,...l,initialMessages:f});return t.set(o,x),u(o,x),x};function M(){return Date.now().toString(36)+Math.random().toString(36).substring(2,9)}let A=o=>{let{id:l=M(),title:g,metadata:f,useMessageOptions:x}=o||{},E=Date.now(),S={id:l,title:g,createdAt:E,updatedAt:E,metadata:f};n.value.unshift(S);let q=V({...e.useMessageOptions,...x});return t.set(l,q),u(l,q),e.storage?.saveConversation?.(S),e.storage?.saveMessages?.(l,q.messages.value),r.value=l,a.value},w=async o=>o?r.value===o?a.value:n.value.find(g=>g.id===o)?(await d(o),t.forEach((g,f)=>{if(f===o)return;g.isProcessing?.value||(y(f),t.delete(f))}),r.value=o,a.value):null:null;return{conversations:n,activeConversationId:r,activeConversation:a,createConversation:A,switchConversation:w,deleteConversation:async o=>{let l=n.value.findIndex(f=>f.id===o);if(l===-1)return;if(await t.get(o)?.abortRequest(),y(o),t.delete(o),n.value.splice(l,1),e.storage?.deleteConversation?.(o),r.value===o){let f=n.value[0];f?await w(f.id):r.value=null}},updateConversationTitle:(o,l)=>{let g=n.value.find(f=>f.id===o);g&&(g.title=l,g.updatedAt=Date.now(),e.storage?.saveConversation?.(g))},saveMessages:i,sendMessage:o=>{a.value?.engine.sendMessage(o)},abortActiveRequest:async()=>{await a.value?.engine.abortRequest()}}};export{ie as EXCLUDE_MODE_REMOVE,se as createSSEStreamGenerator,K as fallbackRolePlugin,z as lengthPlugin,J as thinkingPlugin,Re as toolPlugin,Be as useConversation,V as useMessage};
1
+ var G=class{constructor(t){this.config=t}updateConfig(t){this.config={...this.config,...t}}getConfig(){return{...this.config}}validateRequest(t){if(!t.messages||!Array.isArray(t.messages)||t.messages.length===0)throw new Error("\u8BF7\u6C42\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u6761\u6D88\u606F");for(let e of t.messages)if(!e.role||!e.content)throw new Error("\u6BCF\u6761\u6D88\u606F\u5FC5\u987B\u5305\u542B\u89D2\u8272\u548C\u5185\u5BB9")}};var Me=(c=>(c.NETWORK_ERROR="network_error",c.AUTHENTICATION_ERROR="authentication_error",c.RATE_LIMIT_ERROR="rate_limit_error",c.SERVER_ERROR="server_error",c.MODEL_ERROR="model_error",c.TIMEOUT_ERROR="timeout_error",c.UNKNOWN_ERROR="unknown_error",c))(Me||{}),be=(n=>(n.DATA="data",n.ERROR="error",n.DONE="done",n))(be||{});function F(o){return{type:o.type||"unknown_error",message:o.message||"\u672A\u77E5\u9519\u8BEF",statusCode:o.statusCode,originalError:o.originalError}}function te(o){if(!o.response)return F({type:"network_error",message:"\u7F51\u7EDC\u8FDE\u63A5\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684\u7F51\u7EDC\u8FDE\u63A5",originalError:o});if(o.response){let{status:t,data:e}=o.response;return t===401||t===403?F({type:"authentication_error",message:"\u8EAB\u4EFD\u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684API\u5BC6\u94A5",statusCode:t,originalError:o}):t===429?F({type:"rate_limit_error",message:"\u8D85\u51FAAPI\u8C03\u7528\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:t,originalError:o}):t>=500?F({type:"server_error",message:"\u670D\u52A1\u5668\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:t,originalError:o}):F({type:"unknown_error",message:e?.error?.message||`\u8BF7\u6C42\u5931\u8D25\uFF0C\u72B6\u6001\u7801: ${t}`,statusCode:t,originalError:o})}return o.code==="ECONNABORTED"?F({type:"timeout_error",message:"\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",originalError:o}):F({type:"unknown_error",message:o.message||"\u53D1\u751F\u672A\u77E5\u9519\u8BEF",originalError:o})}async function ne(o,t,e){let n=o.body?.getReader();if(!n)throw new Error("Response body is null");let s=new TextDecoder,r="",a,c;e&&e.addEventListener("abort",()=>{n.cancel().catch(d=>console.error("Error cancelling reader:",d))},{once:!0});try{for(;;){if(e?.aborted){await n.cancel();break}let{done:d,value:f}=await n.read();if(d)break;let v=s.decode(f,{stream:!0});r+=v;let R=r.split(`
2
+
3
+ `);r=R.pop()||"";for(let x of R)if(x.trim()!==""){if(x.trim()==="data: [DONE]"){c&&(a=c),t.onDone(a);continue}try{let A=x.match(/^data: (.+)$/m);if(!A)continue;let M=JSON.parse(A[1]);t.onData(M),c=M.choices?.[0]?.finish_reason||void 0}catch(A){console.error("Error parsing SSE message:",A)}}}(r.trim()==="data: [DONE]"||e?.aborted)&&(e?.aborted&&(a="aborted"),t.onDone(a))}catch(d){if(e?.aborted)return;throw d}}function Re(o){return o.map(t=>typeof t=="object"&&"role"in t&&"content"in t?{role:t.role,content:String(t.content),...t.name?{name:t.name}:{}}:typeof t=="string"?{role:"user",content:t}:{role:"user",content:String(t)})}function xe(o){return!o.choices||!o.choices.length?"":o.choices[0].message?.content||""}function oe(o="The operation was aborted"){let t=new Error(o);return t.name="AbortError",t}async function*Ae(o,t={}){let e=o.body?.getReader();if(!e)throw new Error("ReadableStream not supported");let{signal:n}=t,s=new TextDecoder,r="",a=()=>{e.cancel()};n?.addEventListener("abort",a);try{for(;;){if(n?.aborted)throw oe();let c;try{c=await e.read()}catch(x){throw n?.aborted?oe():x}let{done:d,value:f}=c;if(d){if(n?.aborted)throw oe();return}let v=s.decode(f,{stream:!0});r+=v;let R=r.split(`
4
+ `);r=R.pop()||"";for(let x of R)if(x.trim()!==""&&x.startsWith("data: ")){let A=x.slice(6);if(A==="[DONE]")return;try{yield JSON.parse(A)}catch(M){console.warn("Failed to parse SSE data:",A,M)}}}}finally{n?.removeEventListener("abort",a),e.releaseLock()}}var j=class extends G{constructor(e){super(e);this.defaultModel="gpt-3.5-turbo";this.baseURL=e.apiUrl||"https://api.openai.com/v1",this.apiKey=e.apiKey||"",e.defaultModel&&(this.defaultModel=e.defaultModel),this.apiKey||console.warn("API key is not provided. Authentication will likely fail.")}async chat(e){try{this.validateRequest(e);let n={model:e.options?.model||this.config.defaultModel||this.defaultModel,messages:e.messages,...e.options,stream:!1},s={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)};this.apiKey&&Object.assign(s.headers,{Authorization:`Bearer ${this.apiKey}`});let r=await fetch(`${this.baseURL}/chat/completions`,s);if(!r.ok){let a=await r.text();throw new Error(`HTTP error! status: ${r.status}, details: ${a}`)}return await r.json()}catch(n){throw te(n)}}async chatStream(e,n){let{signal:s,...r}=e.options||{};try{this.validateRequest(e);let a={model:e.options?.model||this.config.defaultModel||this.defaultModel,messages:e.messages,...r,stream:!0},c={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,Accept:"text/event-stream"},body:JSON.stringify(a),signal:s};this.apiKey&&Object.assign(c.headers,{Authorization:`Bearer ${this.apiKey}`});let d=await fetch(`${this.baseURL}/chat/completions`,c);if(!d.ok){let f=await d.text();throw new Error(`HTTP error! status: ${d.status}, details: ${f}`)}await ne(d,n,s)}catch(a){if(s?.aborted)return;n.onError(te(a))}}updateConfig(e){super.updateConfig(e),e.apiUrl&&(this.baseURL=e.apiUrl),e.apiKey&&(this.apiKey=e.apiKey),e.defaultModel&&(this.defaultModel=e.defaultModel)}};var se=class{constructor(t){this.config=t,this.provider=this.createProvider(t)}createProvider(t){if(t.provider==="custom"&&"providerImplementation"in t)return t.providerImplementation;if(t.provider==="deepseek"){let e={defaultModel:"deepseek-chat",apiUrl:"https://api.deepseek.com/v1"};return new j({...e,...t})}else return new j(t)}async chat(t){return this.provider.chat(t)}async chatStream(t,e){let n={...t,options:{...t.options,stream:!0}};return this.provider.chatStream(n,e)}getConfig(){return{...this.config}}updateConfig(t){this.config={...this.config,...t},t.provider&&t.provider!==this.config.provider?this.provider=this.createProvider(this.config):this.provider.updateConfig(this.config)}};import{toRaw as we}from"vue";function V(o,t=new WeakMap){if(o==null||typeof o!="object")return o;try{let e=we(o);if(t.has(e))return t.get(e);if(Array.isArray(e)){let s=[];return t.set(e,s),s.push(...e.map(r=>V(r,t))),s}if(e instanceof Date||e instanceof RegExp||e instanceof ArrayBuffer||e instanceof Blob)return e;let n={};t.set(e,n);for(let s of Object.keys(e)){let r=Object.getOwnPropertyDescriptor(e,s);if(!r||r.get||r.set)continue;let a=e[s];typeof a!="function"&&typeof a!="symbol"&&(n[s]=V(a,t))}return n}catch(e){return console.warn("unwrapProxy error:",e),Array.isArray(o)?[]:{}}}var X=o=>o.map(t=>{let{renderContent:e,...n}=t;if(!Array.isArray(e))return t;let s=e.filter(a=>a.type==="collapsible-text"),r=e.filter(a=>a.type==="markdown"||a.type==="text");return s.length>0&&(n.reasoning_content=s.map(a=>a.content).join("")),r.length>0&&(n.content=r.map(a=>a.content).join("")),n});var $=o=>{let t=localStorage.getItem(o);return t?JSON.parse(t):[]},J=class{constructor(t="tiny-robot-ai-conversations"){this.storageKey=t}saveConversation(t){try{let e=$(this.storageKey),n=e.findIndex(s=>s.id===t.id);n!==-1?Object.assign(e[n],t):e.unshift({...t,messages:[]}),localStorage.setItem(this.storageKey,JSON.stringify(e))}catch(e){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",e)}}loadConversations(){try{return $(this.storageKey).map(e=>({id:e.id,title:e.title,createdAt:e.createdAt,updatedAt:e.updatedAt,metadata:e.metadata}))}catch(t){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",t),[]}}saveMessages(t,e){try{let n=$(this.storageKey),s=n.findIndex(r=>r.id===t);s!==-1&&(n[s].messages=e),localStorage.setItem(this.storageKey,JSON.stringify(n))}catch(n){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",n)}}loadMessages(t){try{let n=$(this.storageKey).find(r=>r.id===t);return X(n?.messages||[])}catch(e){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",e),[]}}deleteConversation(t){let e=$(this.storageKey),n=e.findIndex(s=>s.id===t);n!==-1&&e.splice(n,1),localStorage.setItem(this.storageKey,JSON.stringify(e))}};import{openDB as Te}from"idb";var z=class{constructor(t="tiny-robot-ai-db",e=3){this.db=null;this.dbName=t,this.dbVersion=e}async getDB(){return this.db||(this.db=await Te(this.dbName,this.dbVersion,{upgrade(t){t.objectStoreNames.contains("conversations")||t.createObjectStore("conversations",{keyPath:"id"}).createIndex("by-updated","updatedAt"),t.objectStoreNames.contains("messages")||t.createObjectStore("messages",{keyPath:"conversationId"})}})),this.db}async loadConversations(){try{return(await(await this.getDB()).getAllFromIndex("conversations","by-updated")).reverse()}catch(t){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",t),[]}}async loadMessages(t){try{let n=await(await this.getDB()).get("messages",t);return n?X(n.messages):[]}catch(e){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",e),[]}}async saveConversation(t){try{let e=await this.getDB(),n=V(t);await e.put("conversations",n)}catch(e){throw console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",e),e}}async saveMessages(t,e){try{let n=await this.getDB(),s=V(e);await n.put("messages",{conversationId:t,messages:s})}catch(n){throw console.error("\u4FDD\u5B58\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25:",n),n}}async deleteConversation(t){try{let e=await this.getDB();await e.delete("conversations",t),await e.delete("messages",t)}catch(e){throw console.error("\u5220\u9664\u4F1A\u8BDD\u5931\u8D25:",e),e}}};function re(o={}){return new J(o.key||"tiny-robot-ai-conversations")}function Se(o={}){return new z(o.dbName||"tiny-robot-ai-db",o.dbVersion||1)}import{computed as je,ref as Ce,watch as Le}from"vue";import{computed as _e,reactive as De,ref as Y,watch as Ue}from"vue";var le=(o={})=>{let{fallbackRole:t="assistant",...e}=o;return{name:"fallbackRole",...e,onBeforeRequest(n){let{requestBody:s}=n;return s.messages=s.messages.map(r=>({...r,role:r.role||t})),e.onBeforeRequest?.(n)}}};var ce=(o={})=>{let{continueContent:t="Please continue with your previous answer.",...e}=o;return{name:"length",...e,onAfterRequest:async n=>{let{lastChoice:s,appendMessage:r,requestNext:a}=n;return s?.finish_reason==="length"&&(r({role:"user",content:t}),a()),e.onAfterRequest?.(n)}}};var ue=(o={})=>({name:"thinking",...o,onCompletionChunk(t){let{choice:e,currentMessage:n}=t,r=typeof(e?.message?.reasoning_content||e?.delta?.reasoning_content)=="string";return n.state?n.state.thinking=r:n.state={thinking:r},o.onCompletionChunk?.(t)},onTurnEnd(t){let e=t.currentTurn.slice(-1)[0];return e?.state&&(e.state.thinking=void 0),o.onTurnEnd?.(t)}});import{reactive as Ee}from"vue";var L=class extends Error{constructor(t){super(t),this.name="AbortError"}};function Pe(o){if(o.aborted)return{promise:Promise.reject(new L(String(o.reason??"Aborted"))),cleanup:()=>{}};let t=null;return{promise:new Promise((s,r)=>{t=()=>{r(new L(String(o.reason??"Aborted")))},o.addEventListener("abort",t,{once:!0})}),cleanup:()=>{t&&(o.removeEventListener("abort",t),t=null)}}}function pe(o,t){let{promise:e,cleanup:n}=Pe(t);return Promise.race([o,e]).finally(n)}function fe(o,t){let e={};for(let n in o)t.includes(n)&&(e[n]=o[n]);return e}function me(o,t){let e={};for(let n in o)t.includes(n)||(e[n]=o[n]);return e}async function*Q(o){if(de(o)){yield*o;return}let t=await o;if(de(t)){yield*t;return}yield t}function de(o){return o&&typeof o=="object"&&typeof o[Symbol.asyncIterator]=="function"}var ae=o=>typeof o=="object"&&o!==null,ge=o=>ae(o)&&typeof o.index=="number",W=(o,t)=>{for(let[e,n]of Object.entries(t)){let s=o[e];if(s)if(typeof s=="string"&&typeof n=="string")e==="type"&&s||(o[e]=s+n);else if(Array.isArray(s)&&Array.isArray(n))if(s.every(r=>ge(r))&&n.every(r=>ge(r))){let r=new Map(s.map(f=>[f.index,f])),a=new Map(n.map(f=>[f.index,f]));for(let[f,v]of a)if(r.has(f)){let R=r.get(f);r.set(f,W(R,v))}else r.set(f,v);let c=Math.max(...Array.from(r.keys()),-1)+1,d=c>s.length?Array.from({length:c}):s;for(let[f,v]of r)d[f]=v;o[e]=d}else o[e]=[...s,...n];else ae(s)&&ae(n)&&(o[e]=W(s,n));else o[e]=n}return o};function Oe(o,t){let e=[];for(let n=0;n<o.length;n++){let s=o[n];if(s.role==="assistant"&&s.tool_calls&&s.tool_calls.length>0){let r=new Set(s.tool_calls.map(d=>d.id)),a=new Set;for(let d=n+1;d<o.length;d++){let f=o[d];f.role==="tool"&&f.tool_call_id&&r.has(f.tool_call_id)&&a.add(f.tool_call_id)}let c=s.tool_calls.map(d=>d.id).filter(d=>!a.has(d));c.length>0&&e.push({insertAfterIndex:n,missingToolCallIds:c})}}for(let n=e.length-1;n>=0;n--){let{insertAfterIndex:s,missingToolCallIds:r}=e[n],a=r.map(c=>({role:"tool",tool_call_id:c,content:t}));o.splice(s+1,0,...a)}}var Ie="remove";function ke(o,t,e,n){let s=o.filter(a=>a[e]),r=new Set(s.flatMap(a=>a.tool_calls?.map(c=>c.id)??[]));if(t===Ie)for(let a=o.length-1;a>=0;a--){let c=o[a];(c[e]||c[n]||c.tool_call_id&&r.has(c.tool_call_id))&&o.splice(a,1)}else if(t===!0)for(let a of o)(a[e]||a.tool_call_id&&r.has(a.tool_call_id))&&(a[n]=!0,delete a[e])}var vt=o=>{let{getTools:t,beforeCallTools:e,callTool:n,onToolCallStart:s,onToolCallEnd:r,toolCallCancelledContent:a="Tool call cancelled.",toolCallFailedContent:c="Tool call failed.",autoFillMissingToolMessages:d=!1,excludeToolMessagesNextTurn:f=!1,...v}=o,R=Symbol("doNotSendNextTurn"),x=Symbol("doNotSend"),A=(...m)=>{let[h,{primaryMessage:T}]=m;T.state.toolCall[h.id].status="running",s?.(...m)},M=(...m)=>{let[h,{status:T,primaryMessage:O}]=m;O.state.toolCall[h.id].status=T,r?.(...m)};return{name:"tool",...v,onTurnStart:m=>{let{messages:h}=m;return d&&Oe(h,a),f&&ke(h,f,R,x),v.onTurnStart?.(m)},onBeforeRequest:async m=>{let{messages:h,requestBody:T}=m;f===!0&&(T.messages=h.filter(E=>!E[x]));let O=await t?.();return O&&O.length>0&&(T.tools=O),v.onBeforeRequest?.(m)},onAfterRequest:async m=>{let{currentMessage:h,lastChoice:T,appendMessage:O,abortSignal:E,setRequestState:i,requestNext:l}=m;if(T?.finish_reason!=="tool_calls"||!h.tool_calls?.length)return;f&&(h[R]=!0),i("processing","calling-tools"),await e?.(h.tool_calls,{...m,currentMessage:h});let g=h.tool_calls.map(async y=>{let S=Math.floor(Date.now()/1e3),w=Ee({role:"tool",tool_call_id:y.id,content:"",metadata:{createdAt:S,updatedAt:S}});O(w);let U={...m,primaryMessage:h,toolMessage:w};A(y,U);try{let k=n(y,U),B=Q(k);for await(let u of B){if(typeof u=="string")w.content+=u;else{let p={};try{p=JSON.parse(w.content||"{}")}catch(b){console.warn(b)}w.content=JSON.stringify(W(p,u))}w.metadata.updatedAt=Math.floor(Date.now()/1e3)}M(y,{...U,status:"success"})}catch(k){let B=k instanceof Error?k:new Error(String(k));if(E.aborted){M(y,{...U,status:"cancelled",error:B});return}console.error(k),w.content.length===0&&(w.content=c),M(y,{...U,status:"failed",error:B})}});return await Promise.all(g),l(),v.onAfterRequest?.(m)},onCompletionChunk:m=>{var T,O,E;let{currentMessage:h}=m;if(Array.isArray(h.tool_calls))for(let i of h.tool_calls)h.state?.toolCall?.[i.id]?.status||(h.state??(h.state={}),(T=h.state).toolCall??(T.toolCall={}),(O=h.state.toolCall)[E=i.id]??(O[E]={}));return v.onCompletionChunk?.(m)}}};var qe=o=>{let t=[];for(let e of o){if(e.name){let n=t.findIndex(s=>s.name===e.name);n!==-1&&t.splice(n,1)}t.push(e)}return t},ie=o=>{let{initialMessages:t=[],requestMessageFields:e=[],requestMessageFieldsExclude:n=["state","metadata","loading"],plugins:s=[],onCompletionChunk:r}=o,a=Y("idle"),c=Y(void 0),d=Y(t),f=Y(o.responseProvider),v=null,R=[],x={},A=[le(),ue(),ce()],M=qe(A.concat(s)),m=_e(()=>a.value==="processing"),h=async u=>{if(!u||!u.trim()){console.warn("Cannot send empty message");return}if(m.value){console.warn("Cannot send message while processing is in progress");return}let p=Math.floor(Date.now()/1e3);d.value.push({role:"user",content:u.trim(),metadata:{createdAt:p,updatedAt:p}}),R.push(d.value[d.value.length-1]),await w()},T=async(...u)=>{if(m.value){console.warn("Cannot send message while processing is in progress");return}d.value.push(...u),R.push(...u),await w()},O=u=>{let p=u;return e.length&&(p=p.map(b=>fe(b,e))),n.length&&(p=p.map(b=>me(b,n))),p},E=(u,p)=>{a.value=u,u==="processing"?c.value=p||"requesting":c.value=void 0},i=u=>{Object.assign(x,u)},l=u=>({messages:d.value,currentTurn:R,requestState:a.value,processingState:c.value,plugins:M,abortSignal:u,setRequestState:E,customContext:x,setCustomContext:i}),g=(u,p)=>typeof u.disabled=="function"?u.disabled(p):!!u.disabled,y=u=>!u||Object.keys(u).length===0?!1:Object.values(u).some(p=>!!p),S=async(u,p)=>{E("processing","requesting");let b=new Proxy({messages:O(d.value)},{set(D,P,N){return P==="messages"?(D.messages=O(N),!0):(D[P]=N,!0)}}),I=l(p);for(let D of M.filter(P=>!g(P,I)))await D.onBeforeRequest?.({...I,requestBody:b});let C=De({role:"",content:"",loading:!0});B(C);let _,Z=u(b,p),q=Q(Z);for await(let D of q){E("processing","completing"),C.loading&&(C.loading=void 0);let P=D.choices?.find(K=>K.index===0);if(P){_=P;let K=()=>{C.metadata||(C.metadata={});let{created:H,...ve}=D;C.metadata.createdAt=H,C.metadata.updatedAt=Math.floor(Date.now()/1e3),Object.assign(C.metadata,ve);let ee=y(P.delta)?P.delta:P.message;ee||(ee=P.delta||P.message||{}),W(C,ee)};if(r){let H=l(p);r({...H,chunk:D,choice:P,currentMessage:C},K)}else K()}let N=l(p);for(let K of M.filter(H=>!g(H,N)))K.onCompletionChunk?.({...N,abortSignal:p,chunk:D,choice:P,currentMessage:C})}await k(C,u,p,_)},w=async()=>{let u=new AbortController;v=u,x={};try{E("processing","requesting");let p=l(u.signal);for(let C of M.filter(_=>!g(_,p)))await C.onTurnStart?.(p);let b=f.value;try{await S(b,u.signal),E("completed")}catch(C){if(u.signal.aborted||C instanceof L||C instanceof Error&&C.name==="AbortError")E("aborted");else throw C}let I=l(u.signal);for(let C of M.filter(_=>!g(_,I)))await C.onTurnEnd?.(I)}catch(p){E("error");let b=!1,I=l(u.signal);for(let C of M.filter(_=>!g(_,I)))C.onError&&(b=!0,C.onError({...I,error:p}));if(!b)throw p}finally{let p=l(u.signal);for(let b of M.filter(I=>!g(I,p)))try{b.onFinally?.(p)}catch(I){console.error(`Error in onFinally hook for plugin ${b.name||"Anonymous"}:`,I)}v=null,R.slice(-1)[0]&&(R.slice(-1)[0].loading=void 0),R=[]}},U=async()=>{v?.abort(),m.value&&await new Promise(u=>{let p=Ue(m,b=>{b||(p(),u())},{immediate:!0})})},k=async(u,p,b,I)=>{let C=!1,_=l(b),Z=M.filter(q=>!g(q,_)).map(q=>{if(!q.onAfterRequest)return null;let D=N=>{B(N)},P=()=>{C=!0};return q.onAfterRequest({..._,currentMessage:u,lastChoice:I,appendMessage:D,requestNext:P})}).filter(q=>q!==null);await pe(Promise.all(Z),b),C&&await S(p,b)},B=u=>{let p=Array.isArray(u)?u:[u];d.value.push(...p),R.push(...p)};return{requestState:a,processingState:c,messages:d,responseProvider:f,isProcessing:m,sendMessage:h,send:T,abortRequest:U}};import{isRef as Be,toValue as Ne}from"vue";function ye(o,t=200,e=!1,n=!0,s=!1){return Fe(Ke(t,e,n,s),o)}function Fe(o,t){function e(...n){return new Promise((s,r)=>{Promise.resolve(o(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(s).catch(r)})}return e}var he=()=>{};function Ke(...o){let t=0,e,n=!0,s=he,r,a,c,d,f;!Be(o[0])&&typeof o[0]=="object"?{delay:a,trailing:c=!0,leading:d=!0,rejectOnCancel:f=!1}=o[0]:[a,c=!0,d=!0,f=!1]=o;let v=()=>{e&&(clearTimeout(e),e=void 0,s(),s=he)};return x=>{let A=Ne(a),M=Date.now()-t,m=()=>r=x();return v(),A<=0?(t=Date.now(),m()):(M>A?(t=Date.now(),(d||!n)&&m()):c&&(r=new Promise((h,T)=>{s=f?T:h,e=setTimeout(()=>{t=Date.now(),n=!0,h(m()),v()},Math.max(0,A-M))})),!d&&!e&&(e=setTimeout(()=>n=!0,A)),n=!1,r)}}var _t=o=>{let t=o.storage||re(),e=Ce([]),n=new Map,s=new Map,r=Ce(null),a=je(()=>{let i=r.value;if(!i)return null;let l=e.value.find(y=>y.id===i);if(!l)return null;let g=n.get(i);return g?{...l,engine:g}:null}),c=i=>{if(!t?.saveMessages)return;let l=i||r.value,g=e.value.find(S=>S.id===l);if(!g)return;g.updatedAt=Date.now(),t?.saveConversation?.(g);let y=n.get(g.id);y&&t.saveMessages(g.id,y.messages.value)},d=(i,l)=>{if(!o.autoSaveMessages||!t?.saveMessages)return;let g=s.get(i);g&&g();let y=o.autoSaveThrottle??1e3,S=ye(()=>{c(i)},y,!0,!0),w=Le(l.messages,S,{deep:!0});s.set(i,w)},f=i=>{let l=s.get(i);l&&(l(),s.delete(i))};t?.loadConversations&&Promise.resolve(t.loadConversations()).then(i=>{if(!i?.length)return[];if(e.value.length===0)return e.value=i,e.value;let l=new Map(e.value.map(g=>[g.id,g]));return i.forEach(g=>{l.has(g.id)||l.set(g.id,g)}),e.value=Array.from(l.values()),e.value}).then(i=>{o.onLoad?.(i)}).catch(i=>{console.error("[useConversation] loadConversations failed:",i)});let v=async(i,l)=>{let g=n.get(i);if(g)return g;let y=l?.initialMessages??o.useMessageOptions.initialMessages??[];if(t?.loadMessages)try{y=await t.loadMessages(i)}catch(w){console.error("[useConversation] loadMessages failed:",w)}let S=ie({...o.useMessageOptions,...l,initialMessages:y});return n.set(i,S),d(i,S),S};function R(){return Date.now().toString(36)+Math.random().toString(36).substring(2,9)}let x=i=>{let{id:l=R(),title:g,metadata:y,useMessageOptions:S}=i||{},w=Date.now(),U={id:l,title:g,createdAt:w,updatedAt:w,metadata:y};e.value.unshift(U);let k=ie({...o.useMessageOptions,...S});return n.set(l,k),d(l,k),t?.saveConversation?.(U),t?.saveMessages?.(l,k.messages.value),r.value=l,a.value},A=i=>{let{excludeId:l}=i||{};n.forEach((g,y)=>{if(l&&y===l)return;g.isProcessing?.value||(f(y),n.delete(y))})};return{conversations:e,activeConversationId:r,activeConversation:a,createConversation:x,switchConversation:async i=>i?r.value===i?a.value:e.value.find(g=>g.id===i)?(await v(i),A({excludeId:i}),r.value=i,a.value):null:null,deleteConversation:async i=>{let l=e.value.findIndex(y=>y.id===i);if(l===-1)return;await n.get(i)?.abortRequest(),f(i),n.delete(i),e.value.splice(l,1),t?.deleteConversation?.(i),r.value===i&&(r.value=null,A())},clear:()=>{e.value.map(l=>l.id).forEach(l=>{t?.deleteConversation?.(l)}),n.forEach(l=>{l.abortRequest()}),s.forEach(l=>{l()}),e.value=[],n.clear(),s.clear(),r.value=null},updateConversationTitle:(i,l)=>{let g=e.value.find(y=>y.id===i);g&&(g.title=l,g.updatedAt=Date.now(),t?.saveConversation?.(g))},saveMessages:c,sendMessage:i=>{a.value?.engine.sendMessage(i)},abortActiveRequest:async()=>{await a.value?.engine.abortRequest()}}};export{se as AIClient,G as BaseModelProvider,Ie as EXCLUDE_MODE_REMOVE,Me as ErrorType,z as IndexedDBStrategy,J as LocalStorageStrategy,j as OpenAIProvider,be as StreamEventType,xe as extractTextFromResponse,le as fallbackRolePlugin,Re as formatMessages,ne as handleSSEStream,Se as indexedDBStorageStrategyFactory,ce as lengthPlugin,re as localStorageStrategyFactory,Ae as sseStreamToGenerator,ue as thinkingPlugin,vt as toolPlugin,_t as useConversation,ie as useMessage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/tiny-robot-kit",
3
- "version": "0.4.0-alpha.1",
3
+ "version": "0.4.0-alpha.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -26,5 +26,8 @@
26
26
  "peerDependencies": {
27
27
  "vue": ">=3.0.0"
28
28
  },
29
- "gitHead": "db8d5d524ec974b61544c464ad4a246beec8d364"
29
+ "dependencies": {
30
+ "idb": "^8.0.3"
31
+ },
32
+ "gitHead": "a5ae0949b15667206b72511db738ab68e274e924"
30
33
  }