refactor(core,server): 统一模块职责并消除类型重复

Core 模块职责:
- 添加 inferPermissionType() 权限类型推断函数
- 添加 partToApiFormat()/partsToApiFormat() API 格式转换函数
- 添加 ApiPart/ApiTextPart/ApiToolPart/ApiReasoningPart 类型
- 统一导出 toolRegistry 供 Server 使用

Server 模块职责:
- 重命名 PermissionRequestContext 为 PermissionDisplayContext
- 移除本地 toolRegistry,直接使用 Core 的注册表
- 使用 Core 的 inferPermissionType 替代本地实现
- 使用 Core 的 partsToApiFormat 替代手动转换

文档更新:
- 在 gui-server-client.md 添加第11章「模块职责边界」
- 明确 Core 和 Server 的职责划分
This commit is contained in:
2025-12-16 21:28:19 +08:00
parent 0a26c3ab72
commit e53035ffc0
12 changed files with 274 additions and 185 deletions
+14 -1
View File
@@ -51,13 +51,26 @@ export type {
ToolState,
TodoItem,
TodoList,
// API 格式类型
ApiPart,
ApiTextPart,
ApiToolPart,
ApiReasoningPart,
} from './session/index.js';
// API 格式转换函数
export {
partsToApiFormat,
partToApiFormat,
getToolInput,
getToolDuration,
} from './session/index.js';
// Types
export type { UserInput, ChatResult } from './types/index.js';
// Permission
export { getPermissionManager } from './permission/index.js';
export { getPermissionManager, inferPermissionType } from './permission/index.js';
export type {
PermissionType,
PermissionContext,
+3
View File
@@ -16,6 +16,9 @@ export type {
GitPermissionConfig,
} from './types.js';
// 工具函数
export { inferPermissionType } from './types.js';
export { matchPattern, matchRules, parseCommand, generateAskPattern } from './wildcard.js';
export { PermissionManager, getPermissionManager, resetPermissionManager } from './manager.js';
+40
View File
@@ -170,3 +170,43 @@ export interface GitPermissionConfig {
// 危险操作策略(force push, reset --hard 等,默认 ask
dangerousOperations: PermissionAction;
}
// ============ 工具函数 ============
/**
* 从上下文获取权限类型
* 优先使用结构化的 permissionType 字段,否则从 command 字符串推断
*
* @param ctx 权限上下文
* @returns 权限类型
*/
export function inferPermissionType(ctx: PermissionContext): PermissionType {
// 优先使用结构化字段
if (ctx.permissionType) {
return ctx.permissionType;
}
// 向后兼容:从 command 字符串推断
const command = ctx.command.toLowerCase();
// Git 操作
if (command.startsWith('git ')) {
return 'git';
}
// 文件操作
const fileOps = ['read', 'write', 'edit', 'delete', 'move', 'copy', 'mkdir', 'list'];
for (const op of fileOps) {
if (command.startsWith(`${op} `)) {
return 'file';
}
}
// Web 操作
if (command.includes('fetch') || command.includes('http') || command.startsWith('web_search')) {
return 'web';
}
// 默认为 bash
return 'bash';
}
+102
View File
@@ -167,3 +167,105 @@ export function getToolDuration(toolPart: ToolPart): number | undefined {
}
return undefined;
}
// ============ API 格式转换 ============
// 用于将存储格式的 Part 转换为前端/API 显示的扁平格式
/**
* API 响应用的文本 Part(扁平格式)
*/
export interface ApiTextPart {
type: 'text';
id: string;
text: string;
}
/**
* API 响应用的工具调用 Part(扁平格式)
*/
export interface ApiToolPart {
type: 'tool';
id: string;
toolCallId: string;
toolName: string;
status: 'pending' | 'running' | 'completed' | 'error';
arguments: Record<string, unknown>;
result?: unknown;
error?: string;
duration?: number;
}
/**
* API 响应用的推理 Part(扁平格式)
*/
export interface ApiReasoningPart {
type: 'reasoning';
id: string;
text: string;
}
/**
* API 响应用的 Part 联合类型
*/
export type ApiPart = ApiTextPart | ApiToolPart | ApiReasoningPart;
/**
* 将存储格式的 Part 转换为 API 响应格式(扁平结构)
*
* 存储格式使用状态机模式(ToolState 有 pending/running/completed/error
* API 格式使用扁平结构(直接的 status + result/error 字段)
*
* @param part 存储格式的 Part
* @returns API 格式的 Part,如果是不支持的类型则返回 null
*/
export function partToApiFormat(part: Part): ApiPart | null {
switch (part.type) {
case 'text':
return {
type: 'text',
id: part.id,
text: part.text,
};
case 'reasoning':
return {
type: 'reasoning',
id: part.id,
text: part.text,
};
case 'tool': {
const toolPart = part as ToolPart;
const state = toolPart.state;
const startTime = state.status !== 'pending' ? state.time?.start : undefined;
const endTime =
state.status === 'completed' || state.status === 'error' ? state.time?.end : undefined;
return {
type: 'tool',
id: part.id,
toolCallId: toolPart.toolCallId,
toolName: toolPart.toolName,
status: state.status,
arguments: state.status !== 'pending' ? getToolInput(toolPart) : {},
result: state.status === 'completed' ? (state as { output: unknown }).output : undefined,
error: state.status === 'error' ? (state as { error: string }).error : undefined,
duration: startTime && endTime ? endTime - startTime : undefined,
};
}
default:
// 其他类型(step-start, step-finish, snapshot 等)不传给前端
return null;
}
}
/**
* 批量将存储格式的 Parts 转换为 API 响应格式
*
* @param parts 存储格式的 Parts 数组
* @returns API 格式的 Parts 数组(过滤掉不支持的类型)
*/
export function partsToApiFormat(parts: Part[]): ApiPart[] {
return parts.map(partToApiFormat).filter((p): p is ApiPart => p !== null);
}
+3
View File
@@ -53,6 +53,9 @@ export {
// 消息转换器
export { toModelMessages, getToolInput, getToolDuration } from './converter.js';
// API 格式转换
export { partToApiFormat, partsToApiFormat } from './converter.js';
export type { ApiPart, ApiTextPart, ApiToolPart, ApiReasoningPart } from './converter.js';
// ID 生成器
export {