tabby-ai-assistant 1.0.5 → 1.0.6

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 (116) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
  2. package/dist/components/chat/chat-interface.component.d.ts +38 -6
  3. package/dist/components/settings/general-settings.component.d.ts +6 -3
  4. package/dist/components/settings/provider-config.component.d.ts +25 -12
  5. package/dist/components/terminal/command-preview.component.d.ts +38 -0
  6. package/dist/index-full.d.ts +8 -0
  7. package/dist/index-minimal.d.ts +3 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.js +1 -2
  10. package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
  11. package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
  12. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
  13. package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
  14. package/dist/services/chat/chat-history.service.d.ts +78 -0
  15. package/dist/services/chat/chat-session.service.d.ts +57 -2
  16. package/dist/services/context/compaction.d.ts +90 -0
  17. package/dist/services/context/manager.d.ts +69 -0
  18. package/dist/services/context/memory.d.ts +116 -0
  19. package/dist/services/context/token-budget.d.ts +105 -0
  20. package/dist/services/core/ai-assistant.service.d.ts +40 -1
  21. package/dist/services/core/checkpoint.service.d.ts +130 -0
  22. package/dist/services/platform/escape-sequence.service.d.ts +132 -0
  23. package/dist/services/platform/platform-detection.service.d.ts +146 -0
  24. package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
  25. package/dist/services/providers/base-provider.service.d.ts +6 -1
  26. package/dist/services/providers/glm-provider.service.d.ts +5 -0
  27. package/dist/services/providers/minimax-provider.service.d.ts +10 -1
  28. package/dist/services/providers/ollama-provider.service.d.ts +76 -0
  29. package/dist/services/providers/openai-compatible.service.d.ts +5 -0
  30. package/dist/services/providers/openai-provider.service.d.ts +5 -0
  31. package/dist/services/providers/vllm-provider.service.d.ts +82 -0
  32. package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
  33. package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
  34. package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
  35. package/dist/types/ai.types.d.ts +92 -0
  36. package/dist/types/provider.types.d.ts +1 -1
  37. package/package.json +7 -10
  38. package/src/components/chat/ai-sidebar.component.ts +945 -0
  39. package/src/components/chat/chat-input.component.html +9 -24
  40. package/src/components/chat/chat-input.component.scss +3 -2
  41. package/src/components/chat/chat-interface.component.html +77 -69
  42. package/src/components/chat/chat-interface.component.scss +54 -4
  43. package/src/components/chat/chat-interface.component.ts +250 -34
  44. package/src/components/chat/chat-settings.component.scss +4 -4
  45. package/src/components/chat/chat-settings.component.ts +22 -11
  46. package/src/components/common/error-message.component.html +15 -0
  47. package/src/components/common/error-message.component.scss +77 -0
  48. package/src/components/common/error-message.component.ts +2 -96
  49. package/src/components/common/loading-spinner.component.html +4 -0
  50. package/src/components/common/loading-spinner.component.scss +57 -0
  51. package/src/components/common/loading-spinner.component.ts +2 -63
  52. package/src/components/security/consent-dialog.component.html +22 -0
  53. package/src/components/security/consent-dialog.component.scss +34 -0
  54. package/src/components/security/consent-dialog.component.ts +2 -55
  55. package/src/components/security/password-prompt.component.html +19 -0
  56. package/src/components/security/password-prompt.component.scss +30 -0
  57. package/src/components/security/password-prompt.component.ts +2 -54
  58. package/src/components/security/risk-confirm-dialog.component.html +8 -12
  59. package/src/components/security/risk-confirm-dialog.component.scss +8 -5
  60. package/src/components/security/risk-confirm-dialog.component.ts +6 -6
  61. package/src/components/settings/ai-settings-tab.component.html +16 -20
  62. package/src/components/settings/ai-settings-tab.component.scss +8 -5
  63. package/src/components/settings/ai-settings-tab.component.ts +12 -12
  64. package/src/components/settings/general-settings.component.html +8 -17
  65. package/src/components/settings/general-settings.component.scss +6 -3
  66. package/src/components/settings/general-settings.component.ts +62 -22
  67. package/src/components/settings/provider-config.component.html +19 -39
  68. package/src/components/settings/provider-config.component.scss +182 -39
  69. package/src/components/settings/provider-config.component.ts +119 -7
  70. package/src/components/settings/security-settings.component.scss +1 -1
  71. package/src/components/terminal/ai-toolbar-button.component.html +8 -0
  72. package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
  73. package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
  74. package/src/components/terminal/command-preview.component.html +61 -0
  75. package/src/components/terminal/command-preview.component.scss +72 -0
  76. package/src/components/terminal/command-preview.component.ts +127 -140
  77. package/src/components/terminal/command-suggestion.component.html +23 -0
  78. package/src/components/terminal/command-suggestion.component.scss +55 -0
  79. package/src/components/terminal/command-suggestion.component.ts +2 -77
  80. package/src/index-minimal.ts +32 -0
  81. package/src/index.ts +94 -11
  82. package/src/index.ts.backup +165 -0
  83. package/src/providers/tabby/ai-config.provider.ts +60 -51
  84. package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
  85. package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
  86. package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
  87. package/src/services/chat/ai-sidebar.service.ts +258 -0
  88. package/src/services/chat/chat-history.service.ts +308 -0
  89. package/src/services/chat/chat-history.service.ts.backup +239 -0
  90. package/src/services/chat/chat-session.service.ts +276 -3
  91. package/src/services/context/compaction.ts +483 -0
  92. package/src/services/context/manager.ts +442 -0
  93. package/src/services/context/memory.ts +519 -0
  94. package/src/services/context/token-budget.ts +422 -0
  95. package/src/services/core/ai-assistant.service.ts +280 -5
  96. package/src/services/core/ai-provider-manager.service.ts +2 -2
  97. package/src/services/core/checkpoint.service.ts +619 -0
  98. package/src/services/platform/escape-sequence.service.ts +499 -0
  99. package/src/services/platform/platform-detection.service.ts +494 -0
  100. package/src/services/providers/anthropic-provider.service.ts +28 -1
  101. package/src/services/providers/base-provider.service.ts +7 -1
  102. package/src/services/providers/glm-provider.service.ts +28 -1
  103. package/src/services/providers/minimax-provider.service.ts +209 -11
  104. package/src/services/providers/ollama-provider.service.ts +445 -0
  105. package/src/services/providers/openai-compatible.service.ts +9 -0
  106. package/src/services/providers/openai-provider.service.ts +9 -0
  107. package/src/services/providers/vllm-provider.service.ts +463 -0
  108. package/src/services/security/risk-assessment.service.ts +6 -2
  109. package/src/services/terminal/buffer-analyzer.service.ts +594 -0
  110. package/src/services/terminal/terminal-manager.service.ts +748 -0
  111. package/src/services/terminal/terminal-tools.service.ts +441 -0
  112. package/src/styles/ai-assistant.scss +78 -6
  113. package/src/types/ai.types.ts +144 -0
  114. package/src/types/provider.types.ts +1 -1
  115. package/tsconfig.json +9 -9
  116. package/webpack.config.js +28 -6
