- 新增对话服务(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 组件展示问题 - 提供答案收集和提交机制
225 lines
5.7 KiB
TypeScript
225 lines
5.7 KiB
TypeScript
/**
|
||
* 对话服务
|
||
* 整合 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();
|