refactor(core,server): 统一模块职责并消除类型重复
- 统一 ToolStatus 类型(Core 导出,Server 引用) - 重命名 Server SessionManager 为 SessionMetadataManager - 扩展 Core PermissionContext 添加结构化字段 - 统一 Part 类型导出(Core 定义存储格式,Server 定义展示格式) - 简化 Message 格式(移除 MergedMessage,统一使用 Message) - 添加向后兼容的类型别名和 @deprecated 注释
This commit is contained in:
@@ -45,6 +45,8 @@ export type {
|
|||||||
Part,
|
Part,
|
||||||
PartType,
|
PartType,
|
||||||
ToolPart,
|
ToolPart,
|
||||||
|
TextPart,
|
||||||
|
ReasoningPart,
|
||||||
ToolStatus,
|
ToolStatus,
|
||||||
ToolState,
|
ToolState,
|
||||||
TodoItem,
|
TodoItem,
|
||||||
@@ -57,11 +59,14 @@ export type { UserInput, ChatResult } from './types/index.js';
|
|||||||
// Permission
|
// Permission
|
||||||
export { getPermissionManager } from './permission/index.js';
|
export { getPermissionManager } from './permission/index.js';
|
||||||
export type {
|
export type {
|
||||||
|
PermissionType,
|
||||||
PermissionContext,
|
PermissionContext,
|
||||||
PermissionDecision,
|
PermissionDecision,
|
||||||
PermissionCheckResult,
|
PermissionCheckResult,
|
||||||
FilePermissionContext,
|
FilePermissionContext,
|
||||||
|
FileOperation,
|
||||||
GitPermissionContext,
|
GitPermissionContext,
|
||||||
|
GitOperation,
|
||||||
WebPermissionContext,
|
WebPermissionContext,
|
||||||
} from './permission/index.js';
|
} from './permission/index.js';
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ export class BashPermissionChecker implements PermissionChecker {
|
|||||||
|
|
||||||
const decision = await this.askCallback({
|
const decision = await this.askCallback({
|
||||||
...ctx,
|
...ctx,
|
||||||
|
permissionType: 'bash',
|
||||||
externalPaths,
|
externalPaths,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -273,6 +274,7 @@ export class BashPermissionChecker implements PermissionChecker {
|
|||||||
|
|
||||||
const decision = await this.askCallback({
|
const decision = await this.askCallback({
|
||||||
...ctx,
|
...ctx,
|
||||||
|
permissionType: 'bash',
|
||||||
patterns: askPatterns,
|
patterns: askPatterns,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -262,10 +262,15 @@ export class FilePermissionChecker implements PermissionChecker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造兼容的 PermissionContext
|
// 构造带结构化信息的 PermissionContext
|
||||||
const permCtx: PermissionContext = {
|
const permCtx: PermissionContext = {
|
||||||
command: `${ctx.operation} ${ctx.path}`,
|
command: `${ctx.operation} ${ctx.path}`,
|
||||||
workdir: ctx.workdir,
|
workdir: ctx.workdir,
|
||||||
|
permissionType: 'file',
|
||||||
|
fileOperation: ctx.operation,
|
||||||
|
filePath: ctx.path,
|
||||||
|
newContent: ctx.newContent,
|
||||||
|
oldContent: ctx.oldContent,
|
||||||
patterns: [ctx.operation],
|
patterns: [ctx.operation],
|
||||||
externalPaths: this.isInProjectDirectory(absolutePath) ? undefined : [absolutePath],
|
externalPaths: this.isInProjectDirectory(absolutePath) ? undefined : [absolutePath],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -178,10 +178,16 @@ export class GitPermissionChecker implements PermissionChecker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用回调询问用户
|
// 调用回调询问用户(带结构化信息)
|
||||||
const decision = await this.askCallback({
|
const decision = await this.askCallback({
|
||||||
command: description,
|
command: description,
|
||||||
workdir: process.cwd(),
|
workdir: process.cwd(),
|
||||||
|
permissionType: 'git',
|
||||||
|
gitOperation: ctx.operation,
|
||||||
|
gitTarget: ctx.target,
|
||||||
|
gitRemote: ctx.remote,
|
||||||
|
gitForce: ctx.force,
|
||||||
|
gitMessage: ctx.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (decision.remember) {
|
if (decision.remember) {
|
||||||
|
|||||||
@@ -109,10 +109,12 @@ export class WebPermissionChecker implements PermissionChecker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用回调询问用户
|
// 调用回调询问用户(带结构化信息)
|
||||||
const decision = await this.askCallback({
|
const decision = await this.askCallback({
|
||||||
command: `web_search: ${query}`,
|
command: `web_search: ${query}`,
|
||||||
workdir: process.cwd(),
|
workdir: process.cwd(),
|
||||||
|
permissionType: 'web',
|
||||||
|
webQuery: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (decision.remember) {
|
if (decision.remember) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type {
|
|||||||
PermissionAction,
|
PermissionAction,
|
||||||
PermissionRule,
|
PermissionRule,
|
||||||
BashPermissionConfig,
|
BashPermissionConfig,
|
||||||
|
PermissionType,
|
||||||
PermissionContext,
|
PermissionContext,
|
||||||
PermissionCheckResult,
|
PermissionCheckResult,
|
||||||
PermissionDecision,
|
PermissionDecision,
|
||||||
|
|||||||
@@ -17,12 +17,35 @@ export interface BashPermissionConfig {
|
|||||||
default: PermissionAction;
|
default: PermissionAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 权限类型
|
||||||
|
export type PermissionType = 'bash' | 'file' | 'git' | 'web';
|
||||||
|
|
||||||
// 权限请求上下文
|
// 权限请求上下文
|
||||||
export interface PermissionContext {
|
export interface PermissionContext {
|
||||||
command: string;
|
// 必选字段
|
||||||
workdir: string;
|
command: string; // 命令字符串(用于显示和向后兼容)
|
||||||
|
workdir: string; // 工作目录
|
||||||
|
|
||||||
|
// 可选的结构化字段(用于更精确的类型判断)
|
||||||
|
permissionType?: PermissionType; // 权限类型
|
||||||
patterns?: string[]; // 匹配到的模式
|
patterns?: string[]; // 匹配到的模式
|
||||||
externalPaths?: string[]; // 访问的外部路径
|
externalPaths?: string[]; // 访问的外部路径
|
||||||
|
|
||||||
|
// 文件操作相关(permissionType === 'file' 时使用)
|
||||||
|
fileOperation?: FileOperation;
|
||||||
|
filePath?: string;
|
||||||
|
newContent?: string; // 文件写入/编辑的新内容
|
||||||
|
oldContent?: string; // 文件编辑时的原内容
|
||||||
|
|
||||||
|
// Git 操作相关(permissionType === 'git' 时使用)
|
||||||
|
gitOperation?: GitOperation;
|
||||||
|
gitTarget?: string; // 分支名、文件路径等
|
||||||
|
gitRemote?: string; // 远程仓库名
|
||||||
|
gitForce?: boolean; // 是否强制操作
|
||||||
|
gitMessage?: string; // 提交信息等
|
||||||
|
|
||||||
|
// Web 操作相关(permissionType === 'web' 时使用)
|
||||||
|
webQuery?: string; // 搜索查询
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件操作类型
|
// 文件操作类型
|
||||||
|
|||||||
@@ -11,7 +11,24 @@ export {
|
|||||||
createMessageInfo,
|
createMessageInfo,
|
||||||
} from './message.js';
|
} from './message.js';
|
||||||
|
|
||||||
export type { Part, PartType, ToolStatus, ToolState, ToolPart, TextPart } from './parts.js';
|
export type {
|
||||||
|
Part,
|
||||||
|
PartType,
|
||||||
|
ToolStatus,
|
||||||
|
ToolState,
|
||||||
|
ToolPart,
|
||||||
|
TextPart,
|
||||||
|
ReasoningPart,
|
||||||
|
FilePart,
|
||||||
|
StepStartPart,
|
||||||
|
StepFinishPart,
|
||||||
|
SnapshotPart,
|
||||||
|
PatchPart,
|
||||||
|
AgentPart,
|
||||||
|
SubtaskPart,
|
||||||
|
CompactionPart,
|
||||||
|
RetryPart,
|
||||||
|
} from './parts.js';
|
||||||
export {
|
export {
|
||||||
PartSchema,
|
PartSchema,
|
||||||
TextPartSchema,
|
TextPartSchema,
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export async function startServer(options: ServerOptions = {}): Promise<void> {
|
|||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
export { app, websocket };
|
export { app, websocket };
|
||||||
export { getSessionManager } from './session/manager.js';
|
export { getSessionMetadataManager, getSessionManager } from './session/manager.js';
|
||||||
export { registerTool, getRegisteredTools } from './routes/tools.js';
|
export { registerTool, getRegisteredTools } from './routes/tools.js';
|
||||||
export { getConfig, setConfig } from './routes/config.js';
|
export { getConfig, setConfig } from './routes/config.js';
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -6,12 +6,11 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { broadcastToSession } from '../ws.js';
|
import { broadcastToSession } from '../ws.js';
|
||||||
import type {
|
import type {
|
||||||
PermissionType,
|
|
||||||
PermissionRequestPayload,
|
PermissionRequestPayload,
|
||||||
PermissionRequestContext,
|
PermissionRequestContext,
|
||||||
ServerMessage,
|
ServerMessage,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import type { PermissionDecision, PermissionContext } from '@ai-assistant/core';
|
import type { PermissionDecision, PermissionContext, PermissionType } from '@ai-assistant/core';
|
||||||
|
|
||||||
// 等待中的权限请求
|
// 等待中的权限请求
|
||||||
interface PendingRequest {
|
interface PendingRequest {
|
||||||
@@ -57,14 +56,21 @@ function isAutoApproved(sessionId: string, ctx: PermissionContext): boolean {
|
|||||||
const config = sessionAutoApprove.get(sessionId);
|
const config = sessionAutoApprove.get(sessionId);
|
||||||
if (!config) return false;
|
if (!config) return false;
|
||||||
|
|
||||||
const command = ctx.command.toLowerCase();
|
// 优先使用结构化字段判断
|
||||||
|
if (ctx.permissionType === 'file') {
|
||||||
|
if (ctx.fileOperation === 'write' && config.file?.write === 'allow') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ctx.fileOperation === 'edit' && config.file?.edit === 'allow') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否为文件写入操作
|
// 向后兼容:解析 command 字符串
|
||||||
|
const command = ctx.command.toLowerCase();
|
||||||
if (command.startsWith('write ') && config.file?.write === 'allow') {
|
if (command.startsWith('write ') && config.file?.write === 'allow') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为文件编辑操作
|
|
||||||
if (command.startsWith('edit ') && config.file?.edit === 'allow') {
|
if (command.startsWith('edit ') && config.file?.edit === 'allow') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -73,17 +79,22 @@ function isAutoApproved(sessionId: string, ctx: PermissionContext): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从命令或上下文检测权限类型
|
* 从上下文获取权限类型
|
||||||
|
* 优先使用结构化的 permissionType 字段,否则解析 command 字符串
|
||||||
*/
|
*/
|
||||||
function detectPermissionType(ctx: PermissionContext): PermissionType {
|
function getPermissionType(ctx: PermissionContext): PermissionType {
|
||||||
|
// 优先使用结构化字段
|
||||||
|
if (ctx.permissionType) {
|
||||||
|
return ctx.permissionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向后兼容:解析 command 字符串
|
||||||
const command = ctx.command.toLowerCase();
|
const command = ctx.command.toLowerCase();
|
||||||
|
|
||||||
// 检测 git 操作
|
|
||||||
if (command.startsWith('git ')) {
|
if (command.startsWith('git ')) {
|
||||||
return 'git';
|
return 'git';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测文件操作
|
|
||||||
const fileOps = ['read', 'write', 'edit', 'delete', 'move', 'copy', 'mkdir'];
|
const fileOps = ['read', 'write', 'edit', 'delete', 'move', 'copy', 'mkdir'];
|
||||||
for (const op of fileOps) {
|
for (const op of fileOps) {
|
||||||
if (command.startsWith(`${op} `)) {
|
if (command.startsWith(`${op} `)) {
|
||||||
@@ -91,46 +102,42 @@ function detectPermissionType(ctx: PermissionContext): PermissionType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测 web 操作
|
if (command.includes('fetch') || command.includes('http') || command.startsWith('web_search')) {
|
||||||
if (command.includes('fetch') || command.includes('http')) {
|
|
||||||
return 'web';
|
return 'web';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认为 bash
|
|
||||||
return 'bash';
|
return 'bash';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建权限请求上下文
|
* 构建权限请求上下文
|
||||||
|
* 使用 Core 传递的结构化字段,减少字符串解析
|
||||||
*/
|
*/
|
||||||
function buildRequestContext(ctx: PermissionContext): PermissionRequestContext {
|
function buildRequestContext(ctx: PermissionContext): PermissionRequestContext {
|
||||||
const permType = detectPermissionType(ctx);
|
const permType = getPermissionType(ctx);
|
||||||
|
|
||||||
switch (permType) {
|
switch (permType) {
|
||||||
case 'file': {
|
case 'file':
|
||||||
const parts = ctx.command.split(' ');
|
|
||||||
const operation = parts[0];
|
|
||||||
const path = parts.slice(1).join(' ');
|
|
||||||
return {
|
return {
|
||||||
operation,
|
command: ctx.command,
|
||||||
path,
|
operation: ctx.fileOperation || ctx.command.split(' ')[0],
|
||||||
|
path: ctx.filePath || ctx.command.split(' ').slice(1).join(' '),
|
||||||
patterns: ctx.patterns,
|
patterns: ctx.patterns,
|
||||||
externalPaths: ctx.externalPaths,
|
externalPaths: ctx.externalPaths,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
case 'git': {
|
case 'git':
|
||||||
const gitOp = ctx.command.replace(/^git\s+/, '').split(' ')[0];
|
|
||||||
return {
|
return {
|
||||||
command: ctx.command,
|
command: ctx.command,
|
||||||
gitOperation: gitOp,
|
gitOperation: ctx.gitOperation || ctx.command.replace(/^git\s+/, '').split(' ')[0],
|
||||||
};
|
};
|
||||||
}
|
|
||||||
case 'web': {
|
case 'web':
|
||||||
return {
|
return {
|
||||||
command: ctx.command,
|
command: ctx.command,
|
||||||
query: ctx.command,
|
query: ctx.webQuery || ctx.command,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
command: ctx.command,
|
command: ctx.command,
|
||||||
@@ -155,7 +162,7 @@ export function createServerPermissionCallback(sessionId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestId = randomUUID();
|
const requestId = randomUUID();
|
||||||
const permissionType = detectPermissionType(permCtx);
|
const permissionType = getPermissionType(permCtx);
|
||||||
const context = buildRequestContext(permCtx);
|
const context = buildRequestContext(permCtx);
|
||||||
|
|
||||||
// 构建请求 payload
|
// 构建请求 payload
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getSessionManager } from '../session/manager.js';
|
|||||||
import {
|
import {
|
||||||
CreateSessionInputSchema,
|
CreateSessionInputSchema,
|
||||||
type ToolCallInfo,
|
type ToolCallInfo,
|
||||||
type MergedMessage,
|
type Message,
|
||||||
type MessagePart,
|
type MessagePart,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import type { MessageInfo, Part, ToolPart } from '@ai-assistant/core';
|
import type { MessageInfo, Part, ToolPart } from '@ai-assistant/core';
|
||||||
@@ -131,7 +131,7 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
|||||||
const messageInfos = await MessageStorage.listBySession(id);
|
const messageInfos = await MessageStorage.listBySession(id);
|
||||||
|
|
||||||
// 转换为前端格式
|
// 转换为前端格式
|
||||||
const messages: MergedMessage[] = [];
|
const messages: Message[] = [];
|
||||||
for (const msgInfo of messageInfos) {
|
for (const msgInfo of messageInfos) {
|
||||||
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
|
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Session Manager
|
* Session Metadata Manager
|
||||||
*
|
*
|
||||||
* 管理所有活跃的会话元数据(不存储消息,消息由 Core 负责)
|
* 管理所有活跃的会话元数据(不存储消息,消息由 Core 负责)
|
||||||
|
*
|
||||||
|
* 注意:此类与 Core 的 SessionManager 职责不同:
|
||||||
|
* - SessionMetadataManager(本类):管理会话元数据(id、status、messageCount)
|
||||||
|
* - Core SessionManager:管理完整的会话数据(消息、Parts、Todos)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@@ -13,7 +17,7 @@ import type {
|
|||||||
} from '@ai-assistant/core';
|
} from '@ai-assistant/core';
|
||||||
import { SessionManager as CoreSessionManager } from '@ai-assistant/core';
|
import { SessionManager as CoreSessionManager } from '@ai-assistant/core';
|
||||||
|
|
||||||
export class SessionManager {
|
export class SessionMetadataManager {
|
||||||
private sessions: Map<string, Session> = new Map();
|
private sessions: Map<string, Session> = new Map();
|
||||||
private sessionProjects: Map<string, string> = new Map(); // sessionId -> projectId
|
private sessionProjects: Map<string, string> = new Map(); // sessionId -> projectId
|
||||||
private coreManager: CoreSessionManager | null = null;
|
private coreManager: CoreSessionManager | null = null;
|
||||||
@@ -48,7 +52,7 @@ export class SessionManager {
|
|||||||
|
|
||||||
// 加载已持久化的 sessions(所有项目)
|
// 加载已持久化的 sessions(所有项目)
|
||||||
const summaries = await this.coreManager.listAllSessions();
|
const summaries = await this.coreManager.listAllSessions();
|
||||||
console.log(`[SessionManager] Found ${summaries.length} persisted sessions`);
|
console.log(`[SessionMetadataManager] Found ${summaries.length} persisted sessions`);
|
||||||
|
|
||||||
for (const summary of summaries) {
|
for (const summary of summaries) {
|
||||||
// 转换为 Server Session 格式(只保存元数据,不存储消息)
|
// 转换为 Server Session 格式(只保存元数据,不存储消息)
|
||||||
@@ -69,9 +73,9 @@ export class SessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[SessionManager] Loaded ${this.sessions.size} sessions from storage`);
|
console.log(`[SessionMetadataManager] Loaded ${this.sessions.size} sessions from storage`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[SessionManager] Storage not available, using memory only:', error);
|
console.warn('[SessionMetadataManager] Storage not available, using memory only:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
@@ -207,11 +211,17 @@ export class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 单例
|
// 单例
|
||||||
let instance: SessionManager | null = null;
|
let instance: SessionMetadataManager | null = null;
|
||||||
|
|
||||||
export function getSessionManager(): SessionManager {
|
export function getSessionMetadataManager(): SessionMetadataManager {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = new SessionManager();
|
instance = new SessionMetadataManager();
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 向后兼容别名(已废弃)
|
||||||
|
/** @deprecated 使用 SessionMetadataManager 和 getSessionMetadataManager 代替 */
|
||||||
|
export const SessionManager = SessionMetadataManager;
|
||||||
|
/** @deprecated 使用 getSessionMetadataManager 代替 */
|
||||||
|
export const getSessionManager = getSessionMetadataManager;
|
||||||
|
|||||||
@@ -4,6 +4,22 @@
|
|||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// 从 Core 导入共享类型,避免重复定义
|
||||||
|
import type {
|
||||||
|
ToolStatus,
|
||||||
|
PermissionType,
|
||||||
|
PermissionContext,
|
||||||
|
Part as CorePart,
|
||||||
|
PartType as CorePartType,
|
||||||
|
TextPart as CoreTextPart,
|
||||||
|
ToolPart as CoreToolPart,
|
||||||
|
ReasoningPart as CoreReasoningPart,
|
||||||
|
} from '@ai-assistant/core';
|
||||||
|
|
||||||
|
// 重新导出 Core 类型,供其他模块使用
|
||||||
|
export type { ToolStatus, PermissionType, PermissionContext };
|
||||||
|
export type { CorePart, CorePartType, CoreTextPart, CoreToolPart, CoreReasoningPart };
|
||||||
|
|
||||||
// ============ Session 相关 ============
|
// ============ Session 相关 ============
|
||||||
|
|
||||||
export const SessionStatusSchema = z.enum(['idle', 'active', 'busy', 'running', 'paused']);
|
export const SessionStatusSchema = z.enum(['idle', 'active', 'busy', 'running', 'paused']);
|
||||||
@@ -30,34 +46,8 @@ export type CreateSessionInput = z.infer<typeof CreateSessionInputSchema>;
|
|||||||
|
|
||||||
// ============ Message 相关 ============
|
// ============ Message 相关 ============
|
||||||
|
|
||||||
export const MessageRoleSchema = z.enum(['user', 'assistant', 'system', 'tool']);
|
// 消息角色(仅用于 SendMessageInput,实际消息只有 user/assistant)
|
||||||
|
export const MessageRoleSchema = z.enum(['user', 'assistant']);
|
||||||
export const MessageSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
sessionId: z.string().uuid(),
|
|
||||||
role: MessageRoleSchema,
|
|
||||||
content: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
toolCalls: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
arguments: z.record(z.string(), z.unknown()),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
toolResults: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
toolCallId: z.string(),
|
|
||||||
result: z.unknown(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Message = z.infer<typeof MessageSchema>;
|
|
||||||
|
|
||||||
export const SendMessageInputSchema = z.object({
|
export const SendMessageInputSchema = z.object({
|
||||||
role: MessageRoleSchema.default('user'),
|
role: MessageRoleSchema.default('user'),
|
||||||
@@ -213,10 +203,11 @@ export type SubagentEventPayload =
|
|||||||
|
|
||||||
// ============ Permission 相关 ============
|
// ============ Permission 相关 ============
|
||||||
|
|
||||||
export type PermissionType = 'bash' | 'file' | 'git' | 'web';
|
// PermissionType 和 PermissionContext 已从 Core 导入(见文件顶部)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限请求上下文
|
* 权限请求上下文(Server 专用,已废弃)
|
||||||
|
* @deprecated 使用 PermissionContext(从 Core 导入)代替
|
||||||
*/
|
*/
|
||||||
export interface PermissionRequestContext {
|
export interface PermissionRequestContext {
|
||||||
command?: string; // bash 命令
|
command?: string; // bash 命令
|
||||||
@@ -280,15 +271,19 @@ export interface SSEEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 消息 Parts 相关 ============
|
// ============ 消息 Parts 相关(前端展示格式)============
|
||||||
|
//
|
||||||
|
// 说明:这些是前端展示用的扁平化 Part 类型
|
||||||
|
// Core 的 Part 类型(CorePart)是存储格式,使用状态机模式
|
||||||
|
// Server 在 routes/sessions.ts 中负责将 CorePart 转换为 MessagePart
|
||||||
|
|
||||||
|
// ToolCallStatus 已废弃,使用 ToolStatus(从 Core 导入)
|
||||||
|
// 保留类型别名以保持向后兼容
|
||||||
|
/** @deprecated 使用 ToolStatus 代替 */
|
||||||
|
export type ToolCallStatus = ToolStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具调用状态
|
* 文本 Part(前端展示格式)
|
||||||
*/
|
|
||||||
export type ToolCallStatus = 'pending' | 'running' | 'completed' | 'error';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文本 Part
|
|
||||||
*/
|
*/
|
||||||
export interface TextMessagePart {
|
export interface TextMessagePart {
|
||||||
type: 'text';
|
type: 'text';
|
||||||
@@ -297,14 +292,15 @@ export interface TextMessagePart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具调用 Part
|
* 工具调用 Part(前端展示格式)
|
||||||
|
* 与 Core 的 ToolPart 不同,这里使用扁平结构而非状态机
|
||||||
*/
|
*/
|
||||||
export interface ToolMessagePart {
|
export interface ToolMessagePart {
|
||||||
type: 'tool';
|
type: 'tool';
|
||||||
id: string;
|
id: string;
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
status: ToolCallStatus;
|
status: ToolStatus;
|
||||||
arguments: Record<string, unknown>;
|
arguments: Record<string, unknown>;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -312,7 +308,7 @@ export interface ToolMessagePart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推理 Part
|
* 推理 Part(前端展示格式)
|
||||||
*/
|
*/
|
||||||
export interface ReasoningMessagePart {
|
export interface ReasoningMessagePart {
|
||||||
type: 'reasoning';
|
type: 'reasoning';
|
||||||
@@ -321,7 +317,8 @@ export interface ReasoningMessagePart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息 Part 联合类型
|
* 消息 Part 联合类型(前端展示格式)
|
||||||
|
* 用于 API 响应和 WebSocket 消息
|
||||||
*/
|
*/
|
||||||
export type MessagePart = TextMessagePart | ToolMessagePart | ReasoningMessagePart;
|
export type MessagePart = TextMessagePart | ToolMessagePart | ReasoningMessagePart;
|
||||||
|
|
||||||
@@ -332,7 +329,7 @@ export interface ToolCallInfo {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
arguments: Record<string, unknown>;
|
arguments: Record<string, unknown>;
|
||||||
status: ToolCallStatus;
|
status: ToolStatus;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
@@ -345,7 +342,7 @@ export interface ToolCallInfo {
|
|||||||
* - user: 用户输入
|
* - user: 用户输入
|
||||||
* - assistant: AI 回复(包含文本和工具调用,按原始顺序)
|
* - assistant: AI 回复(包含文本和工具调用,按原始顺序)
|
||||||
*/
|
*/
|
||||||
export interface MergedMessage {
|
export interface Message {
|
||||||
id: string;
|
id: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
role: 'user' | 'assistant';
|
role: 'user' | 'assistant';
|
||||||
@@ -363,6 +360,9 @@ export interface MergedMessage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated 使用 Message 代替 */
|
||||||
|
export type MergedMessage = Message;
|
||||||
|
|
||||||
// ============ API 响应 ============
|
// ============ API 响应 ============
|
||||||
|
|
||||||
export interface ApiResponse<T> {
|
export interface ApiResponse<T> {
|
||||||
|
|||||||
@@ -13,8 +13,12 @@ export interface Session {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具调用状态
|
* 工具调用状态
|
||||||
|
* 与 Core 的 ToolStatus 保持一致
|
||||||
*/
|
*/
|
||||||
export type ToolCallStatus = 'pending' | 'running' | 'completed' | 'error';
|
export type ToolStatus = 'pending' | 'running' | 'completed' | 'error';
|
||||||
|
|
||||||
|
/** @deprecated 使用 ToolStatus 代替 */
|
||||||
|
export type ToolCallStatus = ToolStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具调用信息
|
* 工具调用信息
|
||||||
@@ -23,7 +27,7 @@ export interface ToolCallInfo {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
arguments: Record<string, unknown>;
|
arguments: Record<string, unknown>;
|
||||||
status: ToolCallStatus;
|
status: ToolStatus;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
duration?: number; // 执行时长 ms
|
duration?: number; // 执行时长 ms
|
||||||
@@ -48,7 +52,7 @@ export interface ToolMessagePart {
|
|||||||
id: string;
|
id: string;
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
status: ToolCallStatus;
|
status: ToolStatus;
|
||||||
arguments: Record<string, unknown>;
|
arguments: Record<string, unknown>;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -977,7 +981,7 @@ export type SubagentEventPayload =
|
|||||||
export interface SubagentToolInfo {
|
export interface SubagentToolInfo {
|
||||||
id: string;
|
id: string;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
status: ToolCallStatus;
|
status: ToolStatus;
|
||||||
args: Record<string, unknown>;
|
args: Record<string, unknown>;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { fadeInUp, smoothTransition } from '../utils/animations';
|
|||||||
import { getAgentDisplayName } from '../utils/agent';
|
import { getAgentDisplayName } from '../utils/agent';
|
||||||
import { Markdown } from './Markdown';
|
import { Markdown } from './Markdown';
|
||||||
import { FileMentionText } from './FileMentionTag';
|
import { FileMentionText } from './FileMentionTag';
|
||||||
import type { Message, ToolCallInfo, ToolCallStatus, ToolMessagePart } from '../api/types.js';
|
import type { Message, ToolCallInfo, ToolStatus, ToolMessagePart } from '../api/types.js';
|
||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
@@ -319,7 +319,7 @@ function ToolPartItem({ part }: ToolPartItemProps) {
|
|||||||
/**
|
/**
|
||||||
* 获取工具状态图标
|
* 获取工具状态图标
|
||||||
*/
|
*/
|
||||||
function getStatusIcon(status: ToolCallStatus) {
|
function getStatusIcon(status: ToolStatus) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return <Clock size={14} className="text-yellow-500" />;
|
return <Clock size={14} className="text-yellow-500" />;
|
||||||
|
|||||||
Reference in New Issue
Block a user