# @cicctencent/agent-server

Server-layer toolkit for `@cicctencent/agent-core` — Agent 初始化、SSE 流式推送、内置工具、沙箱、MCP 桥接、知识库、Chat/Settings/Memory/Security 服务，通过 SPI 接口实现可定制性。

**框架无关**：不依赖 Express/Fastify/Koa，通过 `SSEResponse` 接口适配任意 HTTP 框架。

## 架构

```
┌─────────────────────────────────────────────────┐
│  Layer 3: 应用层 (如 workai)                    │
│  路由、认证、DB 模型、业务逻辑、doc-generator    │
├─────────────────────────────────────────────────┤
│  Layer 2: @cicctencent/agent-server (本包)      │
│  Agent 初始化、SSE、Chat、工具、沙箱、MCP 桥接   │
│  KB、安全审计、连接器框架，通过 SPI 自定义        │
├─────────────────────────────────────────────────┤
│  Layer 1: @cicctencent/agent-core               │
│  纯引擎：LLM 编排、工具执行、MCP、Memory、A2A     │
└─────────────────────────────────────────────────┘
```

## 快速开始

```typescript
import { createAgentServer, createBuiltinTools, createSafetyAuditMiddleware } from '@cicctencent/agent-server';
import { createLLMProvider } from '@cicctencent/agent-core';
import express from 'express';

const app = express();

const server = createAgentServer({
  llmProvider: createLLMProvider({
    provider: 'openai',
    model: 'gpt-4',
    apiKey: process.env.OPENAI_API_KEY!,
  }),
  tools: createBuiltinTools(),
  middleware: [createSafetyAuditMiddleware()],
});

// runAgentStream 接收 SSEResponse 接口，Express Response 天然兼容
app.post('/api/work/chat/stream', async (req, res) => {
  await server.runAgentStream(res, {
    content: req.body.content,
    threadId: req.body.threadId,
  });
});

app.listen(3000);
```

## 框架无关

`agent-server` 不依赖任何 HTTP 框架。`runAgentStream` / `reconnectAgentStream` 接收 `SSEResponse` 接口：

```typescript
interface SSEResponse {
  write(data: string): void;
  end(): void;
  readonly writableEnded: boolean;
  setHeader(name: string, value: string | number): void;
  flushHeaders?(): void;
  status?(code: number): void;
  json?(data: any): void;
}
```

Express `Response`、Fastify `Reply`（适配后）、原生 `http.ServerResponse` 均可适配。

## SPI 自定义

agent-server 采用 **SPI (Service Provider Interface)** 设计模式，所有核心组件都可以替换。

### 设计原则

```
┌─────────────────────────────────────────────────────────────┐
│                    agent-server SPI                          │
│                                                              │
│  核心：只定义接口（能力契约）                                  │
│  ├── SSEResponseAdapter    ← SSE 流式响应能力                 │
│  ├── ChatStoreProvider     ← 对话存储能力                     │
│  ├── SettingsProvider      ← 设置存储能力                     │
│  └── AgentProfileProvider  ← Agent Profile 能力              │
│                                                              │
│  可选：提供参考实现（开箱即用）                                │
│  ├── KoaSSEAdapter         ← Koa/MidwayJS 参考               │
│  ├── ExpressSSEAdapter     ← Express 参考                    │
│  └── JsonFileStorage       ← JSON 文件存储参考               │
│                                                              │
│  业务层：完全自主                                             │
│  ├── 直接使用参考实现                                         │
│  ├── 继承扩展                                                 │
│  └── 完全自己实现                                             │
└─────────────────────────────────────────────────────────────┘
```

### 快速示例

