feat: 模式传递和 API 调用

- dialogService 接收并传递 mode 参数
- apiClient 构造带 mode 的请求
- messageHandler 从 WebView 消息获取 mode
This commit is contained in:
XiaoFeng
2025-12-30 20:42:28 +08:00
parent 2aff54de74
commit e77194628a
3 changed files with 265 additions and 14 deletions

View File

@ -6,7 +6,7 @@ import * as https from 'https';
import * as http from 'http';
import { URL } from 'url';
import { getApiUrl, getConfig } from '../config/settings';
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse } from '../types/api';
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse } from '../types/api';
/**
* HTTP 请求选项
@ -103,6 +103,18 @@ export async function submitAnswer(answer: AnswerRequest): Promise<AnswerRespons
});
}
/**
* 提交工具确认响应Ask 模式)
* POST /api/tool/confirm
*/
export async function submitToolConfirm(response: ToolConfirmResponse): Promise<ToolResultResponse> {
console.log(`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`);
return request<ToolResultResponse>('/api/tool/confirm', {
method: 'POST',
body: response
});
}
/**
* 健康检查
* GET /api/dialog/health

View File

@ -7,13 +7,14 @@ import { startStreamDialog, generateTaskId, SSEController, SSECallbacks } from '
import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor';
import { userInteractionManager } from './userInteraction';
import { getConfig } from '../config/settings';
import type { DialogRequest, ToolCallRequest, AskUserEvent } from '../types/api';
import type { DialogRequest, ToolCallRequest, AskUserEvent, RunMode, ToolConfirmEvent, PlanConfirmEvent } from '../types/api';
import { submitToolConfirm, submitAnswer } from './apiClient';
/**
* 消息段落类型
*/
export interface MessageSegment {
type: 'text' | 'tool' | 'question' | 'agent';
type: 'text' | 'tool' | 'question' | 'agent' | 'plan';
content?: string;
toolName?: string;
toolStatus?: 'running' | 'success' | 'error';
@ -26,6 +27,10 @@ export interface MessageSegment {
agentName?: string;
agentStatus?: 'running' | 'completed' | 'error';
agentSteps?: AgentStep[];
// 计划相关字段
planTitle?: string;
planSteps?: string[];
planSummary?: string;
}
/**
@ -51,6 +56,10 @@ export interface DialogCallbacks {
onToolComplete?: (toolName: string, result: string) => void;
/** 工具执行错误 */
onToolError?: (toolName: string, error: string) => void;
/** 工具确认请求Ask 模式) */
onToolConfirm?: (confirmId: number, toolName: string, toolInput: Record<string, unknown>) => void;
/** 计划确认请求Plan 模式) */
onPlanConfirm?: (confirmId: number, title: string, steps: string[], summary: string) => void;
/** 显示问题ask_user */
onQuestion?: (askId: string, question: string, options: string[]) => void;
/** 实时更新段落(流式过程中) */
@ -75,8 +84,9 @@ export class DialogSession {
private segments: MessageSegment[] = [];
private currentTextSegment: MessageSegment | null = null;
constructor(extensionPath: string) {
this.taskId = generateTaskId();
constructor(extensionPath: string, existingTaskId?: string) {
// 支持复用现有 taskId用于 Plan 模式确认后继续执行)
this.taskId = existingTaskId || generateTaskId();
this.toolContext = createToolExecutorContext(extensionPath);
}
@ -142,12 +152,52 @@ export class DialogSession {
return this.isActive;
}
/**
* 获取工具操作描述(用于确认对话框)
*/
private getToolDescription(toolName: string, toolInput: Record<string, unknown>): string {
const lines: string[] = [];
switch (toolName) {
case 'file_write':
lines.push(`文件路径: ${toolInput.path || '未知'}`);
if (toolInput.content) {
const content = String(toolInput.content);
lines.push(`内容长度: ${content.length} 字符`);
lines.push(`内容预览: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`);
}
break;
case 'file_delete':
lines.push(`删除文件: ${toolInput.path || '未知'}`);
break;
case 'syntax_check':
lines.push('执行语法检查');
if (toolInput.code) {
const code = String(toolInput.code);
lines.push(`代码长度: ${code.length} 字符`);
}
break;
case 'simulation':
lines.push(`RTL文件: ${toolInput.rtlPath || '未知'}`);
lines.push(`TB文件: ${toolInput.tbPath || '未知'}`);
if (toolInput.duration) {
lines.push(`仿真时长: ${toolInput.duration}`);
}
break;
default:
lines.push(`参数: ${JSON.stringify(toolInput, null, 2)}`);
}
return lines.join('\n');
}
/**
* 发送消息并开始流式对话
*/
async sendMessage(
message: string,
callbacks: DialogCallbacks
callbacks: DialogCallbacks,
mode?: RunMode
): Promise<void> {
if (this.isActive) {
callbacks.onError?.('当前有对话正在进行中');
@ -164,7 +214,7 @@ export class DialogSession {
taskId: this.taskId,
message,
userId: config.userId,
toolMode: 'AGENT'
mode: mode || 'agent'
};
const sseCallbacks: SSECallbacks = {
@ -248,6 +298,72 @@ export class DialogSession {
callbacks.onSegmentUpdate?.(this.segments);
},
onToolConfirm: async (data: ToolConfirmEvent) => {
console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId);
// 调用回调通知 UI 显示确认对话框
callbacks.onToolConfirm?.(data.confirmId, data.toolName, data.toolInput);
// 使用 VSCode 快速选择框显示确认对话框
const toolDescription = this.getToolDescription(data.toolName, data.toolInput);
const result = await vscode.window.showWarningMessage(
`确认执行操作: ${data.toolName}`,
{ modal: true, detail: toolDescription },
'确认执行',
'取消'
);
const approved = result === '确认执行';
console.log('[DialogSession] 用户确认结果:', approved);
// 发送确认响应到后端
try {
await submitToolConfirm({
confirmId: data.confirmId,
taskId: this.taskId,
approved
});
} catch (error) {
console.error('[DialogSession] 发送确认响应失败:', error);
}
},
onPlanConfirm: async (data: PlanConfirmEvent) => {
console.log('[DialogSession] onPlanConfirm:', data.title);
// 结束当前文本段落
this.finalizeTextSegment();
const askId = `ask_${data.confirmId}`;
// 添加计划段落到聊天界面(包含 askId 用于响应)
this.segments.push({
type: 'plan',
askId: askId,
planTitle: data.title,
planSteps: data.steps,
planSummary: data.summary
});
// 实时发送段落更新
callbacks.onSegmentUpdate?.(this.segments);
// 注册问题到前端(类似 askUser以便用户回答时能找到
const planEvent = {
askId: askId,
question: `请确认执行计划:${data.title}`,
options: ['确认执行', '修改计划', '取消']
};
try {
await userInteractionManager.handleAskUser(planEvent as AskUserEvent, this.taskId);
} catch (error) {
console.error('[DialogSession] 处理计划确认失败:', error);
}
// 调用回调通知 UI
callbacks.onPlanConfirm?.(data.confirmId, data.title, data.steps, data.summary);
},
onAskUser: async (data: AskUserEvent) => {
this.finalizeTextSegment();
this.segments.push({
@ -402,13 +518,15 @@ class DialogManager {
/**
* 创建新会话
* @param extensionPath 扩展路径
* @param existingTaskId 可选,复用现有的 taskId用于 Plan 模式确认后继续执行)
*/
createSession(extensionPath: string): DialogSession {
createSession(extensionPath: string, existingTaskId?: string): DialogSession {
// 如果有活跃会话,先中止
if (this.currentSession?.active) {
this.currentSession.abort();
}
this.currentSession = new DialogSession(extensionPath);
this.currentSession = new DialogSession(extensionPath, existingTaskId);
return this.currentSession;
}