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
+16 -44
View File
@@ -12,8 +12,8 @@ import {
type Message,
type MessagePart,
} from '../types.js';
import type { MessageInfo, Part, ToolPart } from '@ai-assistant/core';
import { MessageStorage, PartStorage } from '@ai-assistant/core';
import type { MessageInfo, Part, ToolPart, ApiPart } from '@ai-assistant/core';
import { MessageStorage, PartStorage, partsToApiFormat, getToolInput, getToolDuration } from '@ai-assistant/core';
export const sessionsRouter = new Hono();
@@ -135,33 +135,10 @@ sessionsRouter.get('/:id/messages', async (c) => {
for (const msgInfo of messageInfos) {
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
// 转换 Parts 为前端格式(保持顺序)
const messageParts: MessagePart[] = parts
.filter((p) => p.type === 'text' || p.type === 'tool' || p.type === 'reasoning')
.map((p): MessagePart => {
if (p.type === 'text') {
return { type: 'text', id: p.id, text: p.text ?? '' };
}
if (p.type === 'reasoning') {
return { type: 'reasoning', id: p.id, text: p.text ?? '' };
}
// tool - 使用类型断言
const toolPart = p 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: p.id,
toolCallId: toolPart.toolCallId ?? '',
toolName: toolPart.toolName ?? '',
status: state.status,
arguments: state.status !== 'pending' ? (state.input as Record<string, unknown>) : {},
result: state.status === 'completed' ? state.output : undefined,
error: state.status === 'error' ? state.error : undefined,
duration: startTime && endTime ? endTime - startTime : undefined,
};
});
// 使用 Core 的转换函数将 Parts 转换为 API 格式
const apiParts = partsToApiFormat(parts);
// 类型兼容:ApiPart 与 MessagePart 结构相同
const messageParts = apiParts as MessagePart[];
// 兼容字段:提取文本内容
const textContent = parts
@@ -169,23 +146,18 @@ sessionsRouter.get('/:id/messages', async (c) => {
.map((p) => p.text ?? '')
.join('');
// 兼容字段:提取工具调用
// 兼容字段:提取工具调用(使用 Core 工具函数)
const toolCalls: ToolCallInfo[] = parts
.filter((p): p is ToolPart => p.type === 'tool')
.map((p) => {
const state = p.state;
const startTime = state.status !== 'pending' ? state.time?.start : undefined;
const endTime = state.status === 'completed' || state.status === 'error' ? state.time?.end : undefined;
return {
id: p.toolCallId ?? '',
name: p.toolName ?? '',
arguments: state.status !== 'pending' ? (state.input as Record<string, unknown>) : {},
status: state.status,
result: state.status === 'completed' ? state.output : undefined,
error: state.status === 'error' ? state.error : undefined,
duration: startTime && endTime ? endTime - startTime : undefined,
};
});
.map((p) => ({
id: p.toolCallId ?? '',
name: p.toolName ?? '',
arguments: getToolInput(p),
status: p.state.status,
result: p.state.status === 'completed' ? (p.state as { output: unknown }).output : undefined,
error: p.state.status === 'error' ? (p.state as { error: string }).error : undefined,
duration: getToolDuration(p),
}));
messages.push({
id: msgInfo.id,