tabby-ai-assistant 1.0.13 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.editorconfig +18 -0
  2. package/README.md +40 -10
  3. package/dist/index.js +1 -1
  4. package/package.json +5 -3
  5. package/src/components/chat/ai-sidebar.component.scss +220 -9
  6. package/src/components/chat/ai-sidebar.component.ts +379 -29
  7. package/src/components/chat/chat-input.component.ts +36 -4
  8. package/src/components/chat/chat-interface.component.ts +225 -5
  9. package/src/components/chat/chat-message.component.ts +6 -1
  10. package/src/components/settings/context-settings.component.ts +91 -91
  11. package/src/components/terminal/ai-toolbar-button.component.ts +4 -2
  12. package/src/components/terminal/command-suggestion.component.ts +148 -6
  13. package/src/index.ts +81 -19
  14. package/src/providers/tabby/ai-toolbar-button.provider.ts +7 -3
  15. package/src/services/chat/ai-sidebar.service.ts +448 -410
  16. package/src/services/chat/chat-session.service.ts +36 -12
  17. package/src/services/context/compaction.ts +110 -134
  18. package/src/services/context/manager.ts +27 -7
  19. package/src/services/context/memory.ts +17 -33
  20. package/src/services/context/summary.service.ts +136 -0
  21. package/src/services/core/ai-assistant.service.ts +1060 -37
  22. package/src/services/core/ai-provider-manager.service.ts +154 -25
  23. package/src/services/core/checkpoint.service.ts +218 -18
  24. package/src/services/core/toast.service.ts +106 -106
  25. package/src/services/providers/anthropic-provider.service.ts +126 -30
  26. package/src/services/providers/base-provider.service.ts +90 -7
  27. package/src/services/providers/glm-provider.service.ts +151 -38
  28. package/src/services/providers/minimax-provider.service.ts +55 -40
  29. package/src/services/providers/ollama-provider.service.ts +117 -28
  30. package/src/services/providers/openai-compatible.service.ts +164 -34
  31. package/src/services/providers/openai-provider.service.ts +169 -34
  32. package/src/services/providers/vllm-provider.service.ts +116 -28
  33. package/src/services/terminal/terminal-context.service.ts +265 -5
  34. package/src/services/terminal/terminal-manager.service.ts +845 -748
  35. package/src/services/terminal/terminal-tools.service.ts +612 -441
  36. package/src/types/ai.types.ts +156 -3
  37. package/src/utils/cost.utils.ts +249 -0
  38. package/src/utils/validation.utils.ts +306 -2
  39. package/dist/index.js.LICENSE.txt +0 -18
  40. package/src/services/terminal/command-analyzer.service.ts +0 -43
  41. package/src/services/terminal/context-menu.service.ts +0 -45
  42. package/src/services/terminal/hotkey.service.ts +0 -53
@@ -6,7 +6,8 @@
6
6
  export enum MessageRole {
7
7
  USER = 'user',
8
8
  ASSISTANT = 'assistant',
9
- SYSTEM = 'system'
9
+ SYSTEM = 'system',
10
+ TOOL = 'tool' // 工具结果角色(部分 AI 需要)
10
11
  }
11
12
 
12
13
  // 聊天消息
