Skip to content

Cell - AI Coding Agent Component ​

Not translated yet

This page is only written in Chinese so far; the original is shown below.

在 Cell 应用里运行 pi coding agent 的会话。pi 负责模型调用、工具(read、bash、edit、write 等)、会话与上下文压缩;本组件把它接进 Cell:

  • 会话和模型运行时由 DI 容器管理,应用停止时自动释放会话;
  • 配置写在 cell.yml 的 cell.ai.codingAgent 下,选项名与 pi 的 createAgentSession() 一致;
  • 工具写成 AgentTool 组件(与 @celljs/ai-agent 共用),pi 扩展写成 AgentExtension 组件,都可以直接用注入的服务;
  • 按配置启用 pi 的 MCP 客户端、codemode 和 tool_search;
  • 事件按 pi JSON/RPC 模式的格式输出,可直接作为 Server-Sent Events 返回给前端。

要求 ​

Node.js >= 22.19 或 Bun(pi 的要求)。pi 是 ES module 包,本组件用动态 import() 加载它,CommonJS 和 ESM 两种构建都可用,只在第一次创建会话时加载。

安装 ​

bash
bun add @celljs/ai-coding-agent

配置 ​

yaml
cell:
  ai:
    codingAgent:
      cwd: /srv/repo                 # 会话的工作目录,工具在这里读写;默认进程当前目录
      model: anthropic/claude-sonnet-4-5   # <provider>/<model id>;默认取 pi 的设置,再取第一个有凭据的模型
      thinkingLevel: medium
      tools: [read, grep, find, ls]  # 工具白名单,支持 *;默认 read、bash、edit、write 加扩展注册的工具
      appendSystemPrompt: Answer in Chinese.
      apiKeys:                       # 只放进内存,不写 pi 的 auth.json
        anthropic: "${ env.MY_ANTHROPIC_KEY }"
配置默认说明
cwd进程当前目录会话工作目录,也是发现项目资源(AGENTS.md、.pi/)的位置
agentDir~/.pi/agentpi 的全局目录:auth.json、models.json、settings.json、扩展、技能
model / thinkingLevelpi 的设置模型写成 <provider>/<model id>,找不到时报 AgentModelNotFoundError 并列出可用模型
tools / excludeToolspi 的默认工具白名单 / 黑名单
systemPrompt / appendSystemPrompt—替换 / 追加系统提示词
discoverResourcestrue像 pi CLI 一样从 cwd 和 agentDir 加载扩展、技能、提示词模板和 AGENTS.md
persistSessionsfalse同时把会话写成 pi CLI 的 JSONL 文件,pi --resume 能看到
allowModelNetworkfalse创建模型运行时时是否联网刷新模型目录
mcp.enabled / mcp.serversfalse / —启用 pi 的 MCP 客户端:连接 agentDir(以及受信任项目)mcp.json 里的服务器,再加上 servers(格式同 mcpServers)
codemodeoffon / only:模型写 JavaScript,在沙箱里调用工具
toolSearchfalse启用 tool_search,让模型查找未声明的工具
apiKeys—按 provider 设置 API key,空值忽略;cell.ai.pi.apiKeys(@celljs/ai-pi)也用于会话,这里的同名项优先;pi 也会读 ANTHROPIC_API_KEY 等环境变量

使用 ​

创建会话并提问 ​

AgentSessionManager.create() 返回 pi 的 AgentSession,之后直接用 pi 的 API(prompt、steer、followUp、abort、subscribe、messages ……):

typescript
import { Autowired, Component } from '@celljs/core';
import { AgentSessionManager } from '@celljs/ai-coding-agent';

@Component()
export class ReviewService {

    @Autowired(AgentSessionManager)
    protected readonly sessionManager: AgentSessionManager;

    async review(): Promise<string | undefined> {
        const session = await this.sessionManager.create({ tools: [ 'read', 'grep', 'find', 'ls' ] });
        try {
            await session.prompt('Review the changes in src/ and list the risks.');
            return session.getLastAssistantText();
        } finally {
            this.sessionManager.dispose(session.sessionId);
        }
    }
}

create() 的参数(cwd、model、thinkingLevel、tools、excludeTools、systemPrompt、appendSystemPrompt)覆盖 cell.ai.codingAgent 中的同名配置。会话按 pi 的 sessionId 保存在内存里:get(id)(不存在时抛 AgentSessionNotFoundError)、find(id)、info(id)、list()、dispose(id)。

用 Cell 组件写工具 ​

工具写成 @celljs/ai-agent 的 AgentTool 组件(本包也导出 AgentTool),工具里可以直接用注入的服务;同一个组件既是编码会话的工具,也是 AgentFactory 创建的通用 agent 的工具:

typescript
import { AgentTool } from '@celljs/ai-coding-agent';
import { Autowired, Component } from '@celljs/core';
import { TicketService } from './ticket-service';

@Component(AgentTool)
export class CreateTicketTool implements AgentTool {
    readonly name = 'create_ticket';
    readonly label = 'Create ticket';
    readonly description = 'Creates a ticket in the issue tracker.';
    readonly parameters = { type: 'object', properties: { title: { type: 'string' } }, required: [ 'title' ] };

    @Autowired(TicketService)
    protected readonly ticketService: TicketService;

    async execute(_toolCallId: string, params: any) {
        const ticket = await this.ticketService.create(params.title);
        return { content: [ { type: 'text' as const, text: `Created ${ticket.id}` } ], details: {} };
    }
}

