@snack-kit/porygon 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -43
- package/dist/index.cjs +2192 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +812 -0
- package/dist/index.d.ts +109 -12
- package/dist/index.js +253 -67
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -39,10 +39,16 @@ const porygon = createPorygon({
|
|
|
39
39
|
backends: {
|
|
40
40
|
claude: {
|
|
41
41
|
model: "sonnet",
|
|
42
|
+
interactive: false, // false = --dangerously-skip-permissions
|
|
43
|
+
cliPath: "/usr/local/bin/claude", // 自定义 CLI 路径
|
|
42
44
|
appendSystemPrompt: "用中文回答",
|
|
43
45
|
cwd: "/path/to/project",
|
|
44
46
|
proxy: { url: "http://127.0.0.1:7897" },
|
|
45
47
|
},
|
|
48
|
+
opencode: {
|
|
49
|
+
serverUrl: "http://localhost:39393",
|
|
50
|
+
apiKey: "sk-xxx",
|
|
51
|
+
},
|
|
46
52
|
},
|
|
47
53
|
defaults: {
|
|
48
54
|
timeoutMs: 300_000,
|
|
@@ -67,9 +73,14 @@ const porygon = createPorygon({
|
|
|
67
73
|
| 字段 | 类型 | 说明 |
|
|
68
74
|
|------|------|------|
|
|
69
75
|
| `model` | `string` | 模型名称 |
|
|
76
|
+
| `interactive` | `boolean` | 是否交互模式,`false` 时 Claude 添加 `--dangerously-skip-permissions` |
|
|
77
|
+
| `cliPath` | `string` | CLI 可执行文件路径(如 Claude CLI 的自定义安装路径) |
|
|
78
|
+
| `serverUrl` | `string` | 远程服务地址(OpenCode serve 模式) |
|
|
79
|
+
| `apiKey` | `string` | API Key 认证 |
|
|
70
80
|
| `appendSystemPrompt` | `string` | 追加系统提示词 |
|
|
71
81
|
| `proxy` | `ProxyConfig` | 后端专属代理 |
|
|
72
82
|
| `cwd` | `string` | 工作目录 |
|
|
83
|
+
| `options` | `Record<string, unknown>` | 透传给后端的额外选项(向后兼容,推荐使用上方的显式字段) |
|
|
73
84
|
|
|
74
85
|
---
|
|
75
86
|
|
|
@@ -98,13 +109,18 @@ for await (const msg of porygon.query({ prompt: "解释快速排序" })) {
|
|
|
98
109
|
console.log("模型:", msg.model);
|
|
99
110
|
break;
|
|
100
111
|
case "stream_chunk":
|
|
101
|
-
process.stdout.write(msg.text); //
|
|
112
|
+
process.stdout.write(msg.text); // 增量文本,实时输出
|
|
102
113
|
break;
|
|
103
114
|
case "assistant":
|
|
104
|
-
|
|
115
|
+
// turnComplete=true: 与前面 stream_chunk 内容重复,流式消费者应跳过
|
|
116
|
+
// turnComplete=false/undefined: 独立文本(如 run() 模式),需要处理
|
|
117
|
+
if (!msg.turnComplete) {
|
|
118
|
+
console.log("回复:", msg.text);
|
|
119
|
+
}
|
|
105
120
|
break;
|
|
106
121
|
case "tool_use":
|
|
107
122
|
console.log("工具调用:", msg.toolName, msg.input);
|
|
123
|
+
if (msg.output) console.log("工具结果:", msg.output);
|
|
108
124
|
break;
|
|
109
125
|
case "result":
|
|
110
126
|
console.log("完成", { cost: msg.costUsd, tokens: msg.inputTokens });
|
|
@@ -116,6 +132,17 @@ for await (const msg of porygon.query({ prompt: "解释快速排序" })) {
|
|
|
116
132
|
}
|
|
117
133
|
```
|
|
118
134
|
|
|
135
|
+
**消息流顺序**(两个后端统一):
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
system → stream_chunk* → assistant(turnComplete) → tool_use* → ... → result
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- `stream_chunk` — 增量文本片段,用于实时展示
|
|
142
|
+
- `assistant` — 单个 turn 的完整文本汇总。`turnComplete: true` 表示内容与 `stream_chunk` 重复
|
|
143
|
+
- `tool_use` — 工具调用;若带 `output` 字段则为工具执行结果
|
|
144
|
+
- `result` — 最终结果,包含完整文本和用量统计
|
|
145
|
+
|
|
119
146
|
---
|
|
120
147
|
|
|
121
148
|
### PromptRequest(请求参数)
|
|
@@ -139,16 +166,15 @@ for await (const msg of porygon.query({ prompt: "解释快速排序" })) {
|
|
|
139
166
|
| `mcpServers` | `Record<string, McpServerConfig>` | 否 | MCP 服务器配置 |
|
|
140
167
|
| `backendOptions` | `Record<string, unknown>` | 否 | 透传给后端的额外选项 |
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
**配置合并策略**(`mergeRequest`):
|
|
143
170
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```
|
|
171
|
+
| 字段 | 策略 |
|
|
172
|
+
|------|------|
|
|
173
|
+
| `model` | request > backendConfig > 不设置 |
|
|
174
|
+
| `timeoutMs` | request > defaults |
|
|
175
|
+
| `maxTurns` | request > defaults |
|
|
176
|
+
| `cwd` | request > backendConfig |
|
|
177
|
+
| `appendSystemPrompt` | defaults + backendConfig + request 三层追加拼接(换行分隔)。若 `systemPrompt` 已设置则忽略全部 append |
|
|
152
178
|
|
|
153
179
|
---
|
|
154
180
|
|
|
@@ -159,7 +185,7 @@ interface McpServerConfig {
|
|
|
159
185
|
| type | 接口 | 关键字段 |
|
|
160
186
|
|------|------|----------|
|
|
161
187
|
| `"system"` | `AgentSystemMessage` | `model?`, `tools?`, `cwd?` |
|
|
162
|
-
| `"assistant"` | `AgentAssistantMessage` | `text` |
|
|
188
|
+
| `"assistant"` | `AgentAssistantMessage` | `text`, `turnComplete?` |
|
|
163
189
|
| `"tool_use"` | `AgentToolUseMessage` | `toolName`, `input`, `output?` |
|
|
164
190
|
| `"stream_chunk"` | `AgentStreamChunkMessage` | `text` |
|
|
165
191
|
| `"result"` | `AgentResultMessage` | `text`, `durationMs?`, `costUsd?`, `inputTokens?`, `outputTokens?` |
|
|
@@ -167,6 +193,39 @@ interface McpServerConfig {
|
|
|
167
193
|
|
|
168
194
|
---
|
|
169
195
|
|
|
196
|
+
### `porygon.checkBackend(backend): Promise<HealthCheckResult>`
|
|
197
|
+
|
|
198
|
+
检查单个后端的健康状态。
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const result = await porygon.checkBackend("claude");
|
|
202
|
+
// { available: true, version: "1.2.0", supported: true }
|
|
203
|
+
// { available: false, error: "command not found" }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**HealthCheckResult:**
|
|
207
|
+
|
|
208
|
+
| 字段 | 类型 | 说明 |
|
|
209
|
+
|------|------|------|
|
|
210
|
+
| `available` | `boolean` | 后端是否可用 |
|
|
211
|
+
| `version` | `string?` | CLI 版本号 |
|
|
212
|
+
| `supported` | `boolean?` | 版本是否在测试范围内 |
|
|
213
|
+
| `warnings` | `string[]?` | 兼容性警告 |
|
|
214
|
+
| `error` | `string?` | 错误信息 |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### `porygon.healthCheck(): Promise<Record<string, HealthCheckResult>>`
|
|
219
|
+
|
|
220
|
+
对所有已注册后端进行健康检查(并行执行)。
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
const health = await porygon.healthCheck();
|
|
224
|
+
// { claude: { available: true, version: "1.2.0", supported: true }, opencode: { available: false, error: "..." } }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
170
229
|
### `porygon.use(direction, fn): () => void`
|
|
171
230
|
|
|
172
231
|
注册拦截器,返回取消注册函数。
|
|
@@ -186,17 +245,6 @@ type InterceptorFn = (
|
|
|
186
245
|
- 返回 `false`:拒绝消息(抛出 `InterceptorRejectedError`)
|
|
187
246
|
- 返回 `true` / `undefined`:不修改,传递原始文本
|
|
188
247
|
|
|
189
|
-
**InterceptorContext:**
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
interface InterceptorContext {
|
|
193
|
-
direction: "input" | "output";
|
|
194
|
-
backend: string;
|
|
195
|
-
sessionId?: string;
|
|
196
|
-
messageType?: string; // 仅 output 方向
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
248
|
**示例:**
|
|
201
249
|
|
|
202
250
|
```ts
|
|
@@ -263,20 +311,6 @@ porygon.use("output", createOutputGuard({
|
|
|
263
311
|
|
|
264
312
|
---
|
|
265
313
|
|
|
266
|
-
### `porygon.healthCheck()`
|
|
267
|
-
|
|
268
|
-
对所有已注册后端进行健康检查(并行执行)。
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
const health = await porygon.healthCheck();
|
|
272
|
-
// {
|
|
273
|
-
// claude: { available: true, compatibility: { version: "1.2.0", supported: true, warnings: [] } },
|
|
274
|
-
// opencode: { available: false, compatibility: null, error: "..." }
|
|
275
|
-
// }
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
314
|
### `porygon.listSessions(backend?, options?): Promise<SessionInfo[]>`
|
|
281
315
|
|
|
282
316
|
列出指定后端的历史会话。
|
|
@@ -324,11 +358,16 @@ const models = await porygon.listModels("claude");
|
|
|
324
358
|
const caps = porygon.getCapabilities("claude");
|
|
325
359
|
// {
|
|
326
360
|
// features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | ...>,
|
|
361
|
+
// streamingMode: "delta" | "chunked",
|
|
327
362
|
// outputFormats: string[],
|
|
328
363
|
// testedVersionRange: string,
|
|
329
364
|
// }
|
|
330
365
|
```
|
|
331
366
|
|
|
367
|
+
**`streamingMode`:**
|
|
368
|
+
- `"delta"` — 后端原生产生增量 `stream_chunk` 事件(OpenCode)
|
|
369
|
+
- `"chunked"` — 适配器将完整 assistant 拆分为 `stream_chunk` + `assistant`(Claude)
|
|
370
|
+
|
|
332
371
|
---
|
|
333
372
|
|
|
334
373
|
### `porygon.dispose(): Promise<void>`
|
|
@@ -383,6 +422,7 @@ porygon.on("health:degraded", (backend: string, warning: string) => {
|
|
|
383
422
|
```ts
|
|
384
423
|
// 核心
|
|
385
424
|
export { Porygon, createPorygon } from "@snack-kit/porygon";
|
|
425
|
+
export type { PorygonEvents, HealthCheckResult } from "@snack-kit/porygon";
|
|
386
426
|
|
|
387
427
|
// 类型
|
|
388
428
|
export type {
|
|
@@ -393,7 +433,7 @@ export type {
|
|
|
393
433
|
AdapterCapabilities, CompatibilityResult, SessionInfo, SessionListOptions, ModelInfo,
|
|
394
434
|
InterceptorFn, InterceptorDirection, InterceptorContext,
|
|
395
435
|
GuardOptions, GuardAction,
|
|
396
|
-
|
|
436
|
+
IAgentAdapter,
|
|
397
437
|
} from "@snack-kit/porygon";
|
|
398
438
|
|
|
399
439
|
// 错误
|
|
@@ -420,7 +460,7 @@ import { createPorygon, createInputGuard, createOutputGuard } from "@snack-kit/p
|
|
|
420
460
|
const porygon = createPorygon({
|
|
421
461
|
defaultBackend: "claude",
|
|
422
462
|
backends: {
|
|
423
|
-
claude: { model: "sonnet",
|
|
463
|
+
claude: { model: "sonnet", interactive: false },
|
|
424
464
|
},
|
|
425
465
|
});
|
|
426
466
|
|
|
@@ -462,6 +502,11 @@ const answer = await porygon.run({
|
|
|
462
502
|
### 健康检查后选择可用后端
|
|
463
503
|
|
|
464
504
|
```ts
|
|
505
|
+
// 检查单个后端
|
|
506
|
+
const result = await porygon.checkBackend("claude");
|
|
507
|
+
if (!result.available) console.error(result.error);
|
|
508
|
+
|
|
509
|
+
// 批量检查所有后端
|
|
465
510
|
const health = await porygon.healthCheck();
|
|
466
511
|
const backend = health.claude?.available ? "claude" : "opencode";
|
|
467
512
|
const answer = await porygon.run({ prompt: "hello", backend });
|
|
@@ -479,12 +524,80 @@ Porygon (Facade)
|
|
|
479
524
|
└── OpenCodeAdapter # opencode serve + REST API + SSE
|
|
480
525
|
```
|
|
481
526
|
|
|
527
|
+
**适配器能力对比:**
|
|
528
|
+
|
|
529
|
+
| 能力 | Claude | OpenCode |
|
|
530
|
+
|------|--------|----------|
|
|
531
|
+
| streaming | `chunked` | `delta` |
|
|
532
|
+
| session-resume | ✓ | ✓ |
|
|
533
|
+
| system-prompt | ✓ | ✓ |
|
|
534
|
+
| tool-restriction | ✓ | - |
|
|
535
|
+
| mcp | ✓ | - |
|
|
536
|
+
| subagents | ✓ | - |
|
|
537
|
+
| worktree | ✓ | - |
|
|
538
|
+
| serve-mode | - | ✓ |
|
|
539
|
+
|
|
482
540
|
## 开发
|
|
483
541
|
|
|
484
542
|
```bash
|
|
485
543
|
npm install
|
|
486
|
-
npm build #
|
|
487
|
-
npm test
|
|
488
|
-
npm dev # watch 模式构建
|
|
489
|
-
npm playground # 启动 Playground
|
|
544
|
+
npm run build # 构建(ESM + CJS + DTS)
|
|
545
|
+
npm test # 运行测试
|
|
546
|
+
npm run dev # watch 模式构建
|
|
547
|
+
npm run playground # 启动 Playground
|
|
490
548
|
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Changelog
|
|
553
|
+
|
|
554
|
+
### v0.3.0
|
|
555
|
+
|
|
556
|
+
#### Bug Fixes
|
|
557
|
+
|
|
558
|
+
- **Claude adapter**: 将 prompt 从 CLI 参数 (`-p <prompt>`) 改为通过 stdin 传递 (`--print` + stdin pipe),修复超长 prompt 导致的偶发 403 "Request not allowed" 错误
|
|
559
|
+
|
|
560
|
+
#### 改进
|
|
561
|
+
|
|
562
|
+
- `SpawnOptions` 新增 `stdinData` 字段,`EphemeralProcess.executeStreaming` 支持向子进程 stdin 写入数据
|
|
563
|
+
|
|
564
|
+
### v0.2.0
|
|
565
|
+
|
|
566
|
+
#### 新特性
|
|
567
|
+
|
|
568
|
+
- **`checkBackend(backend)`** — 新增单后端健康检查方法,无需检查全部后端
|
|
569
|
+
- **`HealthCheckResult`** — 健康检查返回扁平化结构 `{ available, version?, supported?, warnings?, error? }`
|
|
570
|
+
- **`BackendConfig.cliPath`** — Claude CLI 自定义路径,有类型提示
|
|
571
|
+
- **`BackendConfig.interactive`** — 布尔值控制是否跳过权限确认
|
|
572
|
+
- **`BackendConfig.serverUrl`** — OpenCode 远程服务地址(顶级字段)
|
|
573
|
+
- **`BackendConfig.apiKey`** — API Key 认证(顶级字段)
|
|
574
|
+
- **`AgentAssistantMessage.turnComplete`** — 标记 assistant 消息为 turn 完整文本汇总,流式消费者可据此跳过重复文本
|
|
575
|
+
- **`AdapterCapabilities.streamingMode`** — 声明后端的流式模式(`"delta"` 或 `"chunked"`)
|
|
576
|
+
- **`IAgentAdapter.deleteSession?`** — 可选的会话删除方法(接口已定义,适配器待实现)
|
|
577
|
+
- **`tool_result` 映射** — Claude 的 `tool_result` 内容块现在映射为带 `output` 字段的 `tool_use` 消息
|
|
578
|
+
- **`session_id` 自动提取** — Claude 原始事件中的 `session_id` 自动映射到 `AgentMessage.sessionId`
|
|
579
|
+
- **CJS 输出** — 同时构建 ESM 和 CJS 格式,支持 Electron 等 CJS 环境
|
|
580
|
+
|
|
581
|
+
#### 破坏性变更
|
|
582
|
+
|
|
583
|
+
- **`mapClaudeEvent()` 返回类型变更** — 从 `AgentMessage | null` 改为 `AgentMessage[]`(仅影响直接调用 mapper 的代码)
|
|
584
|
+
- **Claude 消息流变更** — `assistant` 事件拆分为 `stream_chunk[]` + `assistant(turnComplete: true)`。之前同时累加 `assistant` 和 `stream_chunk` 文本的代码需要调整:只累加 `stream_chunk`,或检查 `turnComplete` 标记
|
|
585
|
+
- **`healthCheck()` 返回类型变更** — 从 `{ available, compatibility: CompatibilityResult | null, error? }` 改为 `HealthCheckResult`(`{ available, version?, supported?, warnings?, error? }`),移除嵌套的 `compatibility` 字段
|
|
586
|
+
- **`AdapterCapabilities` 新增必填字段** — `streamingMode: "delta" | "chunked"` 为必填,自定义适配器需添加
|
|
587
|
+
|
|
588
|
+
#### 改进
|
|
589
|
+
|
|
590
|
+
- `mergeRequest()` 添加详细 JSDoc 注释说明配置合并策略
|
|
591
|
+
- `mapAssistantContent()` 支持多内容块拆分,不再丢失 tool_use 块
|
|
592
|
+
|
|
593
|
+
### v0.1.0 — 初始版本
|
|
594
|
+
|
|
595
|
+
- 基础 Facade + Adapter 架构
|
|
596
|
+
- Claude Code CLI 适配器(`claude -p --output-format stream-json`)
|
|
597
|
+
- OpenCode 适配器(`opencode serve` + REST API + SSE)
|
|
598
|
+
- 输入/输出拦截器流水线
|
|
599
|
+
- 防护拦截器(prompt 注入检测、敏感信息过滤)
|
|
600
|
+
- 进程生命周期管理(EphemeralProcess / PersistentProcess)
|
|
601
|
+
- 会话管理与恢复
|
|
602
|
+
- Zod 配置校验
|
|
603
|
+
- ESM 输出
|