@@ -140,7 +141,7 @@ export interface ValidationResult {
140
141
 
141
142
  // API消息接口(支持压缩标记)
142
143
  export interface ApiMessage {
143
- role: 'user' | 'assistant' | 'system';
144
+ role: 'user' | 'assistant' | 'system' | 'tool' | 'function';
144
145
  content: string | ContentBlock[];
145
146
  ts: number; // 时间戳(毫秒)
146
147
 
@@ -148,6 +149,11 @@ export interface ApiMessage {
148
149
  isSummary?: boolean; // 是否为摘要消息
149
150
  condenseId?: string; // 摘要ID
150
151
  condenseParent?: string; // 被哪个摘要压缩
152
+ summaryMeta?: { // 摘要元数据
153
+ originalMessageCount: number;
154
+ tokensCost: number;
155
+ compressionRatio: number;
156
+ };
151
157
 
152
158
  // 截断相关元数据
153
159
  isTruncationMarker?: boolean; // 是否为截断标记
@@ -245,6 +251,16 @@ export interface ExtendedChatMessage extends ChatMessage {
245
251
  tokenUsage?: TokenUsage;
246
252
  }
247
253
 
254
+ // 压缩后的检查点数据接口
255
+ export interface CompressedCheckpointData {
256
+ compressed: boolean;
257
+ compressionRatio: number;
258
+ originalSize: number;
259
+ compressedSize: number;
260
+ messages?: ApiMessage[]; // 可选,用于即时访问
261
+ messagesJson: string; // 压缩后的JSON字符串
262
+ }
263
+
248
264
  // 检查点接口
249
265
  export interface Checkpoint {
250
266
  id: string;
@@ -253,6 +269,11 @@ export interface Checkpoint {
253
269
  summary: string;
254
270
  createdAt: number; // 时间戳(毫秒)
255
271
  tokenUsage: TokenUsage;
272
+ compressedData?: CompressedCheckpointData; // 压缩数据(可选)
273
+
274
+ // 新增字段
275
+ tags?: string[]; // 标签列表
276
+ isArchived?: boolean; // 是否已归档
256
277
  }
257
278
 
258
279
  // ============================================================================
@@ -261,7 +282,7 @@ export interface Checkpoint {
261
282
 
262
283
  // 流式事件类型
263
284
  export interface StreamEvent {
264
- type: 'text_delta' | 'tool_use_start' | 'tool_use_delta' | 'tool_use_end' | 'message_end' | 'error';
285
+ type: 'text_delta' | 'tool_use_start' | 'tool_use_delta' | 'tool_use_end' | 'tool_result' | 'tool_error' | 'message_end' | 'error';
265
286
  // 文本增量
266
287
  textDelta?: string;
267
288
  // 工具调用(完整时才有)
@@ -270,8 +291,140 @@ export interface StreamEvent {
270
291
  name: string;
271
292
  input: any;
272
293
  };
294
+ // 工具结果(tool_result 事件)
295
+ result?: {
296
+ tool_use_id: string;
297
+ content: string;
298
+ is_error?: boolean;
299
+ };
273
300
  // 错误信息
274
301
  error?: string;
275
302
  // 最终消息(message_end 时)
276
303
  message?: ChatMessage;
277
304
  }
305
+
306
+ // ============================================================================
307
+ // Agent 循环相关类型定义
308
+ // ============================================================================
309
+
310
+ // 工具调用接口
311
+ export interface ToolCall {
312
+ id: string;
313
+ name: string;
314
+ input: any;
315
+ }
316
+
317
+ // 工具结果接口
318
+ export interface ToolResult {
319
+ tool_use_id: string;
320
+ name?: string; // 工具名称
321
+ content: string;
322
+ is_error?: boolean;
323
+ }
324
+
325
+ // Agent 事件类型
326
+ export type AgentEventType =
327
+ | 'text_delta' // 文本增量
328
+ | 'tool_use_start' // 工具开始
329
+ | 'tool_use_end' // 工具调用结束(收集参数)
330
+ | 'tool_executing' // 工具正在执行
331
+ | 'tool_executed' // 工具执行完成(带结果)
332
+ | 'tool_error' // 工具执行错误
333
+ | 'round_start' // 新一轮开始
334
+ | 'round_end' // 一轮结束
335
+ | 'agent_complete' // Agent 循环完成
336
+ | 'error'; // 错误
337
+
338
+ // Agent 流式事件
339
+ export interface AgentStreamEvent {
340
+ type: AgentEventType;
341
+
342
+ // text_delta 事件
343
+ textDelta?: string;
344
+
345
+ // 工具相关事件
346
+ toolCall?: {
347
+ id: string;
348
+ name: string;
349
+ input: any;
350
+ };
351
+
352
+ // tool_executed/tool_error 事件
353
+ toolResult?: {
354
+ tool_use_id: string;
355
+ content: string;
356
+ is_error?: boolean;
357
+ duration?: number;
358
+ };
359
+
360
+ // round_start/round_end 事件
361
+ round?: number;
362
+
363
+ // agent_complete 事件
364
+ reason?: TerminationReason;
365
+ totalRounds?: number;
366
+ terminationMessage?: string; // 可选的终止详情消息
367
+
368
+ // error 事件
369
+ error?: string;
370
+
371
+ // message_end 保留
372
+ message?: ChatMessage;
373
+ }
374
+
375
+ // Agent 循环配置
376
+ export interface AgentLoopConfig {
377
+ maxRounds?: number; // 最大轮数,默认 15
378
+ timeoutMs?: number; // 默认 120000 (2分钟)
379
+ repeatThreshold?: number; // 默认 3 次
380
+ failureThreshold?: number; // 默认 2 次
381
+ enableTaskComplete?: boolean; // 默认 true
382
+ onRoundStart?: (round: number) => void;
383
+ onRoundEnd?: (round: number) => void;
384
+ onAgentComplete?: (reason: string, totalRounds: number) => void;
385
+ }
386
+
387
+ // ============================================================================
388
+ // 智能 Agent 终止相关类型定义
389
+ // ============================================================================
390
+
391
+ // 终止原因枚举
392
+ export type TerminationReason =
393
+ | 'task_complete' // AI 主动调用 task_complete 工具
394
+ | 'no_tools' // 本轮无工具调用
395
+ | 'summarizing' // 检测到 AI 正在总结
396
+ | 'repeated_tool' // 重复调用相同工具
397
+ | 'high_failure_rate' // 连续失败率过高
398
+ | 'timeout' // 总时间超时
399
+ | 'max_rounds' // 达到最大轮数(安全保底)
400
+ | 'user_cancel'; // 用户取消
401
+
402
+ // Agent 状态追踪
403
+ export interface AgentState {
404
+ currentRound: number;
405
+ startTime: number;
406
+ toolCallHistory: ToolCallRecord[];
407
+ lastAiResponse: string;
408
+ isActive: boolean;
409
+ }
410
+
411
+ // 工具调用记录
412
+ export interface ToolCallRecord {
413
+ name: string;
414
+ input: any;
415
+ inputHash: string; // 用于快速比较
416
+ success: boolean;
417
+ timestamp: number;
418
+ }
419
+
420
+ // 终止检测结果
421
+ export interface TerminationResult {
422
+ shouldTerminate: boolean;
423
+ reason: TerminationReason;
424
+ message?: string;
425
+ }
426
+
427
+ // 扩展 ToolResult 添加任务完成标记
428
+ export interface ExtendedToolResult extends ToolResult {
429
+ isTaskComplete?: boolean; // 特殊标记:task_complete 工具调用
430
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * API成本计算工具类
3
+ * 提供各AI提供商的API调用成本估算功能
4
+ */
5
+
6
+ /**
7
+ * AI提供商类型
8
+ */
9
+ export type AIProvider = 'openai' | 'anthropic' | 'minimax' | 'glm' | 'openai-compatible';
10
+
11
+ /**
12
+ * 模型定价信息
13
+ */
14
+ export interface ModelPricing {
15
+ provider: AIProvider;
16
+ model: string;
17
+ inputPricePerMillion: number; // 每百万输入token的价格(美元)
18
+ outputPricePerMillion: number; // 每百万输出token的价格(美元)
19
+ }
20
+
21
+ /**
22
+ * Token使用信息
23
+ */
24
+ export interface TokenUsage {
25
+ inputTokens: number;
26
+ outputTokens: number;
27
+ }
28
+
29
+ /**
30
+ * 成本计算结果
31
+ */
32
+ export interface CostResult {
33
+ inputCost: number; // 输入成本(美元)
34
+ outputCost: number; // 输出成本(美元)
35
+ totalCost: number; // 总成本(美元)
36
+ inputPricePerMillion: number;
37
+ outputPricePerMillion: number;
38
+ }
39
+
40
+ /**
41
+ * 默认模型定价表(2024年最新价格)
42
+ */
43
+ const DEFAULT_PRICING: ModelPricing[] = [
44
+ // OpenAI
45
+ { provider: 'openai', model: 'gpt-4', inputPricePerMillion: 30, outputPricePerMillion: 60 },
46
+ { provider: 'openai', model: 'gpt-4-turbo', inputPricePerMillion: 10, outputPricePerMillion: 30 },
47
+ { provider: 'openai', model: 'gpt-4o', inputPricePerMillion: 5, outputPricePerMillion: 15 },
48
+ { provider: 'openai', model: 'gpt-3.5-turbo', inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
49
+
50
+ // Anthropic
51
+ { provider: 'anthropic', model: 'claude-3-5-sonnet-20241022', inputPricePerMillion: 3, outputPricePerMillion: 15 },
52
+ { provider: 'anthropic', model: 'claude-3-opus-20240229', inputPricePerMillion: 15, outputPricePerMillion: 75 },
53
+ { provider: 'anthropic', model: 'claude-3-haiku-20240307', inputPricePerMillion: 0.25, outputPricePerMillion: 1.25 },
54
+
55
+ // Minimax (智谱AI)
56
+ { provider: 'minimax', model: 'abab6.5s-chat', inputPricePerMillion: 0.3, outputPricePerMillion: 0.3 },
57
+ { provider: 'minimax', model: 'abab6.5-chat', inputPricePerMillion: 0.5, outputPricePerMillion: 0.5 },
58
+ { provider: 'minimax', model: 'abab5.5-chat', inputPricePerMillion: 1, outputPricePerMillion: 1 },
59
+
60
+ // GLM (智谱AI)
61
+ { provider: 'glm', model: 'glm-4', inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
62
+ { provider: 'glm', model: 'glm-4v', inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
63
+ { provider: 'glm', model: 'glm-3-turbo', inputPricePerMillion: 0.1, outputPricePerMillion: 0.1 },
64
+ ];
65
+
66
+ // 自定义定价表(可扩展)
67
+ let customPricing: ModelPricing[] = [];
68
+
69
+ /**
70
+ * 设置自定义模型定价
71
+ */
72
+ export function setCustomPricing(pricing: ModelPricing[]): void {
73
+ customPricing = [...pricing];
74
+ }
75
+
76
+ /**
77
+ * 获取模型定价信息
78
+ */
79
+ export function getModelPricing(provider: AIProvider, model: string): ModelPricing | undefined {
80
+ // 首先查找自定义定价
81
+ const custom = customPricing.find(p => p.provider === provider && p.model === model);
82
+ if (custom) {
83
+ return custom;
84
+ }
85
+
86
+ // 然后查找默认定价
87
+ const defaultPricing = DEFAULT_PRICING.find(p => p.provider === provider && p.model === model);
88
+
89
+ if (defaultPricing) {
90
+ return defaultPricing;
91
+ }
92
+
93
+ // 返回该提供商的通用定价
94
+ return getDefaultPricingForProvider(provider);
95
+ }
96
+
97
+ /**
98
+ * 获取提供商的默认定价
99
+ */
100
+ function getDefaultPricingForProvider(provider: AIProvider): ModelPricing | undefined {
101
+ const providerDefaults: Record<AIProvider, Partial<ModelPricing>> = {
102
+ 'openai': { inputPricePerMillion: 5, outputPricePerMillion: 15 },
103
+ 'anthropic': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
104
+ 'minimax': { inputPricePerMillion: 0.5, outputPricePerMillion: 0.5 },
105
+ 'glm': { inputPricePerMillion: 0.5, outputPricePerMillion: 1 },
106
+ 'openai-compatible': { inputPricePerMillion: 1, outputPricePerMillion: 2 }
107
+ };
108
+
109
+ const defaults = providerDefaults[provider];
110
+ if (defaults) {
111
+ return {
112
+ provider,
113
+ model: 'default',
114
+ inputPricePerMillion: defaults.inputPricePerMillion ?? 1,
115
+ outputPricePerMillion: defaults.outputPricePerMillion ?? 2
116
+ };
117
+ }
118
+
119
+ return undefined;
120
+ }
121
+
122
+ /**
123
+ * 计算API调用成本
124
+ * @param provider AI提供商
125
+ * @param model 模型名称
126
+ * @param usage Token使用情况
127
+ * @returns 成本计算结果
128
+ */
129
+ export function calculateCost(
130
+ provider: AIProvider,
131
+ model: string,
132
+ usage: TokenUsage
133
+ ): CostResult {
134
+ const pricing = getModelPricing(provider, model);
135
+
136
+ if (!pricing) {
137
+ // 未知提供商,返回零成本
138
+ return {
139
+ inputCost: 0,
140
+ outputCost: 0,
141
+ totalCost: 0,
142
+ inputPricePerMillion: 0,
143
+ outputPricePerMillion: 0
144
+ };
145
+ }
146
+
147
+ const inputCost = (usage.inputTokens / 1000000) * pricing.inputPricePerMillion;
148
+ const outputCost = (usage.outputTokens / 1000000) * pricing.outputPricePerMillion;
149
+
150
+ return {
151
+ inputCost: Math.round(inputCost * 1000000) / 1000000, // 保留6位小数
152
+ outputCost: Math.round(outputCost * 1000000) / 1000000,
153
+ totalCost: Math.round((inputCost + outputCost) * 1000000) / 1000000,
154
+ inputPricePerMillion: pricing.inputPricePerMillion,
155
+ outputPricePerMillion: pricing.outputPricePerMillion
156
+ };
157
+ }
158
+
159
+ /**
160
+ * 计算摘要生成成本
161
+ */
162
+ export function calculateSummaryCost(
163
+ provider: AIProvider,
164
+ model: string,
165
+ originalMessageCount: number,
166
+ tokensUsed: number
167
+ ): CostResult {
168
+ // 摘要生成主要是输入成本
169
+ const pricing = getModelPricing(provider, model);
170
+
171
+ if (!pricing) {
172
+ return {
173
+ inputCost: 0,
174
+ outputCost: 0,
175
+ totalCost: 0,
176
+ inputPricePerMillion: 0,
177
+ outputPricePerMillion: 0
178
+ };
179
+ }
180
+
181
+ // 估算摘要的输入和输出token(假设输出占输入的5%)
182
+ const estimatedInputTokens = tokensUsed;
183
+ const estimatedOutputTokens = Math.floor(tokensUsed * 0.05);
184
+
185
+ return calculateCost(provider, model, {
186
+ inputTokens: estimatedInputTokens,
187
+ outputTokens: estimatedOutputTokens
188
+ });
189
+ }
190
+
191
+ /**
192
+ * 格式化成本为可读字符串
193
+ */
194
+ export function formatCost(cost: number): string {
195
+ if (cost < 0.001) {
196
+ return `$${(cost * 1000000).toFixed(2)}`;
197
+ } else if (cost < 1) {
198
+ return `$${cost.toFixed(4)}`;
199
+ } else {
200
+ return `$${cost.toFixed(2)}`;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 格式化成本详细信息
206
+ */
207
+ export function formatCostDetail(result: CostResult): string {
208
+ const parts: string[] = [];
209
+
210
+ if (result.inputCost > 0) {
211
+ parts.push(`输入: ${formatCost(result.inputCost)}`);
212
+ }
213
+ if (result.outputCost > 0) {
214
+ parts.push(`输出: ${formatCost(result.outputCost)}`);
215
+ }
216
+
217
+ return parts.join(', ') + ` (总计: ${formatCost(result.totalCost)})`;
218
+ }
219
+
220
+ /**
221
+ * 估算消息的Token数量
222
+ */
223
+ export function estimateTokenCount(text: string): number {
224
+ // 粗略估算:1个Token约等于4个字符(英文)
225
+ // 中文:1个Token约等于1.5个字符
226
+ const chineseCharCount = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
227
+ const englishCharCount = text.length - chineseCharCount;
228
+
229
+ return Math.ceil(chineseCharCount / 1.5 + englishCharCount / 4);
230
+ }
231
+
232
+ /**
233
+ * 计算批量请求的总成本
234
+ */
235
+ export function calculateBatchCost(
236
+ provider: AIProvider,
237
+ model: string,
238
+ requests: TokenUsage[]
239
+ ): CostResult {
240
+ const totalUsage = requests.reduce(
241
+ (acc, usage) => ({
242
+ inputTokens: acc.inputTokens + usage.inputTokens,
243
+ outputTokens: acc.outputTokens + usage.outputTokens
244
+ }),
245
+ { inputTokens: 0, outputTokens: 0 }
246
+ );
247
+
248
+ return calculateCost(provider, model, totalUsage);
249
+ }