
Product
Introducing Repository Access Permissions and Custom Roles
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.
@snack-kit/porygon
Advanced tools
统一的 LLM Agent CLI 编程接口框架。通过 Facade + Adapter 模式,将 Claude Code、OpenCode 等 CLI 工具封装为一致的 TypeScript API。
npm add @snack-kit/porygon
前置条件:需要安装至少一个 CLI 后端:
claude 命令)opencode 命令)import { createPorygon } from "@snack-kit/porygon";
const porygon = createPorygon();
// 简单调用:发送 prompt,返回最终文本
const answer = await porygon.run({ prompt: "什么是闭包?" });
console.log(answer);
// 释放资源
await porygon.dispose();
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[] | 禁止使用的工具黑名单(后端级) |
streamPartialMessages | boolean | 是否启用 token 级流式输出(Claude CLI --include-partial-messages),默认 true |
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* → ... → result
stream_chunk — 增量文本片段,用于实时展示assistant — 单个 turn 的完整文本汇总。turnComplete: true 表示内容与 stream_chunk 重复tool_use — 工具调用;若带 output 字段则为工具执行结果result — 最终结果,包含完整文本和用量统计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 |
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>检查单个后端的健康状态。支持两种模式:
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 | 深度检测超时(毫秒) |
envVars | Record<string, string> | - | 注入到深度检测子进程的环境变量(用于隔离第三方后端凭证) |
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;Claude 启用 streamPartialMessages 时也为此模式)"chunked" — 适配器将完整 assistant 拆分为 stream_chunk + assistant(Claude 关闭 streamPartialMessages 时)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,
});
}
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 # 启动 Playground
EphemeralProcess.executeStreaming idle timeout 死循环 — 当子进程不响应 SIGTERM 或 stdout fd 被孙子进程持有时(容器内常见),idle timeout 触发后仅杀进程,readline 因等不到 stdout EOF 而无限阻塞,timeoutMs 形同虚设。修复后 timer 触发时主动 rl.close() 让 for-await 立即退出,并优先抛错不再等待 exitPromise,确保 timeoutMs 在任何场景下都能可靠生效executeStreaming AbortSignal 路径同源问题 — 用户主动 controller.abort() 时若子进程不响应 SIGTERM 且 stdout fd 残留,generator 同样卡死。修复后 onAbort 主动关闭 readline 并保持原 abort 静默退出语义,调用侧通过自身 AbortController 状态感知取消stream_chunk 与 assistant 块文本重复 — 当 streamPartialMessages 启用(--include-partial-messages)时,CLI 会通过 stream_event.text_delta 持续推送增量,而 assistant 事件同时携带完整文本块。旧实现仍会对每个 text 块再发一次 stream_chunk,导致下游累积文本(例如前缀式累加的 UI 面板)出现整段重复。修复后 ClaudeAdapter 在该模式下只保留 stream_event 的增量通道,assistant(turnComplete=true) 与 tool_use 行为保持不变mapClaudeEvent 新增可选 suppressAssistantTextChunks — 对外暴露抑制开关,ClaudeAdapter 依据 streamPartialMessages !== false 自动启用;未显式传入选项时完全保留旧版行为,API 向后兼容deepCheck() 适配器级 API 凭证验证 — ClaudeAdapter 新增 deepCheck() 方法,直接调用 Anthropic Messages API 验证凭证有效性,无需启动 CLI 子进程。支持 ANTHROPIC_AUTH_TOKEN、ANTHROPIC_API_KEY 两种凭证类型,OAuth token 和无显式凭证时自动 fallback 到 CLI 方式CheckBackendOptions.envVars — 深度检测支持注入环境变量,用于隔离第三方后端凭证验证场景CLAUDECODE 外额外清除 ANTHROPIC_* 和 CLAUDE_CODE_* 前缀变量,确保认证只来自显式传入的 envVars,避免宿主机凭证泄漏到第三方后端envVars 空字符串覆盖支持 — request.envVars 中值为空字符串时也会写入环境变量(之前被忽略),允许通过空值覆盖系统环境变量实现凭证隔离IAgentAdapter 接口扩展 — 新增可选 deepCheck() 方法定义,自定义适配器可实现此方法提供免 CLI 的凭证验证BackendConfig.streamPartialMessages 配置项(默认 true),启用后 Claude CLI 添加 --include-partial-messages 参数,实现真正的逐 token 流式输出(stream_event → text_delta)。此前 Claude 后端的文本输出需等整个 turn 完成后才一次性返回,对于长文本生成(如代码生成)场景体验较差,启用后文本会以 stream_chunk 消息逐步到达,用户可实时看到生成过程streamingMode 在启用 streamPartialMessages 时等效于 "delta" 模式,与 OpenCode 适配器行为一致timeoutMs 语义变更为空闲超时 — timeoutMs 不再表示进程的绝对超时时间,而是"空闲超时":进程无任何输出(stdout/stderr)超过此时间才触发终止。每次收到输出会重置计时器,更适合 LLM 调用耗时不可预测的场景300_000(5 分钟)调整为 120_000(2 分钟)timeoutMs 语义变更 — 从绝对超时改为空闲超时(idle timeout)。如果依赖旧的绝对超时行为,需要调整使用方式。对于模型思考时间可能较长的场景,建议适当调大此值defaults.timeoutMs 或请求参数中显式设置InteractiveSession 多轮对话封装 — 新增 InteractiveSession 类,封装 per-turn spawn + --resume 模式,提供透明的多轮对话体验。通过 porygon.session() 创建,调用 session.send(prompt) 自动管理 sessionId 续接EphemeralProcess 超时错误明确化 — 进程超时终止后抛出明确的 Process timed out after ${timeoutMs}ms 错误,替代之前模糊的退出码错误信息EphemeralProcess 内部新增 timedOut 状态标记,区分正常退出与超时终止场景cross-spawn 替换原生 child_process.spawn,解决 Windows 上因 PATH 解析、PATHEXT 扩展名(.exe、.cmd 等)导致的 ENOENT 错误。所有进程启动(EphemeralProcess、PersistentProcess)均已适配isAvailable() Windows 适配 — Claude adapter 的 CLI 存在性检测在 Windows 上使用 where 命令替代 whichcross-spawn — Node.js 生态标准的跨平台进程启动库,正确处理 Windows 上的命令解析、参数转义和 PATHEXT 查找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>,调用方需 awaitClaudeResultEvent 缺少 total_cost_usd 字段导致的 TS2322 类型错误result 事件中 inputTokens 和 outputTokens 始终为 0 的问题。Claude CLI 的 token 统计位于 event.usage.input_tokens / event.usage.output_tokens,而非顶层的 event.input_tokens / event.output_tokenscostUsd 始终为 undefined 的问题。Claude CLI 使用 total_cost_usd 字段名,而非 cost_usd-p <prompt>) 改为通过 stdin 传递 (--print + stdin pipe),修复超长 prompt 导致的偶发 403 "Request not allowed" 错误SpawnOptions 新增 stdinData 字段,EphemeralProcess.executeStreaming 支持向子进程 stdin 写入数据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.sessionIdmapClaudeEvent() 返回类型变更 — 从 AgentMessage | null 改为 AgentMessage[](仅影响直接调用 mapper 的代码)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 块claude -p --output-format stream-json)opencode serve + REST API + SSE)FAQs
Unified programmatic interface for LLM Agent CLI tools
We found that @snack-kit/porygon demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.