```typescript
import {
  createAgentServer,
  KoaSSEAdapter,
  type ChatStoreProvider,
  type BaseThreadEntity,
  type BaseMessageEntity,
} from '@cicctencent/agent-server';

// 1. 定义自定义实体（可以添加额外字段）
interface MyThread extends BaseThreadEntity {
  userId: string;           // 额外字段
  applicationId: number;    // 额外字段
}

// 2. 实现 ChatStoreProvider（使用任意存储技术）
class MyChatStore implements ChatStoreProvider<MyThread, MyMessage> {
  async getThreads(userId?: string): Promise<MyThread[]> {
    // 使用 TypeORM / Prisma / Mongoose / 任意存储
    return this.database.query('SELECT * FROM threads WHERE userId = ?', userId);
  }
  // ... 实现其他方法
}

// 3. 创建 AgentServer，注入自定义实现
const server = createAgentServer({
  llmProvider: myProvider,
  chatStoreProvider: new MyChatStore(),  // 替换默认实现
});

// 4. 在 MidwayJS/Koa 中使用 SSE 适配器
@Get('/chat/stream')
async chatStream(@Ctx() ctx: Context) {
  const adapter = new KoaSSEAdapter(ctx);
  await server.runAgentStream(adapter, { content: 'Hello' });
}
```

### 组件级使用（不依赖工厂）

```typescript
import {
  KoaSSEAdapter,
  writeSSE,
  createBuiltinTools,
  MemoryService,
} from '@cicctencent/agent-server';

// 只使用需要的组件，不使用 createAgentServer
const tools = createBuiltinTools();
const memoryService = new MemoryService('./data');

@Get('/stream')
async stream(@Ctx() ctx: Context) {
  const adapter = new KoaSSEAdapter(ctx);
  writeSSE(adapter, 'start', { message: 'Hello' });
}
```

### SPI 接口清单

| 接口 | 默认实现 | 用途 |
|------|---------|------|
| `SettingsProvider<TSettings>` | `DefaultSettingsProvider` | 设置持久化（泛型） |
| `ChatStoreProvider<TThread, TMessage>` | `DefaultChatStoreProvider` | 对话 CRUD + 历史重建（泛型） |
| `PromptBuilderProvider` | `DefaultPromptBuilder` | 系统提示词构建 |
| `ModelConfigResolver` | — | 模型配置解析 |
| `MCPServiceProvider` | — | MCP 服务器管理 |
| `SkillServiceProvider` | — | 技能 CRUD 管理 |
| `ConnectorServiceProvider` | — | 数据源连接器管理 |
| `AgentProfileProvider<TProfile>` | `DefaultAgentProfileProvider` | 多 Agent 配置管理（泛型） |
| `SSEResponseAdapter` | `KoaSSEAdapter` / `ExpressSSEAdapter` | SSE 流式响应适配 |
| `KVStorageAdapter` | `JsonFileKVStorage` | 键值存储抽象 |
| `ListStorageAdapter<T>` | `JsonFileListStorage` | 列表存储抽象 |

### 详细文档

完整的 SPI 使用指南请参考 [src/spi/README.md](src/spi/README.md)，包含：
- 所有接口的详细定义
- TypeORM / Prisma / Mongoose 实现示例
- MidwayJS + TypeORM 完整模板
- 常见问题解答

## 模块清单

### 核心工厂
- `createAgentServer(options)` — 创建 AgentServer 实例

### 默认 SPI 实现
- `DefaultSettingsProvider` — JSON 文件设置存储
- `DefaultChatStoreProvider` — JSON 文件对话存储 + 历史重建
- `DefaultAgentProfileProvider` — JSON 文件 Agent Profile 管理
- `DefaultPromptBuilder` — 系统提示词构建器

### 服务
- `MemoryService` / `getMemoryService()` — 长期记忆持久化
- `getEnginePool()` — AgentEngine 实例复用池
- `getRunRegistry()` — Agent 运行会话注册表
- `getSecurityPolicy()` / `updateSecurityPolicy()` — 安全策略管理
- `assessToolRisk()` / `isLowRiskTool()` — 工具风险评估
- `createPermissionRequest()` / `findApprovedPermission()` — 权限审批
- `appendAuditLog()` / `listAuditLogs()` — 审计日志
- `createSafetyAuditMiddleware()` — 安全审计中间件
- `logEvent()` / `setLogTransport()` — 可观测性
- `saveArtifact()` / `resolveArtifactPath()` / `getMimeType()` — Artifact 存储
- `getWorkspaces()` / `createWorkspace()` / `ensureDefaultWorkspace()` — 工作空间 CRUD