parameters 是 JSON Schema;工具失败时抛异常。配置了 tools 白名单时,工具名要列进去。

用 Cell 组件写 pi 扩展 ​

工具以外的扩展能力(命令、事件处理器、拦截工具调用、注册 MCP 服务器等,见 pi 文档 docs/extensions.md)写成 AgentExtension,register 在每个新会话里拿到 pi 的 ExtensionAPI:

typescript
import { AgentExtension, type ExtensionAPI } from '@celljs/ai-coding-agent';
import { Autowired, Component, Logger } from '@celljs/core';

@Component(AgentExtension)
export class AuditExtension implements AgentExtension {

    @Autowired(Logger)
    protected readonly logger: Logger;

    register(pi: ExtensionAPI): void {
        pi.on('tool_call', async event => {
            this.logger.info(`agent calls ${event.toolName}`);
            return event.toolName === 'bash' ? { block: true, reason: 'bash is disabled' } : undefined;
        });
    }
}

register 抛错时创建会话失败,报 AgentExtensionError;从磁盘发现的扩展出错只记警告。

pi CLI 默认加载这三个扩展,SDK 默认不加载;在配置里打开即可。MCP 服务器的工具名是 mcp__<服务器>__<工具>,exposure: direct 时直接声明给模型,默认(codemode)时只能在 codemode 脚本里调用:

yaml
cell:
  ai:
    codingAgent:
      codemode: "on"
      mcp:
        enabled: true
        servers:
          docs:
            url: https://example.com/mcp
            headers:
              Authorization: "Bearer ${ env.DOCS_TOKEN }"
          filesystem:
            command: npx
            args: [-y, "@modelcontextprotocol/server-filesystem", "."]
            exposure: direct

配置了 tools 白名单时,要把 codemode、tool_search 或 mcp__* 也列进去。AgentExtension 里也可以用 pi.registerMcpServer(name, config) 按需注册服务器。

以 Server-Sent Events 返回事件 ​

AgentEventUtil.stream(session, response, run) 在 run 执行期间把会话事件写成 SSE,结束后关闭响应;run 失败时写一个 error 事件:

typescript
import { AgentEventUtil, AgentSessionManager, type EventStreamResponse } from '@celljs/ai-coding-agent';
import { Autowired } from '@celljs/core';
import { Body, Controller, Param, Post, Res } from '@celljs/mvc/lib/node';

@Controller('sessions')
export class AgentController {

    @Autowired(AgentSessionManager)
    protected readonly sessionManager: AgentSessionManager;

    @Post(':id/prompt')
    async prompt(@Param('id') id: string, @Body('text') text: string, @Res() response: EventStreamResponse): Promise<void> {
        const session = this.sessionManager.get(id);
        await AgentEventUtil.stream(session, response, () => session.prompt(text));
    }
}

每帧是 event: <type>\ndata: <JSON>,JSON 与 pi --mode json 的事件相同(pi 文档 docs/json.md):message_update 只带增量和 usage,完整消息在 message_end 里,agent_settled 表示这轮结束。另有本组件加的 { "type": "error", "message": "..." }。客户端断开不会中止会话,需要时调用 session.abort()。完整的增删查、提问、中止接口见 examples/coding-agent-app。

配置模型运行时 ​

所有会话共享一个 pi ModelRuntime(ModelRuntimeProvider)。要注册自定义 provider、从密钥服务读取凭据,实现 ModelRuntimeContribution:

typescript
import { type ModelRuntime, ModelRuntimeContribution } from '@celljs/ai-coding-agent';
import { Autowired, Component } from '@celljs/core';
import { SecretStore } from './secret-store';

@Component(ModelRuntimeContribution)
export class GatewayModels implements ModelRuntimeContribution {

    @Autowired(SecretStore)
    protected readonly secrets: SecretStore;

    async configure(runtime: ModelRuntime): Promise<void> {
        await runtime.setRuntimeApiKey('openai', await this.secrets.get('openai'));
    }
}

测试 ​

@celljs/ai-coding-agent/lib/node/test 提供基于 pi faux provider 的脚本化模型,不联网、不读写 ~/.pi:

typescript
import { AgentSessionManager } from '@celljs/ai-coding-agent';
import { createTestContainer } from '@celljs/ai-coding-agent/lib/node/test';

const { container, faux } = await createTestContainer({ tools: [ 'create_ticket' ] }, myModule);
faux.reply({ tool: 'create_ticket', args: { title: 'Fix login' } }, 'Created.');
const session = await container.get<AgentSessionManager>(AgentSessionManager).create();
await session.prompt('File a ticket for the login bug');

createTestContainer 把会话的 cwd 和 agentDir 放在一个新临时目录里,默认模型是 FauxModels.MODEL,并关闭资源发现。

安全 ​

pi 以服务进程的权限执行工具(读写文件、运行命令),没有沙箱。通过 HTTP 暴露会话时要加鉴权(如 @celljs/security),用 tools 只开放需要的工具,并把 cwd 限定在允许 agent 访问的目录;不要让客户端传入 cwd。详见 pi 文档 docs/security.md。

构建产物 ​

pi 在 cell build 时保持为外部依赖(cell.optionalDependencies),连同它的依赖复制到 .cell/output/server/node_modules(约 160 MB),产物目录可以单独部署。