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.
- package/.editorconfig +18 -0
- package/README.md +40 -10
- package/dist/index.js +1 -1
- package/package.json +5 -3
- package/src/components/chat/ai-sidebar.component.scss +220 -9
- package/src/components/chat/ai-sidebar.component.ts +379 -29
- package/src/components/chat/chat-input.component.ts +36 -4
- package/src/components/chat/chat-interface.component.ts +225 -5
- package/src/components/chat/chat-message.component.ts +6 -1
- package/src/components/settings/context-settings.component.ts +91 -91
- package/src/components/terminal/ai-toolbar-button.component.ts +4 -2
- package/src/components/terminal/command-suggestion.component.ts +148 -6
- package/src/index.ts +81 -19
- package/src/providers/tabby/ai-toolbar-button.provider.ts +7 -3
- package/src/services/chat/ai-sidebar.service.ts +448 -410
- package/src/services/chat/chat-session.service.ts +36 -12
- package/src/services/context/compaction.ts +110 -134
- package/src/services/context/manager.ts +27 -7
- package/src/services/context/memory.ts +17 -33
- package/src/services/context/summary.service.ts +136 -0
- package/src/services/core/ai-assistant.service.ts +1060 -37
- package/src/services/core/ai-provider-manager.service.ts +154 -25
- package/src/services/core/checkpoint.service.ts +218 -18
- package/src/services/core/toast.service.ts +106 -106
- package/src/services/providers/anthropic-provider.service.ts +126 -30
- package/src/services/providers/base-provider.service.ts +90 -7
- package/src/services/providers/glm-provider.service.ts +151 -38
- package/src/services/providers/minimax-provider.service.ts +55 -40
- package/src/services/providers/ollama-provider.service.ts +117 -28
- package/src/services/providers/openai-compatible.service.ts +164 -34
- package/src/services/providers/openai-provider.service.ts +169 -34
- package/src/services/providers/vllm-provider.service.ts +116 -28
- package/src/services/terminal/terminal-context.service.ts +265 -5
- package/src/services/terminal/terminal-manager.service.ts +845 -748
- package/src/services/terminal/terminal-tools.service.ts +612 -441
- package/src/types/ai.types.ts +156 -3
- package/src/utils/cost.utils.ts +249 -0
- package/src/utils/validation.utils.ts +306 -2
- package/dist/index.js.LICENSE.txt +0 -18
- package/src/services/terminal/command-analyzer.service.ts +0 -43
- package/src/services/terminal/context-menu.service.ts +0 -45
- package/src/services/terminal/hotkey.service.ts +0 -53
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import { Injectable } from '@angular/core';
|
|
2
|
-
import { Subject } from 'rxjs';
|
|
3
|
-
|
|
4
|
-
export interface ToastMessage {
|
|
5
|
-
id: string;
|
|
6
|
-
type: 'success' | 'error' | 'warning' | 'info';
|
|
7
|
-
message: string;
|
|
8
|
-
duration?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@Injectable({ providedIn: 'root' })
|
|
12
|
-
export class ToastService {
|
|
13
|
-
private toastSubject = new Subject<ToastMessage>();
|
|
14
|
-
toast$ = this.toastSubject.asObservable();
|
|
15
|
-
|
|
16
|
-
show(type: ToastMessage['type'], message: string, duration = 3000): void {
|
|
17
|
-
const id = `toast-${Date.now()}`;
|
|
18
|
-
|
|
19
|
-
// 确保容器存在
|
|
20
|
-
let container = document.getElementById('ai-toast-container');
|
|
21
|
-
if (!container) {
|
|
22
|
-
container = document.createElement('div');
|
|
23
|
-
container.id = 'ai-toast-container';
|
|
24
|
-
container.className = 'ai-toast-container';
|
|
25
|
-
container.style.cssText = `
|
|
26
|
-
position: fixed;
|
|
27
|
-
bottom: 20px;
|
|
28
|
-
right: 20px;
|
|
29
|
-
z-index: 99999;
|
|
30
|
-
display: flex;
|
|
31
|
-
flex-direction: column;
|
|
32
|
-
gap: 10px;
|
|
33
|
-
pointer-events: none;
|
|
34
|
-
`;
|
|
35
|
-
document.body.appendChild(container);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 创建 Toast 元素
|
|
39
|
-
const toast = document.createElement('div');
|
|
40
|
-
toast.id = id;
|
|
41
|
-
toast.className = `ai-toast ai-toast-${type}`;
|
|
42
|
-
toast.style.cssText = `
|
|
43
|
-
padding: 12px 16px;
|
|
44
|
-
border-radius: 8px;
|
|
45
|
-
color: white;
|
|
46
|
-
font-size: 14px;
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
gap: 8px;
|
|
50
|
-
animation: toastSlideIn 0.3s ease;
|
|
51
|
-
cursor: pointer;
|
|
52
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
53
|
-
pointer-events: auto;
|
|
54
|
-
min-width: 200px;
|
|
55
|
-
max-width: 350px;
|
|
56
|
-
${type === 'success' ? 'background: linear-gradient(135deg, #22c55e, #16a34a);' : ''}
|
|
57
|
-
${type === 'error' ? 'background: linear-gradient(135deg, #ef4444, #dc2626);' : ''}
|
|
58
|
-
${type === 'warning' ? 'background: linear-gradient(135deg, #f59e0b, #d97706);' : ''}
|
|
59
|
-
${type === 'info' ? 'background: linear-gradient(135deg, #3b82f6, #2563eb);' : ''}
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
const icon = type === 'success' ? '✓' : type === 'error' ? '✗' : type === 'warning' ? '⚠' : 'ℹ';
|
|
63
|
-
toast.innerHTML = `<span style="font-size: 16px;">${icon}</span><span>${message}</span>`;
|
|
64
|
-
|
|
65
|
-
toast.onclick = () => {
|
|
66
|
-
this.removeToast(toast);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
container.appendChild(toast);
|
|
70
|
-
|
|
71
|
-
// 自动消失
|
|
72
|
-
setTimeout(() => {
|
|
73
|
-
this.removeToast(toast);
|
|
74
|
-
}, duration);
|
|
75
|
-
|
|
76
|
-
// 发射事件(兼容现有订阅)
|
|
77
|
-
this.toastSubject.next({ id, type, message, duration });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private removeToast(toast: HTMLElement): void {
|
|
81
|
-
if (toast && toast.parentNode) {
|
|
82
|
-
toast.style.animation = 'toastSlideOut 0.3s ease forwards';
|
|
83
|
-
setTimeout(() => {
|
|
84
|
-
if (toast.parentNode) {
|
|
85
|
-
toast.remove();
|
|
86
|
-
}
|
|
87
|
-
}, 300);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
success(message: string, duration = 3000): void {
|
|
92
|
-
this.show('success', message, duration);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
error(message: string, duration = 5000): void {
|
|
96
|
-
this.show('error', message, duration);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
warning(message: string, duration = 4000): void {
|
|
100
|
-
this.show('warning', message, duration);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
info(message: string, duration = 3000): void {
|
|
104
|
-
this.show('info', message, duration);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
export interface ToastMessage {
|
|
5
|
+
id: string;
|
|
6
|
+
type: 'success' | 'error' | 'warning' | 'info';
|
|
7
|
+
message: string;
|
|
8
|
+
duration?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Injectable({ providedIn: 'root' })
|
|
12
|
+
export class ToastService {
|
|
13
|
+
private toastSubject = new Subject<ToastMessage>();
|
|
14
|
+
toast$ = this.toastSubject.asObservable();
|
|
15
|
+
|
|
16
|
+
show(type: ToastMessage['type'], message: string, duration = 3000): void {
|
|
17
|
+
const id = `toast-${Date.now()}`;
|
|
18
|
+
|
|
19
|
+
// 确保容器存在
|
|
20
|
+
let container = document.getElementById('ai-toast-container');
|
|
21
|
+
if (!container) {
|
|
22
|
+
container = document.createElement('div');
|
|
23
|
+
container.id = 'ai-toast-container';
|
|
24
|
+
container.className = 'ai-toast-container';
|
|
25
|
+
container.style.cssText = `
|
|
26
|
+
position: fixed;
|
|
27
|
+
bottom: 20px;
|
|
28
|
+
right: 20px;
|
|
29
|
+
z-index: 99999;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 10px;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
`;
|
|
35
|
+
document.body.appendChild(container);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 创建 Toast 元素
|
|
39
|
+
const toast = document.createElement('div');
|
|
40
|
+
toast.id = id;
|
|
41
|
+
toast.className = `ai-toast ai-toast-${type}`;
|
|
42
|
+
toast.style.cssText = `
|
|
43
|
+
padding: 12px 16px;
|
|
44
|
+
border-radius: 8px;
|
|
45
|
+
color: white;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 8px;
|
|
50
|
+
animation: toastSlideIn 0.3s ease;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
53
|
+
pointer-events: auto;
|
|
54
|
+
min-width: 200px;
|
|
55
|
+
max-width: 350px;
|
|
56
|
+
${type === 'success' ? 'background: linear-gradient(135deg, #22c55e, #16a34a);' : ''}
|
|
57
|
+
${type === 'error' ? 'background: linear-gradient(135deg, #ef4444, #dc2626);' : ''}
|
|
58
|
+
${type === 'warning' ? 'background: linear-gradient(135deg, #f59e0b, #d97706);' : ''}
|
|
59
|
+
${type === 'info' ? 'background: linear-gradient(135deg, #3b82f6, #2563eb);' : ''}
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const icon = type === 'success' ? '✓' : type === 'error' ? '✗' : type === 'warning' ? '⚠' : 'ℹ';
|
|
63
|
+
toast.innerHTML = `<span style="font-size: 16px;">${icon}</span><span>${message}</span>`;
|
|
64
|
+
|
|
65
|
+
toast.onclick = () => {
|
|
66
|
+
this.removeToast(toast);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
container.appendChild(toast);
|
|
70
|
+
|
|
71
|
+
// 自动消失
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
this.removeToast(toast);
|
|
74
|
+
}, duration);
|
|
75
|
+
|
|
76
|
+
// 发射事件(兼容现有订阅)
|
|
77
|
+
this.toastSubject.next({ id, type, message, duration });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private removeToast(toast: HTMLElement): void {
|
|
81
|
+
if (toast && toast.parentNode) {
|
|
82
|
+
toast.style.animation = 'toastSlideOut 0.3s ease forwards';
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
if (toast.parentNode) {
|
|
85
|
+
toast.remove();
|
|
86
|
+
}
|
|
87
|
+
}, 300);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
success(message: string, duration = 3000): void {
|
|
92
|
+
this.show('success', message, duration);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
error(message: string, duration = 5000): void {
|
|
96
|
+
this.show('error', message, duration);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
warning(message: string, duration = 4000): void {
|
|
100
|
+
this.show('warning', message, duration);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
info(message: string, duration = 3000): void {
|
|
104
|
+
this.show('info', message, duration);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
-
import { Observable,
|
|
2
|
+
import { Observable, Observer } from 'rxjs';
|
|
3
3
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
4
4
|
import { BaseAiProvider } from './base-provider.service';
|
|
5
|
-
import { ProviderCapability,
|
|
6
|
-
import { ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, AnalysisRequest, AnalysisResponse, MessageRole } from '../../types/ai.types';
|
|
5
|
+
import { ProviderCapability, ValidationResult } from '../../types/provider.types';
|
|
6
|
+
import { ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, AnalysisRequest, AnalysisResponse, MessageRole, StreamEvent } from '../../types/ai.types';
|
|
7
7
|
import { LoggerService } from '../core/logger.service';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -93,11 +93,119 @@ export class AnthropicProviderService extends BaseAiProvider {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* 流式聊天功能 -
|
|
96
|
+
* 流式聊天功能 - 支持工具调用事件
|
|
97
97
|
*/
|
|
98
|
-
chatStream(request: ChatRequest): Observable<
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
chatStream(request: ChatRequest): Observable<StreamEvent> {
|
|
99
|
+
return new Observable<StreamEvent>((subscriber: Observer<StreamEvent>) => {
|
|
100
|
+
if (!this.client) {
|
|
101
|
+
const error = new Error('Anthropic client not initialized');
|
|
102
|
+
subscriber.next({ type: 'error', error: error.message });
|
|
103
|
+
subscriber.error(error);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let currentToolId = '';
|
|
108
|
+
let currentToolName = '';
|
|
109
|
+
let currentToolInput = '';
|
|
110
|
+
let fullContent = '';
|
|
111
|
+
|
|
112
|
+
const abortController = new AbortController();
|
|
113
|
+
|
|
114
|
+
const runStream = async () => {
|
|
115
|
+
try {
|
|
116
|
+
const stream = await this.client!.messages.stream({
|
|
117
|
+
model: this.config?.model || 'claude-3-sonnet',
|
|
118
|
+
max_tokens: request.maxTokens || 1000,
|
|
119
|
+
system: request.systemPrompt || this.getDefaultSystemPrompt(),
|
|
120
|
+
messages: this.transformMessages(request.messages),
|
|
121
|
+
temperature: request.temperature || 1.0,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
for await (const event of stream) {
|
|
125
|
+
if (abortController.signal.aborted) break;
|
|
126
|
+
|
|
127
|
+
const eventAny = event as any;
|
|
128
|
+
this.logger.debug('Stream event', { type: event.type });
|
|
129
|
+
|
|
130
|
+
// 处理文本增量
|
|
131
|
+
if (event.type === 'content_block_delta' && eventAny.delta?.type === 'text_delta') {
|
|
132
|
+
const textDelta = eventAny.delta.text;
|
|
133
|
+
fullContent += textDelta;
|
|
134
|
+
subscriber.next({
|
|
135
|
+
type: 'text_delta',
|
|
136
|
+
textDelta
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// 处理工具调用开始
|
|
140
|
+
else if (event.type === 'content_block_start' && eventAny.content_block?.type === 'tool_use') {
|
|
141
|
+
currentToolId = eventAny.content_block.id || `tool_${Date.now()}`;
|
|
142
|
+
currentToolName = eventAny.content_block.name;
|
|
143
|
+
currentToolInput = '';
|
|
144
|
+
subscriber.next({
|
|
145
|
+
type: 'tool_use_start',
|
|
146
|
+
toolCall: {
|
|
147
|
+
id: currentToolId,
|
|
148
|
+
name: currentToolName,
|
|
149
|
+
input: {}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
this.logger.debug('Stream event', { type: 'tool_use_start', name: currentToolName });
|
|
153
|
+
}
|
|
154
|
+
// 处理工具调用参数
|
|
155
|
+
else if (event.type === 'content_block_delta' && eventAny.delta?.type === 'input_json_delta') {
|
|
156
|
+
currentToolInput += eventAny.delta.partial_json || '';
|
|
157
|
+
}
|
|
158
|
+
// 处理工具调用结束
|
|
159
|
+
else if (event.type === 'content_block_stop') {
|
|
160
|
+
if (currentToolId && currentToolName) {
|
|
161
|
+
let parsedInput = {};
|
|
162
|
+
try {
|
|
163
|
+
parsedInput = JSON.parse(currentToolInput || '{}');
|
|
164
|
+
} catch (e) {
|
|
165
|
+
// 使用原始输入
|
|
166
|
+
}
|
|
167
|
+
subscriber.next({
|
|
168
|
+
type: 'tool_use_end',
|
|
169
|
+
toolCall: {
|
|
170
|
+
id: currentToolId,
|
|
171
|
+
name: currentToolName,
|
|
172
|
+
input: parsedInput
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
this.logger.debug('Stream event', { type: 'tool_use_end', name: currentToolName });
|
|
176
|
+
currentToolId = '';
|
|
177
|
+
currentToolName = '';
|
|
178
|
+
currentToolInput = '';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
subscriber.next({
|
|
184
|
+
type: 'message_end',
|
|
185
|
+
message: {
|
|
186
|
+
id: this.generateId(),
|
|
187
|
+
role: MessageRole.ASSISTANT,
|
|
188
|
+
content: fullContent,
|
|
189
|
+
timestamp: new Date()
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
this.logger.debug('Stream event', { type: 'message_end', contentLength: fullContent.length });
|
|
193
|
+
subscriber.complete();
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if ((error as any).name !== 'AbortError') {
|
|
197
|
+
const errorMessage = `Anthropic stream failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
198
|
+
this.logger.error('Stream error', error);
|
|
199
|
+
subscriber.next({ type: 'error', error: errorMessage });
|
|
200
|
+
subscriber.error(new Error(errorMessage));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
runStream();
|
|
206
|
+
|
|
207
|
+
return () => abortController.abort();
|
|
208
|
+
});
|
|
101
209
|
}
|
|
102
210
|
|
|
103
211
|
async generateCommand(request: CommandRequest): Promise<CommandResponse> {
|
|
@@ -160,31 +268,19 @@ export class AnthropicProviderService extends BaseAiProvider {
|
|
|
160
268
|
return this.parseAnalysisResponse(response.message.content);
|
|
161
269
|
}
|
|
162
270
|
|
|
163
|
-
async
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const response = await this.client.messages.create({
|
|
170
|
-
model: this.config?.model || 'claude-3-sonnet',
|
|
171
|
-
max_tokens: 1,
|
|
172
|
-
messages: [
|
|
173
|
-
{
|
|
174
|
-
role: 'user',
|
|
175
|
-
content: 'Hi'
|
|
176
|
-
}
|
|
177
|
-
]
|
|
178
|
-
});
|
|
271
|
+
protected async sendTestRequest(request: ChatRequest): Promise<ChatResponse> {
|
|
272
|
+
if (!this.client) {
|
|
273
|
+
throw new Error('Anthropic client not initialized');
|
|
274
|
+
}
|
|
179
275
|
|
|
180
|
-
|
|
181
|
-
|
|
276
|
+
const response = await this.client.messages.create({
|
|
277
|
+
model: this.config?.model || 'claude-3-sonnet',
|
|
278
|
+
max_tokens: request.maxTokens || 1,
|
|
279
|
+
messages: this.transformMessages(request.messages),
|
|
280
|
+
temperature: request.temperature || 0
|
|
281
|
+
});
|
|
182
282
|
|
|
183
|
-
|
|
184
|
-
this.logger.error('Anthropic health check failed', error);
|
|
185
|
-
this.lastHealthCheck = { status: HealthStatus.UNHEALTHY, timestamp: new Date() };
|
|
186
|
-
return HealthStatus.UNHEALTHY;
|
|
187
|
-
}
|
|
283
|
+
return this.transformChatResponse(response);
|
|
188
284
|
}
|
|
189
285
|
|
|
190
286
|
validateConfig(): ValidationResult {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
-
import { Observable } from 'rxjs';
|
|
2
|
+
import { Observable, of } from 'rxjs';
|
|
3
3
|
import { IBaseAiProvider, ProviderConfig, AuthConfig, ProviderCapability, HealthStatus, ValidationResult, ProviderInfo, PROVIDER_DEFAULTS } from '../../types/provider.types';
|
|
4
|
-
import { ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, AnalysisRequest, AnalysisResponse, StreamEvent } from '../../types/ai.types';
|
|
4
|
+
import { ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, AnalysisRequest, AnalysisResponse, StreamEvent, MessageRole } from '../../types/ai.types';
|
|
5
5
|
import { LoggerService } from '../core/logger.service';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -60,16 +60,22 @@ export abstract class BaseAiProvider implements IBaseAiProvider {
|
|
|
60
60
|
*/
|
|
61
61
|
async healthCheck(): Promise<HealthStatus> {
|
|
62
62
|
try {
|
|
63
|
-
//
|
|
63
|
+
// 1. 验证配置
|
|
64
64
|
const validation = this.validateConfig();
|
|
65
65
|
if (!validation.valid) {
|
|
66
66
|
this.lastHealthCheck = { status: HealthStatus.UNHEALTHY, timestamp: new Date() };
|
|
67
67
|
return HealthStatus.UNHEALTHY;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
// 2. 执行网络健康检查
|
|
71
|
+
const networkStatus = await this.performNetworkHealthCheck();
|
|
72
|
+
|
|
73
|
+
this.lastHealthCheck = {
|
|
74
|
+
status: networkStatus,
|
|
75
|
+
timestamp: new Date()
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return networkStatus;
|
|
73
79
|
|
|
74
80
|
} catch (error) {
|
|
75
81
|
this.logger.error(`Health check failed for ${this.name}`, error);
|
|
@@ -78,6 +84,64 @@ export abstract class BaseAiProvider implements IBaseAiProvider {
|
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* 执行网络健康检查 - 发送测试请求
|
|
89
|
+
*/
|
|
90
|
+
protected async performNetworkHealthCheck(): Promise<HealthStatus> {
|
|
91
|
+
try {
|
|
92
|
+
// 发送一个简单的测试请求
|
|
93
|
+
const testRequest: ChatRequest = {
|
|
94
|
+
messages: [{
|
|
95
|
+
id: 'health-check',
|
|
96
|
+
role: MessageRole.USER,
|
|
97
|
+
content: 'Hi',
|
|
98
|
+
timestamp: new Date()
|
|
99
|
+
}],
|
|
100
|
+
maxTokens: 1,
|
|
101
|
+
temperature: 0
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const response = await this.withRetry(() => this.sendTestRequest(testRequest));
|
|
105
|
+
|
|
106
|
+
if (this.isSuccessfulResponse(response)) {
|
|
107
|
+
return HealthStatus.HEALTHY;
|
|
108
|
+
} else {
|
|
109
|
+
this.logger.warn(`Health check returned unsuccessful response for ${this.name}`, {
|
|
110
|
+
response: this.sanitizeResponse(response)
|
|
111
|
+
});
|
|
112
|
+
return HealthStatus.DEGRADED;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
|
|
118
|
+
// 根据错误类型判断状态
|
|
119
|
+
if (errorMessage.includes('timeout') || errorMessage.includes('Timed out')) {
|
|
120
|
+
this.logger.warn(`Health check timed out for ${this.name}`);
|
|
121
|
+
return HealthStatus.DEGRADED;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (errorMessage.includes('401') || errorMessage.includes('Unauthorized') ||
|
|
125
|
+
errorMessage.includes('invalid') || errorMessage.includes('API key')) {
|
|
126
|
+
this.logger.warn(`Health check authentication failed for ${this.name}`);
|
|
127
|
+
return HealthStatus.UNHEALTHY;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (errorMessage.includes('429') || errorMessage.includes('rate limit')) {
|
|
131
|
+
this.logger.warn(`Health check rate limited for ${this.name}`);
|
|
132
|
+
return HealthStatus.DEGRADED;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.logger.error(`Health check network error for ${this.name}`, error);
|
|
136
|
+
return HealthStatus.UNHEALTHY;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 发送测试请求 - 子类必须实现
|
|
142
|
+
*/
|
|
143
|
+
protected abstract sendTestRequest(request: ChatRequest): Promise<ChatResponse>;
|
|
144
|
+
|
|
81
145
|
/**
|
|
82
146
|
* 验证配置 - 默认实现,子类可以重写
|
|
83
147
|
*/
|
|
@@ -194,7 +258,10 @@ export abstract class BaseAiProvider implements IBaseAiProvider {
|
|
|
194
258
|
break;
|
|
195
259
|
|
|
196
260
|
case 'oauth':
|
|
197
|
-
//
|
|
261
|
+
// OAuth 认证预留
|
|
262
|
+
// 当前无提供商使用此认证方式
|
|
263
|
+
// 如需实现,可参考 OAuth 2.0 Authorization Code Flow
|
|
264
|
+
this.logger.debug('OAuth authentication not implemented');
|
|
198
265
|
break;
|
|
199
266
|
}
|
|
200
267
|
|
|
@@ -574,6 +641,22 @@ export abstract class BaseAiProvider implements IBaseAiProvider {
|
|
|
574
641
|
5. 如果不确定当前目录或终端状态,先使用 get_terminal_cwd 或 get_terminal_list 获取信息
|
|
575
642
|
6. **永远不要假装执行了操作,必须真正调用工具**
|
|
576
643
|
|
|
644
|
+
## 命令执行策略
|
|
645
|
+
### 快速命令(无需额外等待)
|
|
646
|
+
- dir, ls, cd, pwd, echo, cat, type, mkdir, rm, copy, move
|
|
647
|
+
- 这些命令通常在 500ms 内完成
|
|
648
|
+
|
|
649
|
+
### 慢速命令(需要等待完整输出)
|
|
650
|
+
- systeminfo, ipconfig, netstat: 等待 3-8 秒
|
|
651
|
+
- npm, yarn, pip, docker: 等待 5-10 秒
|
|
652
|
+
- git: 等待 3 秒以上
|
|
653
|
+
- ping, tracert: 可能需要 10+ 秒
|
|
654
|
+
|
|
655
|
+
**对于慢速命令**:
|
|
656
|
+
1. 执行命令后,系统会自动等待
|
|
657
|
+
2. 如果输出不完整,可以再次调用 read_terminal_output 获取更新的内容
|
|
658
|
+
3. **不要猜测或假设命令输出,始终以实际读取到的输出为准**
|
|
659
|
+
|
|
577
660
|
## 示例
|
|
578
661
|
用户:"查看当前目录的文件"
|
|
579
662
|
正确做法:调用 write_to_terminal 工具,参数 { "command": "dir", "execute": true }
|