@@ -1,20 +1,73 @@
1
- import { Injectable } from '@angular/core';
1
+ import { Injectable, Inject, Optional } from '@angular/core';
2
2
  import { Observable, from, throwError } from 'rxjs';
3
3
  import { map, catchError, tap } from 'rxjs/operators';
4
- import { ChatMessage, MessageRole, ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse } from '../../types/ai.types';
4
+ import { ChatMessage, MessageRole, ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, StreamEvent } from '../../types/ai.types';
5
5
  import { AiProviderManagerService } from './ai-provider-manager.service';
6
6
  import { ConfigProviderService } from './config-provider.service';
7
7
  import { TerminalContextService } from '../terminal/terminal-context.service';
8
+ import { TerminalToolsService, ToolCall, ToolResult } from '../terminal/terminal-tools.service';
8
9
  import { LoggerService } from './logger.service';
10
+ import { BaseAiProvider } from '../../types/provider.types';
11
+
12
+ // Import all provider services
13
+ import { OpenAiProviderService } from '../providers/openai-provider.service';
14
+ import { AnthropicProviderService } from '../providers/anthropic-provider.service';
15
+ import { MinimaxProviderService } from '../providers/minimax-provider.service';
16
+ import { GlmProviderService } from '../providers/glm-provider.service';
17
+ import { OpenAiCompatibleProviderService } from '../providers/openai-compatible.service';
18
+ import { OllamaProviderService } from '../providers/ollama-provider.service';
19
+ import { VllmProviderService } from '../providers/vllm-provider.service';
9
20
 
10
21
  @Injectable({ providedIn: 'root' })
