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:
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
export { sessionsRouter } from './sessions.js';
|
||||
export { toolsRouter, registerTool, getRegisteredTools } from './tools.js';
|
||||
export { toolsRouter } from './tools.js';
|
||||
export { configRouter, getConfig, setConfig } from './config.js';
|
||||
export { filesRouter, setWorkingDirectory, getWorkingDirectory } from './files.js';
|
||||
export { commandsRouter } from './commands.js';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,39 +2,30 @@
|
||||
* Tools API Routes
|
||||
*
|
||||
* 工具管理相关的 REST API
|
||||
* 直接使用 Core 的 toolRegistry,不再维护独立的注册表
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { Tool } from '../types.js';
|
||||
import { toolRegistry } from '@ai-assistant/core';
|
||||
|
||||
export const toolsRouter = new Hono();
|
||||
|
||||
// 工具注册表 (后续会从 core 模块获取)
|
||||
const toolRegistry: Map<string, Tool> = new Map();
|
||||
|
||||
/**
|
||||
* 注册工具 (内部使用)
|
||||
*/
|
||||
export function registerTool(tool: Tool): void {
|
||||
toolRegistry.set(tool.name, tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的工具
|
||||
*/
|
||||
export function getRegisteredTools(): Tool[] {
|
||||
return Array.from(toolRegistry.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /tools - 列出所有可用工具
|
||||
*/
|
||||
toolsRouter.get('/', (c) => {
|
||||
const tools = getRegisteredTools();
|
||||
const tools = toolRegistry.getAllTools();
|
||||
|
||||
// 转换为 API 格式(不包含 execute 函数)
|
||||
const toolList = tools.map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.parameters,
|
||||
}));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: tools,
|
||||
data: toolList,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +34,7 @@ toolsRouter.get('/', (c) => {
|
||||
*/
|
||||
toolsRouter.get('/:name', (c) => {
|
||||
const name = c.req.param('name');
|
||||
const tool = toolRegistry.get(name);
|
||||
const tool = toolRegistry.getTool(name);
|
||||
|
||||
if (!tool) {
|
||||
return c.json(
|
||||
@@ -57,7 +48,11 @@ toolsRouter.get('/:name', (c) => {
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: tool,
|
||||
data: {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,7 +64,7 @@ toolsRouter.get('/:name', (c) => {
|
||||
*/
|
||||
toolsRouter.post('/:name/execute', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const tool = toolRegistry.get(name);
|
||||
const tool = toolRegistry.getTool(name);
|
||||
|
||||
if (!tool) {
|
||||
return c.json(
|
||||
@@ -85,8 +80,9 @@ toolsRouter.post('/:name/execute', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const params = body.params || {};
|
||||
|
||||
// TODO: 实际调用 core 模块的工具执行逻辑
|
||||
// const result = await executeTool(name, params);
|
||||
// TODO: 实际调用工具执行逻辑
|
||||
// 需要设置正确的上下文(workdir、权限回调等)
|
||||
// const result = await tool.execute(params, context);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -94,7 +90,7 @@ toolsRouter.post('/:name/execute', async (c) => {
|
||||
tool: name,
|
||||
params,
|
||||
result: null, // 占位,后续实现
|
||||
message: 'Tool execution not yet implemented',
|
||||
message: 'Tool execution not yet implemented - use WebSocket for tool execution',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user