### SSE
- `writeSSE()` / `initSSEResponse()` — SSE 事件写入（框架无关）
- `runAgentSSEStream()` — Agent SSE 流式推送

### 内置工具
- `createBuiltinTools(options?)` — 聚合所有内置工具（含 docgen）
- `createFilesystemTools()` — execute_command, read_terminal, write_file, read_file, search_code, edit_file, append_file
- `createDiscoveryTools()` — read_tool_doc, load_skill, read_tool_result, read_offload
- `createWebTools()` — web_fetch, http_request, browser_navigate
- `createMemoryTools()` — save_memory, recall_memory, delete_memory
- `createSandboxTools()` — run_script, show_widget, save_artifact
- `createAutomationTools()` — create_automation, list_automations, delete_automation
- `createDocgenTools(options?)` — create_presentation, create_document, create_pdf, create_report

#### 文档生成（docgen）

`createBuiltinTools()` / `createDocgenTools()` 支持 `options.templatesDir` 指定应用层主题模板根目录：

```typescript
const tools = createBuiltinTools({
  templatesDir: '/abs/path/to/your-app/data/_shared/docs',
});
```

主题解析优先级：`options.templatesDir` → 代码内置 `DEFAULT_THEME`（始终可用）。旧的消费方数据目录 `getProjectDataPath('doc-templates')` 回退已移除，应用层须通过 `templatesDir` 显式传入模板目录。也可在运行时通过 `ToolContext.custom.templatesDir` 按请求覆盖。

> ⚠️ 文档生成依赖 `ctx.custom.sandbox` 与 `ctx.emitArtifact`（由引擎注入），请确保运行时上下文携带这些字段。
> 完整的主题目录布局、Schema 与接入示例见仓库 [docs/DOCGEN.md](../../../docs/DOCGEN.md)。

### 工具执行器
- `LocalShellExecutor` — 基于 child_process 的 Shell 执行器
- `LocalFileSystemExecutor` — 基于 fs/promises 的文件系统执行器

### 沙箱
- `LocalSandbox` — 进程级沙箱实例（隔离文件系统 + Shell + 脚本执行）
- `LocalSandboxManager` / `getSandboxManager()` — 沙箱生命周期管理器
- `ProcessCodeSandbox` — 结构化代码执行沙箱（stdout/stderr/files/charts）
- `SandboxFileSystemExecutor` — 沙箱内文件系统执行器（路径穿越防护）
- `SandboxShellExecutor` — 沙箱内 Shell 执行器（命令黑名单 + 环境变量过滤）

### MCP 桥接
- `ApiBridgeClient` — HTTP API → MCP 桥接客户端
- `buildFromTemplate` — 通用模板请求构建器（配置驱动）
- `resolveBodyTemplate()` — `${param|default}` 模板解析
- `importFromOpenAPI()` — OpenAPI 3.x / Swagger 2.0 → MCP 工具定义
- 类型：`RequestBuilderFn`, `AuthConfig`, `ResponseConfig`, `TemplateBuilderExtra`

### 知识库
- `FileKnowledgeBase` — 基于文件的知识库（文档分块 + embedding 存储）
- `EmbeddingVectorIndex` — 基于 Embedding 的向量索引
- `parseDocument()` — 文档解析与分块（Markdown/TXT/JSON）

### 连接器
- `registerAdapter()` / `getAdapter()` — 数据源适配器注册表

### 存储
- `JsonStore` / `JsonListStore` — 通用 JSON 文件存储
- `McpServerStore` — 目录型 MCP 服务器存储
- `getToolResultStore()` — 工具结果存储单例
- `FileNoteStore` — 工作空间笔记存储（agent.md）
- `WorkspaceStore` / `getWorkspaceStore()` — 工作空间存储
- `AutomationStore` / `getAutomationStore()` — 自动化任务存储
- `AutomationRunStore` / `getAutomationRunStore()` — 自动化执行记录存储