11
22
  export class AiAssistantService {
23
+ // 提供商映射表
24
+ private providerMapping: { [key: string]: BaseAiProvider } = {};
25
+
12
26
  constructor(
13
27
  private providerManager: AiProviderManagerService,
14
28
  private config: ConfigProviderService,
15
29
  private terminalContext: TerminalContextService,
16
- private logger: LoggerService
17
- ) {}
30
+ private terminalTools: TerminalToolsService,
31
+ private logger: LoggerService,
32
+ // 注入所有提供商服务
33
+ @Optional() private openaiProvider: OpenAiProviderService,
34
+ @Optional() private anthropicProvider: AnthropicProviderService,
35
+ @Optional() private minimaxProvider: MinimaxProviderService,
36
+ @Optional() private glmProvider: GlmProviderService,
37
+ @Optional() private openaiCompatibleProvider: OpenAiCompatibleProviderService,
38
+ @Optional() private ollamaProvider: OllamaProviderService,
39
+ @Optional() private vllmProvider: VllmProviderService
40
+ ) {
41
+ // 构建提供商映射表
42
+ this.buildProviderMapping();
43
+ }
44
+
45
+ /**
46
+ * 构建提供商映射表
47
+ */
48
+ private buildProviderMapping(): void {
49
+ if (this.openaiProvider) {
50
+ this.providerMapping['openai'] = this.openaiProvider;
51
+ }
52
+ if (this.anthropicProvider) {
53
+ this.providerMapping['anthropic'] = this.anthropicProvider;
54
+ }
55
+ if (this.minimaxProvider) {
56
+ this.providerMapping['minimax'] = this.minimaxProvider;
57
+ }
58
+ if (this.glmProvider) {
59
+ this.providerMapping['glm'] = this.glmProvider;
60
+ }
61
+ if (this.openaiCompatibleProvider) {
62
+ this.providerMapping['openai-compatible'] = this.openaiCompatibleProvider;
63
+ }
64
+ if (this.ollamaProvider) {
65
+ this.providerMapping['ollama'] = this.ollamaProvider;
66
+ }
67
+ if (this.vllmProvider) {
68
+ this.providerMapping['vllm'] = this.vllmProvider;
69
+ }
70
+ }
18
71
 
19
72
  /**
20
73
  * 初始化AI助手
@@ -28,15 +81,70 @@ export class AiAssistantService {
28
81
  return;
29
82
  }
30
83
 
84
+ // 注册并配置所有提供商
85
+ this.registerAllProviders();
86
+
31
87
  // 设置默认提供商
32
88
  const defaultProvider = this.config.getDefaultProvider();
33
89
  if (defaultProvider && this.providerManager.hasProvider(defaultProvider)) {
34
90
  this.providerManager.setActiveProvider(defaultProvider);
91
+ this.logger.info(`Active provider set to: ${defaultProvider}`);
92
+ } else {
93
+ // 尝试设置第一个已配置的提供商
94
+ const allConfigs = this.config.getAllProviderConfigs();
95
+ for (const [name, providerConfig] of Object.entries(allConfigs)) {
96
+ if (providerConfig?.apiKey && this.providerManager.hasProvider(name)) {
97
+ this.providerManager.setActiveProvider(name);
98
+ this.config.setDefaultProvider(name);
99
+ this.logger.info(`Auto-selected provider: ${name}`);
100
+ break;
101
+ }
102
+ }
35
103
  }
36
104
 
37
105
  this.logger.info('AI Assistant initialized successfully');
38
106
  }
39
107
 
108
+ /**
109
+ * 注册并配置所有提供商
110
+ */
111
+ private registerAllProviders(): void {
112
+ this.logger.info('Registering AI providers...');
113
+
114
+ const allConfigs = this.config.getAllProviderConfigs();
115
+ let registeredCount = 0;
116
+
117
+ for (const [name, providerConfig] of Object.entries(allConfigs)) {
118
+ const provider = this.providerMapping[name];
119
+ if (provider) {
120
+ try {
121
+ // 配置提供商(这会初始化客户端)
122
+ if (providerConfig) {
123
+ provider.configure({
124
+ ...providerConfig,
125
+ enabled: providerConfig.enabled !== false
126
+ });
127
+ this.logger.info(`Provider ${name} configured`, {
128
+ hasApiKey: !!providerConfig.apiKey,
129
+ model: providerConfig.model
130
+ });
131
+ }
132
+
133
+ // 注册到管理器
134
+ this.providerManager.registerProvider(provider);
135
+ registeredCount++;
136
+ this.logger.info(`Provider registered: ${name}`);
137
+ } catch (error) {
138
+ this.logger.error(`Failed to register provider: ${name}`, error);
139
+ }
140
+ } else {
141
+ this.logger.warn(`Provider not found in mapping: ${name}`);
142
+ }
143
+ }
144
+
145
+ this.logger.info(`Total providers registered: ${registeredCount}`);
146
+ }
147
+
40
148
  /**
41
149
  * 聊天功能
42
150
  */
