feat: 实现核心服务层
- 新增对话服务(src/services/dialogService.ts) - 封装完整的对话生命周期管理 - 集成 SSE 流式响应处理 - 支持对话创建、消息发送、对话中止 - 提供统一的事件回调接口 - 新增工具执行器(src/services/toolExecutor.ts) - 实现前端工具调用框架 - 支持 readFile、writeFile、listFiles、executeCommand 等工具 - 提供工具执行结果的标准化返回 - 集成 VSCode API 进行文件和终端操作 - 新增用户交互处理(src/services/userInteraction.ts) - 实现 AI 向用户提问功能(AskUser) - 支持 input、confirm、quickPick 等交互类型 - 使用 VSCode 原生 UI 组件展示问题 - 提供答案收集和提交机制
This commit is contained in:
224
src/services/dialogService.ts
Normal file
224
src/services/dialogService.ts
Normal file
@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 对话服务
|
||||
* 整合 SSE 通信、工具执行、用户交互
|
||||
*/
|
||||
import * as vscode from 'vscode';
|
||||
import { startStreamDialog, generateTaskId, SSEController, SSECallbacks } from './sseHandler';
|
||||
import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor';
|
||||
import { userInteractionManager } from './userInteraction';
|
||||
import { getConfig } from '../config/settings';
|
||||
import type { DialogRequest, ToolCallRequest, AskUserEvent } from '../types/api';
|
||||
|
||||
/**
|
||||
* 对话回调接口
|
||||
*/
|
||||
export interface DialogCallbacks {
|
||||
/** 收到文本(可能多次调用,流式) */
|
||||
onText?: (text: string, isStreaming: boolean) => void;
|
||||
/** 工具开始执行 */
|
||||
onToolStart?: (toolName: string) => void;
|
||||
/** 工具执行完成 */
|
||||
onToolComplete?: (toolName: string, result: string) => void;
|
||||
/** 工具执行错误 */
|
||||
onToolError?: (toolName: string, error: string) => void;
|
||||
/** 显示问题(ask_user) */
|
||||
onQuestion?: (askId: string, question: string, options: string[]) => void;
|
||||
/** 对话完成 */
|
||||
onComplete?: () => void;
|
||||
/** 错误 */
|
||||
onError?: (message: string) => void;
|
||||
/** 通知消息 */
|
||||
onNotification?: (message: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话会话
|
||||
*/
|
||||
export class DialogSession {
|
||||
private taskId: string;
|
||||
private sseController: SSEController | null = null;
|
||||
private toolContext: ToolExecutorContext;
|
||||
private accumulatedText = '';
|
||||
private isActive = false;
|
||||
|
||||
constructor(extensionPath: string) {
|
||||
this.taskId = generateTaskId();
|
||||
this.toolContext = createToolExecutorContext(extensionPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务ID
|
||||
*/
|
||||
getTaskId(): string {
|
||||
return this.taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否活跃
|
||||
*/
|
||||
get active(): boolean {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息并开始流式对话
|
||||
*/
|
||||
async sendMessage(
|
||||
message: string,
|
||||
callbacks: DialogCallbacks
|
||||
): Promise<void> {
|
||||
if (this.isActive) {
|
||||
callbacks.onError?.('当前有对话正在进行中');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isActive = true;
|
||||
this.accumulatedText = '';
|
||||
|
||||
const config = getConfig();
|
||||
const request: DialogRequest = {
|
||||
taskId: this.taskId,
|
||||
message,
|
||||
userId: config.userId,
|
||||
toolMode: 'AGENT'
|
||||
};
|
||||
|
||||
const sseCallbacks: SSECallbacks = {
|
||||
onTextDelta: (data) => {
|
||||
this.accumulatedText += data.text;
|
||||
console.log('[DialogSession] onTextDelta, 累积文本长度:', this.accumulatedText.length);
|
||||
callbacks.onText?.(this.accumulatedText, true);
|
||||
},
|
||||
|
||||
onToolCall: async (data: ToolCallRequest) => {
|
||||
callbacks.onToolStart?.(data.params.name);
|
||||
try {
|
||||
await executeToolCall(data, this.toolContext);
|
||||
callbacks.onToolComplete?.(data.params.name, '执行完成');
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : '未知错误';
|
||||
callbacks.onToolError?.(data.params.name, errorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
onToolStart: (data) => {
|
||||
callbacks.onToolStart?.(data.tool_name);
|
||||
},
|
||||
|
||||
onToolComplete: (data) => {
|
||||
callbacks.onToolComplete?.(data.tool_name, data.result);
|
||||
},
|
||||
|
||||
onToolError: (data) => {
|
||||
callbacks.onToolError?.(data.tool_name, data.error);
|
||||
},
|
||||
|
||||
onAskUser: async (data: AskUserEvent) => {
|
||||
callbacks.onQuestion?.(data.askId, data.question, data.options);
|
||||
try {
|
||||
await userInteractionManager.handleAskUser(data, this.taskId);
|
||||
} catch (error) {
|
||||
console.error('[DialogSession] 处理用户问题失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
onComplete: (data) => {
|
||||
this.isActive = false;
|
||||
// 发送最终文本(非流式)
|
||||
if (this.accumulatedText) {
|
||||
callbacks.onText?.(this.accumulatedText, false);
|
||||
}
|
||||
callbacks.onComplete?.();
|
||||
},
|
||||
|
||||
onError: (data) => {
|
||||
this.isActive = false;
|
||||
callbacks.onError?.(data.message);
|
||||
},
|
||||
|
||||
onWarning: (data) => {
|
||||
callbacks.onNotification?.(`⚠️ ${data.message}`);
|
||||
},
|
||||
|
||||
onNotification: (data) => {
|
||||
callbacks.onNotification?.(data.message);
|
||||
},
|
||||
|
||||
onOpen: () => {
|
||||
console.log('[DialogSession] SSE 连接已建立');
|
||||
},
|
||||
|
||||
onClose: () => {
|
||||
console.log('[DialogSession] SSE 连接已关闭');
|
||||
this.isActive = false;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.sseController = await startStreamDialog(request, sseCallbacks);
|
||||
} catch (error) {
|
||||
this.isActive = false;
|
||||
const errorMsg = error instanceof Error ? error.message : '连接失败';
|
||||
callbacks.onError?.(errorMsg);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中止当前对话
|
||||
*/
|
||||
abort(): void {
|
||||
if (this.sseController) {
|
||||
this.sseController.abort();
|
||||
this.sseController = null;
|
||||
}
|
||||
this.isActive = false;
|
||||
userInteractionManager.cancelAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交用户回答
|
||||
*/
|
||||
async submitAnswer(
|
||||
askId: string,
|
||||
selected?: string[],
|
||||
customInput?: string
|
||||
): Promise<void> {
|
||||
await userInteractionManager.receiveAnswer(askId, selected, customInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局对话会话管理
|
||||
*/
|
||||
class DialogManager {
|
||||
private currentSession: DialogSession | null = null;
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
*/
|
||||
createSession(extensionPath: string): DialogSession {
|
||||
// 如果有活跃会话,先中止
|
||||
if (this.currentSession?.active) {
|
||||
this.currentSession.abort();
|
||||
}
|
||||
this.currentSession = new DialogSession(extensionPath);
|
||||
return this.currentSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话
|
||||
*/
|
||||
getCurrentSession(): DialogSession | null {
|
||||
return this.currentSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 中止当前会话
|
||||
*/
|
||||
abortCurrentSession(): void {
|
||||
this.currentSession?.abort();
|
||||
}
|
||||
}
|
||||
|
||||
export const dialogManager = new DialogManager();
|
||||
Reference in New Issue
Block a user