### 数据模型
- `AutomationEntity` / `AutomationRunEntity` / `AutomationStatus` — 自动化任务类型

### 工具函数
- `getProjectDataPath()` / `ensureDataDir()` — 数据目录管理
- `expandEnvVars()` — 环境变量展开
- `setAppName()` / `getAppName()` — 应用名设置
- `ensureDefaultData()` — 默认数据文件复制

## AgentServer API

```typescript
interface AgentServer {
  agentSystem: AgentSystem;
  registerTool(tool): void;
  unregisterTool(name): void;
  registerMiddleware(mw): void;
  unregisterMiddleware(name): void;
  runAgentStream(res: SSEResponse, params: RunAgentParams, signal?): Promise<void>;
  reconnectAgentStream(res: SSEResponse, threadId): Promise<void>;
  getEngineForProfile(profileId?, canDelegateOverride?): Promise<AgentEngine>;
  abortAgent(threadId): boolean;
  steerAgent(sessionId, message): boolean;
  clearEnginePool(): void;
  refreshLLMConfig(): void;
  refreshAgentConfig(): void;
  getRegisteredTools(): ToolInfo[];
  getLoadedSkills(): SkillInfo[];
  isAgentAvailable(): boolean;
  checkRunStatus(threadId): RunStatus;
  providers: { ... };
}
```

### RunAgentParams

```typescript
interface RunAgentParams {
  content: string | ContentPart[];   // 用户输入
  threadId?: string;                  // 对话线程 ID（不传则不持久化）
  agentProfileId?: number;            // Agent Profile ID
  traceId?: string;                   // 追踪 ID
  tenant?: TenantContext;             // 多租户上下文
  allowedSpecialistIds?: number[];    // 允许委派的 Specialist ID 列表
  customContext?: Record<string, unknown>; // 自定义上下文
  testMode?: boolean;                 // 测试模式：禁用委派，不持久化
}
```

### getEngineForProfile(profileId?, canDelegateOverride?)

获取指定 Profile 的 AgentEngine 实例（带缓存）。

- `profileId` — Agent Profile ID，不传使用默认
- `canDelegateOverride` — 强制覆盖委派策略（`false` = 禁用委派，用于测试模式）
- 返回带 `delegate_task` 工具的引擎（如果 `canDelegate` 为 `true`）
- 测试模式使用独立缓存 key（`_nodelegate`），不污染正常引擎缓存

### REST A2A 自定义 Action

远程 REST A2A Agent 支持自定义 action 后缀，通过 `restA2AConfig` 配置：

```typescript
restA2AConfig: {
  capabilityId: 'data_query_assistant',
  streamAction: 'stream',    // 流式端点后缀，默认 'stream'
  invokeAction: 'run',       // 同步端点后缀，默认 'invoke'
  fallbackToSync: false,     // 流式失败时不回退到同步
}
```

URL 构建规则：
- 用户配置的 `remoteUrl` 如果已带 action 后缀（如 `.../data_query_assistant:stream`），系统会自动剥离旧后缀，再拼接配置的 action
- 如果 URL 不含 `/capabilities/` 路径，系统会自动补全 `/api/v1/a2a/capabilities/{capabilityId}` 前缀

## Optional Dependencies

以下依赖为可选（`optionalDependencies`），仅在用到对应功能时需要安装：

| 依赖 | 用途 |
|------|------|
| `jsonpath-plus` | MCP api-bridge 响应提取 |
| `@apidevtools/swagger-parser` | OpenAPI 导入 |
| `fast-glob` | search_code 工具 |
| `turndown` / `html-to-text` | web_fetch 工具 HTML 转换 |
| `cheerio` / `axios` / `socks-proxy-agent` | web_fetch / http_request 工具 |

未安装时对应工具会在运行时报错，不影响其他功能。

## 构建

```bash
cd packages/agent-server
npm run build
```