@@ -54,7 +162,30 @@ export class AiAssistantService {
54
162
  throw new Error(`Provider ${activeProvider.name} does not support chat capability`);
55
163
  }
56
164
 
57
- const response = await activeProvider.chat(request);
165
+ // 如果启用工具调用,添加工具定义
166
+ if (request.enableTools !== false) {
167
+ request.tools = this.terminalTools.getToolDefinitions();
168
+ }
169
+
170
+ let response = await activeProvider.chat(request);
171
+
172
+ // 处理工具调用(如果响应包含工具调用)
173
+ response = await this.handleToolCalls(request, response, activeProvider);
174
+
175
+ // 添加:检测未调用工具的问题响应
176
+ const toolCalls = (response as any).toolCalls;
177
+ if (!toolCalls || toolCalls.length === 0) {
178
+ const content = response.message.content.toLowerCase();
179
+ const intentPatterns = ['我将向', '我会执行', '正在执行', '向终端写入'];
180
+ const hasExecutionIntent = intentPatterns.some(p => content.includes(p));
181
+
182
+ if (hasExecutionIntent) {
183
+ this.logger.warn('AI stated execution intent but did not call tools');
184
+ // 添加警告消息到响应中
185
+ response.message.content += '\n\n⚠️ 注意:命令未实际执行。请明确告诉我需要执行的命令。';
186
+ }
187
+ }
188
+
58
189
  this.logger.info('Chat request completed successfully');
59
190
  return response;
60
191
 
@@ -64,6 +195,150 @@ export class AiAssistantService {
64
195
  }
65
196
  }
66
197
 
