@snack-kit/porygon
v0.8.0
Published
Unified programmatic interface for LLM Agent CLI tools
Readme
Porygon
统一的 LLM Agent CLI 编程接口框架。通过 Facade + Adapter 模式,将 Claude Code、OpenCode 等 CLI 工具封装为一致的 TypeScript API。
安装
npm add @snack-kit/porygon前置条件:需要安装至少一个 CLI 后端:
- Claude Code (
claude命令) - OpenCode (
opencode命令)
快速上手
import { createPorygon } from "@snack-kit/porygon";
const porygon = createPorygon();
// 简单调用:发送 prompt,返回最终文本
const answer = await porygon.run({ prompt: "什么是闭包?" });
console.log(answer);
// 释放资源
await porygon.dispose();API 速查
createPorygon(config?): Porygon
工厂函数,创建 Porygon 实例。
const porygon = createPorygon({
defaultBackend: "claude", // "claude" | "opencode"
backends: {
claude: {
model: "sonnet",
interactive: false, // false = --dangerously-skip-permissions
cliPath: "/usr/local/bin/claude", // 自定义 CLI 路径
appendSystemPrompt: "用中文回答",
cwd: "/path/to/project",
proxy: { url: "http://127.0.0.1:7897" },
},
opencode: {
serverUrl: "http://localhost:39393",
apiKey: "sk-xxx",
},
},
defaults: {
timeoutMs: 300_000,
maxTurns: 50,
appendSystemPrompt: "请简洁回答",
disallowedTools: ["WebSearch"], // 全局禁用的工具
},
proxy: { url: "http://proxy:8080", noProxy: "localhost" },
});PorygonConfig 字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| defaultBackend | string | 默认后端名称 |
| backends | Record<string, BackendConfig> | 各后端独立配置 |
| defaults | { appendSystemPrompt?, timeoutMs?, maxTurns?, disallowedTools? } | 全局默认参数 |
| proxy | { url: string, noProxy?: string } | 全局代理(后端级 proxy 优先) |
BackendConfig 字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| model | string | 模型名称 |
| interactive | boolean | 是否交互模式,false 时 Claude 添加 --dangerously-skip-permissions |
| cliPath | string | CLI 可执行文件路径(如 Claude CLI 的自定义安装路径) |
| serverUrl | string | 远程服务地址(OpenCode serve 模式) |
| apiKey | string | API Key 认证 |
| appendSystemPrompt | string | 追加系统提示词 |
| proxy | ProxyConfig | 后端专属代理 |
| cwd | string | 工作目录 |
| disallowedTools | string[] | 禁止使用的工具黑名单(后端级) |
| options | Record<string, unknown> | 透传给后端的额外选项(向后兼容,推荐使用上方的显式字段) |
porygon.run(request): Promise<string>
发送 prompt,等待完成,返回最终结果文本。适合简单的一次性调用。
const result = await porygon.run({
prompt: "写一个 HTTP 服务器",
backend: "claude",
model: "opus",
});porygon.query(request): AsyncGenerator<AgentMessage>
流式查询,逐条 yield AgentMessage。适合需要实时输出或监控工具调用的场景。
for await (const msg of porygon.query({ prompt: "解释快速排序" })) {
switch (msg.type) {
case "system":
console.log("模型:", msg.model);
break;
case "stream_chunk":
process.stdout.write(msg.text); // 增量文本,实时输出
break;
case "assistant":
// turnComplete=true: 与前面 stream_chunk 内容重复,流式消费者应跳过
// turnComplete=false/undefined: 独立文本(如 run() 模式),需要处理
if (!msg.turnComplete) {
console.log("回复:", msg.text);
}
break;
case "tool_use":
console.log("工具调用:", msg.toolName, msg.input);
if (msg.output) console.log("工具结果:", msg.output);
break;
case "result":
console.log("完成", { cost: msg.costUsd, tokens: msg.inputTokens });
break;
case "error":
console.error("错误:", msg.message);
break;
}
}消息流顺序(两个后端统一):
system → stream_chunk* → assistant(turnComplete) → tool_use* → ... → resultstream_chunk— 增量文本片段,用于实时展示assistant— 单个 turn 的完整文本汇总。turnComplete: true表示内容与stream_chunk重复tool_use— 工具调用;若带output字段则为工具执行结果result— 最终结果,包含完整文本和用量统计
PromptRequest(请求参数)
run() 和 query() 共用同一参数类型:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| prompt | string | 是 | 提示词 |
| backend | string | 否 | 后端名称(默认取 config.defaultBackend) |
| model | string | 否 | 模型名称 |
| resume | string | 否 | 恢复会话的 session ID |
| systemPrompt | string | 否 | 系统提示词(替换模式,设置后 appendSystemPrompt 失效) |
| appendSystemPrompt | string | 否 | 系统提示词(追加模式,与 config 中的追加内容合并) |
| onlyTools | string[] | 否 | 仅允许使用的工具白名单(内部通过 getTools() 差集转为黑名单,与 disallowedTools 互斥,优先级更高) |
| disallowedTools | string[] | 否 | 禁止使用的工具黑名单 |
| maxTurns | number | 否 | 最大对话轮次 |
| timeoutMs | number | 否 | 超时毫秒数 |
| cwd | string | 否 | 工作目录 |
| envVars | Record<string, string> | 否 | 注入子进程的环境变量 |
| mcpServers | Record<string, McpServerConfig> | 否 | MCP 服务器配置 |
| backendOptions | Record<string, unknown> | 否 | 透传给后端的额外选项 |
配置合并策略(mergeRequest):
| 字段 | 策略 |
|------|------|
| model | request > backendConfig > 不设置 |
| timeoutMs | request > defaults |
| maxTurns | request > defaults |
| cwd | request > backendConfig |
| onlyTools | 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(设置后忽略 disallowedTools) |
| disallowedTools | defaults + backendConfig + request 三层叠加去重 |
| appendSystemPrompt | defaults + backendConfig + request 三层追加拼接(换行分隔)。若 systemPrompt 已设置则忽略全部 append |
AgentMessage(消息类型)
porygon.query() 返回的 AgentMessage 是一个联合类型。所有消息均包含 timestamp: number,可选 sessionId?: string 和 raw?: unknown(后端原始数据)。
| type | 接口 | 关键字段 |
|------|------|----------|
| "system" | AgentSystemMessage | model?, tools?, cwd? |
| "assistant" | AgentAssistantMessage | text, turnComplete? |
| "tool_use" | AgentToolUseMessage | toolName, input, output? |
| "stream_chunk" | AgentStreamChunkMessage | text |
| "result" | AgentResultMessage | text, durationMs?, costUsd?, inputTokens?, outputTokens? |
| "error" | AgentErrorMessage | message, code? |
porygon.checkBackend(backend, options?): Promise<HealthCheckResult>
检查单个后端的健康状态。支持两种模式:
- 轻量检测(默认):仅检查 CLI 是否存在 + 版本兼容性
- 深度检测(
deep: true):额外向模型发送一条测试消息,验证 Token/模型/配额是否真正可用
// 轻量检测:仅检查 CLI 存在性和版本
const result = await porygon.checkBackend("claude");
// { available: true, version: "1.2.0", supported: true }
// 深度检测:真实调用模型验证可用性
const deep = await porygon.checkBackend("claude", { deep: true, model: "haiku" });
// { available: true, version: "1.2.0", supported: true, modelVerified: true }
// { available: true, version: "1.2.0", supported: true, modelVerified: false, warnings: ["模型验证失败: ..."] }CheckBackendOptions:
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| deep | boolean | false | 启用深度检测,向模型发送测试消息 |
| model | string? | 后端默认 | 深度检测使用的模型 |
| timeoutMs | number | 15000 | 深度检测超时(毫秒) |
HealthCheckResult:
| 字段 | 类型 | 说明 |
|------|------|------|
| available | boolean | 后端是否可用 |
| version | string? | CLI 版本号 |
| supported | boolean? | 版本是否在测试范围内 |
| warnings | string[]? | 兼容性警告 |
| error | string? | 错误信息 |
| modelVerified | boolean? | 深度检测时模型是否真正响应 |
porygon.healthCheck(): Promise<Record<string, HealthCheckResult>>
对所有已注册后端进行健康检查(并行执行)。
const health = await porygon.healthCheck();
// { claude: { available: true, version: "1.2.0", supported: true }, opencode: { available: false, error: "..." } }porygon.use(direction, fn): () => void
注册拦截器,返回取消注册函数。
direction: "input" | "output"
InterceptorFn 签名:
type InterceptorFn = (
text: string,
context: InterceptorContext
) => string | boolean | undefined | Promise<string | boolean | undefined>;- 返回
string:替换文本 - 返回
false:拒绝消息(抛出InterceptorRejectedError) - 返回
true/undefined:不修改,传递原始文本
示例:
// 输入拦截:追加指令
const unuse = porygon.use("input", (text) => text + "\n请用 Markdown 格式回答");
// 输出拦截:过滤敏感信息
porygon.use("output", (text) => text.replace(/sk-[a-zA-Z0-9]{48}/g, "[REDACTED]"));
// 拒绝过长输入
porygon.use("input", (text) => { if (text.length > 10000) return false; });
// 取消注册
unuse();防护拦截器
内置的 prompt 注入防护工具函数。
createInputGuard(options?): InterceptorFn
检测并阻止常见 prompt 注入攻击(内置 13 条中英文模式)。
import { createInputGuard } from "@snack-kit/porygon";
// 使用内置规则
porygon.use("input", createInputGuard());
// 自定义配置
porygon.use("input", createInputGuard({
blockedPatterns: [/我是管理员/, /sudo mode/i],
action: "reject", // "reject"(拒绝)| "redact"(替换为 [REDACTED])
backends: ["claude"], // 仅对指定后端生效
customCheck: (text) => text.includes("bypass"),
}));createOutputGuard(options?): InterceptorFn
检测输出中的敏感关键词泄露。
import { createOutputGuard } from "@snack-kit/porygon";
porygon.use("output", createOutputGuard({
sensitiveKeywords: ["内部API密钥", "sk-xxxx"],
action: "redact",
}));GuardOptions:
| 字段 | 类型 | 说明 |
|------|------|------|
| blockedPatterns | RegExp[] | 额外阻止模式(输入侧) |
| sensitiveKeywords | string[] | 敏感关键词列表(输出侧) |
| action | "reject" | "redact" | 触发时的处理动作,输入默认 reject,输出默认 redact |
| customCheck | (text, context) => boolean | 自定义判定函数 |
| backends | string[] | 仅对指定后端生效 |
porygon.listSessions(backend?, options?): Promise<SessionInfo[]>
列出指定后端的历史会话。
const sessions = await porygon.listSessions("claude", { limit: 10 });
// SessionInfo: { sessionId, backend, summary?, lastModified, cwd?, metadata? }porygon.abort(backend, sessionId): void
中止正在运行的查询。
porygon.settings(backend, newSettings?): Promise<Record<string, unknown>>
读取或更新后端设置。
const settings = await porygon.settings("claude");
await porygon.settings("claude", { model: "opus" });porygon.listModels(backend?): Promise<ModelInfo[]>
获取后端可用模型列表。
const models = await porygon.listModels("claude");
// ModelInfo: { id: string, name?: string, provider?: string }porygon.getTools(force?): Promise<string[]>
获取当前可用的工具列表。首次调用通过发起最小化对话(maxTurns: 1)从 system 事件中提取,结果会被缓存,后续调用直接返回缓存。
const tools = await porygon.getTools();
console.log(tools);
// ["Bash", "Read", "Edit", "Write", "Glob", "Grep", "mcp__xxx__yyy", ...]
// 强制刷新缓存
const fresh = await porygon.getTools(true);仅 Claude 后端支持此能力,其他后端返回空数组。
porygon.getCapabilities(backend?): AdapterCapabilities
获取后端能力声明。
const caps = porygon.getCapabilities("claude");
// {
// features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | ...>,
// streamingMode: "delta" | "chunked",
// outputFormats: string[],
// testedVersionRange: string,
// }streamingMode:
"delta"— 后端原生产生增量stream_chunk事件(OpenCode)"chunked"— 适配器将完整 assistant 拆分为stream_chunk+assistant(Claude)
porygon.dispose(): Promise<void>
终止所有进程、中止所有查询、清空缓存。使用完毕后必须调用。
错误处理
所有错误继承自 PorygonError,包含 code 字段:
import { PorygonError, AdapterNotFoundError } from "@snack-kit/porygon";
try {
await porygon.run({ prompt: "hi", backend: "nonexistent" });
} catch (err) {
if (err instanceof AdapterNotFoundError) {
console.log(err.code); // "ADAPTER_NOT_FOUND"
}
}| 错误类 | code | 场景 |
|--------|------|------|
| AdapterNotFoundError | ADAPTER_NOT_FOUND | 后端未注册 |
| AdapterNotAvailableError | ADAPTER_NOT_AVAILABLE | CLI 不可用 |
| AdapterIncompatibleError | ADAPTER_INCOMPATIBLE | 版本不兼容 |
| SessionNotFoundError | SESSION_NOT_FOUND | 会话不存在 |
| InterceptorRejectedError | INTERCEPTOR_REJECTED | 拦截器拒绝 |
| AgentExecutionError | AGENT_EXECUTION_ERROR | 执行异常 |
| AgentTimeoutError | AGENT_TIMEOUT | 超时 |
| ConfigValidationError | CONFIG_VALIDATION_ERROR | 配置校验失败 |
事件
Porygon 继承自 EventEmitter:
porygon.on("health:degraded", (backend: string, warning: string) => {
console.warn(`${backend} 降级: ${warning}`);
});| 事件 | 参数 | 说明 |
|------|------|------|
| health:degraded | (backend, warning) | 后端版本不兼容时触发 |
完整导出列表
// 核心
export { Porygon, createPorygon } from "@snack-kit/porygon";
export type { PorygonEvents, HealthCheckResult, CheckBackendOptions } from "@snack-kit/porygon";
// 类型
export type {
PorygonConfig, BackendConfig, ProxyConfig,
PromptRequest, McpServerConfig,
AgentMessage, AgentSystemMessage, AgentAssistantMessage,
AgentToolUseMessage, AgentStreamChunkMessage, AgentResultMessage, AgentErrorMessage,
AdapterCapabilities, CompatibilityResult, SessionInfo, SessionListOptions, ModelInfo,
InterceptorFn, InterceptorDirection, InterceptorContext,
GuardOptions, GuardAction,
IAgentAdapter,
} from "@snack-kit/porygon";
// 错误
export {
PorygonError, AdapterNotFoundError, AdapterNotAvailableError,
AdapterIncompatibleError, SessionNotFoundError, InterceptorRejectedError,
AgentExecutionError, AgentTimeoutError, ConfigValidationError,
} from "@snack-kit/porygon";
// 防护拦截器
export { createInputGuard, createOutputGuard } from "@snack-kit/porygon";
// 交互式会话
export { InteractiveSession } from "@snack-kit/porygon";
// 适配器(自定义扩展用)
export { AbstractAgentAdapter, ClaudeAdapter, OpenCodeAdapter } from "@snack-kit/porygon";典型使用模式
带防护的流式调用
import { createPorygon, createInputGuard, createOutputGuard } from "@snack-kit/porygon";
const porygon = createPorygon({
defaultBackend: "claude",
backends: {
claude: { model: "sonnet", interactive: false },
},
});
porygon.use("input", createInputGuard());
porygon.use("output", createOutputGuard({ sensitiveKeywords: ["SECRET"] }));
for await (const msg of porygon.query({ prompt: "解释 async/await" })) {
if (msg.type === "stream_chunk") process.stdout.write(msg.text);
if (msg.type === "result") console.log("\n完成", msg.costUsd);
}
await porygon.dispose();恢复会话
const sessions = await porygon.listSessions("claude", { limit: 1 });
if (sessions.length > 0) {
const answer = await porygon.run({
prompt: "继续上次的工作",
resume: sessions[0].sessionId,
});
}注入环境变量与 MCP 服务器
const answer = await porygon.run({
prompt: "查找相关文档后回答",
envVars: { CLAUDE_CODE_OAUTH_TOKEN: "token-xxx" },
mcpServers: {
"context7": { command: "npx", args: ["-y", "@context7/mcp"] },
},
});工具控制
// 查询所有可用工具
const tools = await porygon.getTools();
console.log(tools); // ["Bash", "Read", "Edit", ...]
// 黑名单:禁用危险工具
await porygon.run({
prompt: "分析代码",
disallowedTools: ["Bash", "Edit", "Write"],
});
// 白名单:只允许只读工具(内部自动转为黑名单,getTools 结果有缓存)
await porygon.run({
prompt: "阅读代码并回答",
onlyTools: ["Read", "Grep", "Glob"],
});
// 配置级黑名单:全局 + 后端级 + 请求级三层叠加
const porygon = createPorygon({
defaults: { disallowedTools: ["WebSearch"] },
backends: {
claude: {
interactive: false,
disallowedTools: ["Bash"],
},
},
});
// 此时 disallowedTools = ["WebSearch", "Bash"],无需每次请求重复传入健康检查后选择可用后端
// 轻量检查:CLI 存在性 + 版本
const result = await porygon.checkBackend("claude");
if (!result.available) console.error(result.error);
// 深度检查:验证模型真正可用(Token/配额/模型名)
const deep = await porygon.checkBackend("claude", { deep: true, model: "haiku" });
if (deep.modelVerified) console.log("模型验证通过");
// 批量检查所有后端
const health = await porygon.healthCheck();
const backend = health.claude?.available ? "claude" : "opencode";
const answer = await porygon.run({ prompt: "hello", backend });架构
Porygon (Facade)
├── InterceptorManager # 输入/输出拦截器流水线
├── ProcessManager # 子进程生命周期管理
├── SessionManager # 会话缓存与委托
└── Adapters
├── ClaudeAdapter # claude -p --output-format stream-json
└── OpenCodeAdapter # opencode serve + REST API + SSE适配器能力对比:
| 能力 | Claude | OpenCode |
|------|--------|----------|
| streaming | chunked | delta |
| session-resume | ✓ | ✓ |
| system-prompt | ✓ | ✓ |
| tool-restriction | ✓ | - |
| mcp | ✓ | - |
| subagents | ✓ | - |
| worktree | ✓ | - |
| serve-mode | - | ✓ |
开发
npm install
npm run build # 构建(ESM + CJS + DTS)
npm test # 运行测试
npm run dev # watch 模式构建
npm run playground # 启动 PlaygroundChangelog
v0.8.0
改进
timeoutMs语义变更为空闲超时 —timeoutMs不再表示进程的绝对超时时间,而是"空闲超时":进程无任何输出(stdout/stderr)超过此时间才触发终止。每次收到输出会重置计时器,更适合 LLM 调用耗时不可预测的场景- 默认空闲超时调整 — 默认值从
300_000(5 分钟)调整为120_000(2 分钟)
破坏性变更
timeoutMs语义变更 — 从绝对超时改为空闲超时(idle timeout)。如果依赖旧的绝对超时行为,需要调整使用方式。对于模型思考时间可能较长的场景,建议适当调大此值- 默认超时时间缩短 — 从 5 分钟缩短至 2 分钟。如需更长超时,请在
defaults.timeoutMs或请求参数中显式设置
v0.7.0
新特性
InteractiveSession多轮对话封装 — 新增InteractiveSession类,封装 per-turn spawn +--resume模式,提供透明的多轮对话体验。通过porygon.session()创建,调用session.send(prompt)自动管理 sessionId 续接EphemeralProcess超时错误明确化 — 进程超时终止后抛出明确的Process timed out after ${timeoutMs}ms错误,替代之前模糊的退出码错误信息
改进
EphemeralProcess内部新增timedOut状态标记,区分正常退出与超时终止场景
v0.6.0
改进
- Windows 跨平台兼容 — 使用
cross-spawn替换原生child_process.spawn,解决 Windows 上因 PATH 解析、PATHEXT 扩展名(.exe、.cmd等)导致的ENOENT错误。所有进程启动(EphemeralProcess、PersistentProcess)均已适配 isAvailable()Windows 适配 — Claude adapter 的 CLI 存在性检测在 Windows 上使用where命令替代which
新增依赖
cross-spawn— Node.js 生态标准的跨平台进程启动库,正确处理 Windows 上的命令解析、参数转义和 PATHEXT 查找
v0.5.0
新特性
porygon.getTools(force?)— 获取当前可用的工具列表(包括内置工具和 MCP 插件工具)。首次调用通过最小化对话获取并缓存,后续直接返回缓存,传入force: true强制刷新IAgentAdapter.getTools?(force?)— 适配器接口新增可选方法,支持缓存与强制刷新onlyTools(请求参数) — 仅允许使用的工具白名单,内部通过getTools()获取全量列表后差集计算转为disallowedTools,工具缓存避免重复对话开销- 配置级工具黑名单 —
BackendConfig和defaults新增disallowedTools字段,支持全局或后端级统一禁用工具,三层叠加去重 checkBackend(backend, options?)深度检测模式 — 新增第二个参数CheckBackendOptions,传入{ deep: true }时在基础 CLI/版本检查通过后,向模型发送一条极短测试消息(maxTurns: 1),验证 Token、模型、配额是否真正可用CheckBackendOptions— 新增选项类型,支持deep(启用深度检测)、model(指定验证模型)、timeoutMs(深度检测超时,默认 15s)HealthCheckResult.modelVerified— 新增字段,深度检测时标识模型是否真正响应
破坏性变更
PromptRequest.allowedTools移除 — Claude CLI 的--allowedTools实际语义为"免确认"而非"限制范围",已移除避免误用。替代方案:onlyTools(白名单,自动转黑名单)或disallowedTools(黑名单)porygon.session()返回类型变更 — 从InteractiveSession改为Promise<InteractiveSession>,调用方需await
Bug Fixes
- Claude adapter 类型定义: 修复
ClaudeResultEvent缺少total_cost_usd字段导致的 TS2322 类型错误
v0.4.0
Bug Fixes
- Claude adapter: 修复
result事件中inputTokens和outputTokens始终为 0 的问题。Claude CLI 的 token 统计位于event.usage.input_tokens/event.usage.output_tokens,而非顶层的event.input_tokens/event.output_tokens - Claude adapter: 修复
costUsd始终为undefined的问题。Claude CLI 使用total_cost_usd字段名,而非cost_usd
v0.3.0
Bug Fixes
- Claude adapter: 将 prompt 从 CLI 参数 (
-p <prompt>) 改为通过 stdin 传递 (--print+ stdin pipe),修复超长 prompt 导致的偶发 403 "Request not allowed" 错误
改进
SpawnOptions新增stdinData字段,EphemeralProcess.executeStreaming支持向子进程 stdin 写入数据
v0.2.0
新特性
checkBackend(backend)— 新增单后端健康检查方法,无需检查全部后端HealthCheckResult— 健康检查返回扁平化结构{ available, version?, supported?, warnings?, error? }BackendConfig.cliPath— Claude CLI 自定义路径,有类型提示BackendConfig.interactive— 布尔值控制是否跳过权限确认BackendConfig.serverUrl— OpenCode 远程服务地址(顶级字段)BackendConfig.apiKey— API Key 认证(顶级字段)AgentAssistantMessage.turnComplete— 标记 assistant 消息为 turn 完整文本汇总,流式消费者可据此跳过重复文本AdapterCapabilities.streamingMode— 声明后端的流式模式("delta"或"chunked")IAgentAdapter.deleteSession?— 可选的会话删除方法(接口已定义,适配器待实现)tool_result映射 — Claude 的tool_result内容块现在映射为带output字段的tool_use消息session_id自动提取 — Claude 原始事件中的session_id自动映射到AgentMessage.sessionId- CJS 输出 — 同时构建 ESM 和 CJS 格式,支持 Electron 等 CJS 环境
破坏性变更
mapClaudeEvent()返回类型变更 — 从AgentMessage | null改为AgentMessage[](仅影响直接调用 mapper 的代码)- Claude 消息流变更 —
assistant事件拆分为stream_chunk[]+assistant(turnComplete: true)。之前同时累加assistant和stream_chunk文本的代码需要调整:只累加stream_chunk,或检查turnComplete标记 healthCheck()返回类型变更 — 从{ available, compatibility: CompatibilityResult | null, error? }改为HealthCheckResult({ available, version?, supported?, warnings?, error? }),移除嵌套的compatibility字段AdapterCapabilities新增必填字段 —streamingMode: "delta" | "chunked"为必填,自定义适配器需添加
改进
mergeRequest()添加详细 JSDoc 注释说明配置合并策略mapAssistantContent()支持多内容块拆分,不再丢失 tool_use 块
v0.1.0 — 初始版本
- 基础 Facade + Adapter 架构
- Claude Code CLI 适配器(
claude -p --output-format stream-json) - OpenCode 适配器(
opencode serve+ REST API + SSE) - 输入/输出拦截器流水线
- 防护拦截器(prompt 注入检测、敏感信息过滤)
- 进程生命周期管理(EphemeralProcess / PersistentProcess)
- 会话管理与恢复
- Zod 配置校验
- ESM 输出