198
+ /**
199
+ * 流式聊天功能
200
+ */
201
+ chatStream(request: ChatRequest): Observable<any> {
202
+ const activeProvider = this.providerManager.getActiveProvider() as any;
203
+ if (!activeProvider) {
204
+ return throwError(() => new Error('No active AI provider available'));
205
+ }
206
+
207
+ // 检查提供商是否支持流式
208
+ if (!activeProvider.supportsCapability('streaming' as any)) {
209
+ this.logger.warn(`Provider ${activeProvider.name} does not support streaming, falling back to non-streaming`);
210
+ return from(this.chat(request));
211
+ }
212
+
213
+ // 添加工具定义
214
+ if (request.enableTools !== false) {
215
+ request.tools = this.terminalTools.getToolDefinitions();
216
+ }
217
+
218
+ // 调用流式方法
219
+ return activeProvider.chatStream(request).pipe(
220
+ tap((event: StreamEvent) => {
221
+ // 工具调用完成时执行
222
+ if (event.type === 'tool_use_end' && event.toolCall) {
223
+ this.executeToolAndContinue(event.toolCall, request, activeProvider);
224
+ }
225
+ }),
226
+ catchError(error => {
227
+ this.logger.error('Stream error', error);
228
+ return throwError(() => error);
229
+ })
230
+ );
231
+ }
232
+
233
+ /**
234
+ * 执行工具调用(不阻塞流)
235
+ */
236
+ private async executeToolAndContinue(
237
+ toolCall: { id: string; name: string; input: any },
238
+ request: ChatRequest,
239
+ provider: any
240
+ ): Promise<void> {
241
+ try {
242
+ const result = await this.terminalTools.executeToolCall({
243
+ id: toolCall.id,
244
+ name: toolCall.name,
245
+ input: toolCall.input
246
+ });
247
+ this.logger.info('Tool executed in stream', { name: toolCall.name, result: result.content.substring(0, 100) });
248
+ } catch (error) {
249
+ this.logger.error('Tool execution failed in stream', error);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * 处理工具调用
255
+ * @param maxDepth 最大递归深度,避免无限循环
256
+ */
257
+ private async handleToolCalls(
258
+ originalRequest: ChatRequest,
259
+ response: ChatResponse,
260
+ provider: BaseAiProvider,
261
+ depth: number = 0,
262
+ maxDepth: number = 10
263
+ ): Promise<ChatResponse> {
264
+ // 检查响应中是否有工具调用
265
+ const toolCalls = (response as any).toolCalls as ToolCall[] | undefined;
266
+
267
+ if (!toolCalls || toolCalls.length === 0) {
268
+ return response;
269
+ }
270
+
271
+ // 检查递归深度
272
+ if (depth >= maxDepth) {
273
+ this.logger.warn('Max tool call depth reached', { depth, maxDepth });
274
+ return response;
275
+ }
276
+
277
+ this.logger.info('Tool calls detected', { count: toolCalls.length, depth });
278
+
279
+ // 执行所有工具调用
280
+ const toolResults: ToolResult[] = [];
281
+ for (const toolCall of toolCalls) {
282
+ this.logger.info('Executing tool in handleToolCalls', { name: toolCall.name, depth });
283
+ const result = await this.terminalTools.executeToolCall(toolCall);
284
+ toolResults.push(result);
285
+ }
286
+
287
+ // 构建包含工具结果的新请求
288
+ const toolResultsMessage: ChatMessage = {
289
+ id: `tool_result_${Date.now()}`,
290
+ role: MessageRole.USER,
291
+ content: toolResults.map(r =>
292
+ `工具 ${r.tool_use_id} 结果:\n${r.content}`
293
+ ).join('\n\n'),
294
+ timestamp: new Date(),
295
+ metadata: { toolResults }
296
+ };
297
+
298
+ // 继续对话 - 仍然允许工具调用但递归处理
299
+ const followUpRequest: ChatRequest = {
300
+ ...originalRequest,
301
+ messages: [
302
+ ...originalRequest.messages,
303
+ response.message,
304
+ toolResultsMessage
305
+ ],
306
+ tools: this.terminalTools.getToolDefinitions()
307
+ };
308
+
309
+ // 发送后续请求
310
+ const followUpResponse = await provider.chat(followUpRequest);
311
+
312
+ // ===== 关键修复:如果 AI 回复太短,直接附加工具结果 =====
313
+ const minResponseLength = 50; // 如果回复少于50字符,认为AI没有正确展示结果
314
+ const toolResultsText = toolResults.map(r => r.content).join('\n\n');
315
+
316
+ if (followUpResponse.message.content.length < minResponseLength && toolResultsText.length > 0) {
317
+ this.logger.info('AI response too short, appending tool results directly', {
318
+ responseLength: followUpResponse.message.content.length,
319
+ toolResultsLength: toolResultsText.length
320
+ });
321
+
322
+ // 查找包含终端输出的工具结果
323
+ const terminalOutput = toolResults.find(r =>
324
+ r.content.includes('=== 终端输出 ===') ||
325
+ r.content.includes('✅ 命令已执行')
326
+ );
327
+
328
+ if (terminalOutput) {
329
+ followUpResponse.message.content =
330
+ followUpResponse.message.content + '\n\n' + terminalOutput.content;
331
+ } else {
332
+ // 附加所有工具结果
333
+ followUpResponse.message.content =
334
+ followUpResponse.message.content + '\n\n' + toolResultsText;
335
+ }
336
+ }
337
+
338
+ // 递归处理后续响应中的工具调用
339
+ return this.handleToolCalls(followUpRequest, followUpResponse, provider, depth + 1, maxDepth);
340
+ }
341
+
67
342
  /**
68
343
  * 生成命令
69
344
  */
@@ -151,7 +151,7 @@ export class AiProviderManagerService implements ProviderManager {
151
151
  * 验证所有提供商配置
152
152
  */
153
153
  async validateAllProviders(): Promise<{ name: string; valid: boolean; errors: string[] }[]> {
154
- const results = [];
154
+ const results: { name: string; valid: boolean; errors: string[] }[] = [];
155
155
 
156
156
  for (const [name, provider] of this.providers) {
157
157
  try {
@@ -177,7 +177,7 @@ export class AiProviderManagerService implements ProviderManager {
177
177
  * 检查所有提供商健康状态
178
178
  */
179
179
  async checkAllProvidersHealth(): Promise<{ provider: string; status: string; latency?: number }[]> {
180
- const results = [];
180
+ const results: { provider: string; status: string; latency?: number }[] = [];
181
181
 
182
182
  for (const [name, provider] of this.providers) {
183
183
  try {