@snack-kit/porygon 0.4.0 → 0.6.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 +121 -8
- package/dist/index.cjs +112 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -4
- package/dist/index.d.ts +50 -4
- package/dist/index.js +99 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ const porygon = createPorygon({
|
|
|
54
54
|
timeoutMs: 300_000,
|
|
55
55
|
maxTurns: 50,
|
|
56
56
|
appendSystemPrompt: "请简洁回答",
|
|
57
|
+
disallowedTools: ["WebSearch"], // 全局禁用的工具
|
|
57
58
|
},
|
|
58
59
|
proxy: { url: "http://proxy:8080", noProxy: "localhost" },
|
|
59
60
|
});
|
|
@@ -65,7 +66,7 @@ const porygon = createPorygon({
|
|
|
65
66
|
|------|------|------|
|
|
66
67
|
| `defaultBackend` | `string` | 默认后端名称 |
|
|
67
68
|
| `backends` | `Record<string, BackendConfig>` | 各后端独立配置 |
|
|
68
|
-
| `defaults` | `{ appendSystemPrompt?, timeoutMs?, maxTurns? }` | 全局默认参数 |
|
|
69
|
+
| `defaults` | `{ appendSystemPrompt?, timeoutMs?, maxTurns?, disallowedTools? }` | 全局默认参数 |
|
|
69
70
|
| `proxy` | `{ url: string, noProxy?: string }` | 全局代理(后端级 proxy 优先) |
|
|
70
71
|
|
|
71
72
|
**BackendConfig 字段:**
|
|
@@ -80,6 +81,7 @@ const porygon = createPorygon({
|
|
|
80
81
|
| `appendSystemPrompt` | `string` | 追加系统提示词 |
|
|
81
82
|
| `proxy` | `ProxyConfig` | 后端专属代理 |
|
|
82
83
|
| `cwd` | `string` | 工作目录 |
|
|
84
|
+
| `disallowedTools` | `string[]` | 禁止使用的工具黑名单(后端级) |
|
|
83
85
|
| `options` | `Record<string, unknown>` | 透传给后端的额外选项(向后兼容,推荐使用上方的显式字段) |
|
|
84
86
|
|
|
85
87
|
---
|
|
@@ -157,8 +159,8 @@ system → stream_chunk* → assistant(turnComplete) → tool_use* → ... → r
|
|
|
157
159
|
| `resume` | `string` | 否 | 恢复会话的 session ID |
|
|
158
160
|
| `systemPrompt` | `string` | 否 | 系统提示词(替换模式,设置后 appendSystemPrompt 失效) |
|
|
159
161
|
| `appendSystemPrompt` | `string` | 否 | 系统提示词(追加模式,与 config 中的追加内容合并) |
|
|
160
|
-
| `
|
|
161
|
-
| `disallowedTools` | `string[]` | 否 |
|
|
162
|
+
| `onlyTools` | `string[]` | 否 | 仅允许使用的工具白名单(内部通过 getTools() 差集转为黑名单,与 disallowedTools 互斥,优先级更高) |
|
|
163
|
+
| `disallowedTools` | `string[]` | 否 | 禁止使用的工具黑名单 |
|
|
162
164
|
| `maxTurns` | `number` | 否 | 最大对话轮次 |
|
|
163
165
|
| `timeoutMs` | `number` | 否 | 超时毫秒数 |
|
|
164
166
|
| `cwd` | `string` | 否 | 工作目录 |
|
|
@@ -174,6 +176,8 @@ system → stream_chunk* → assistant(turnComplete) → tool_use* → ... → r
|
|
|
174
176
|
| `timeoutMs` | request > defaults |
|
|
175
177
|
| `maxTurns` | request > defaults |
|
|
176
178
|
| `cwd` | request > backendConfig |
|
|
179
|
+
| `onlyTools` | 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(设置后忽略 disallowedTools) |
|
|
180
|
+
| `disallowedTools` | defaults + backendConfig + request 三层叠加去重 |
|
|
177
181
|
| `appendSystemPrompt` | defaults + backendConfig + request 三层追加拼接(换行分隔)。若 `systemPrompt` 已设置则忽略全部 append |
|
|
178
182
|
|
|
179
183
|
---
|
|
@@ -193,16 +197,32 @@ system → stream_chunk* → assistant(turnComplete) → tool_use* → ... → r
|
|
|
193
197
|
|
|
194
198
|
---
|
|
195
199
|
|
|
196
|
-
### `porygon.checkBackend(backend): Promise<HealthCheckResult>`
|
|
200
|
+
### `porygon.checkBackend(backend, options?): Promise<HealthCheckResult>`
|
|
197
201
|
|
|
198
|
-
|
|
202
|
+
检查单个后端的健康状态。支持两种模式:
|
|
203
|
+
|
|
204
|
+
- **轻量检测**(默认):仅检查 CLI 是否存在 + 版本兼容性
|
|
205
|
+
- **深度检测**(`deep: true`):额外向模型发送一条测试消息,验证 Token/模型/配额是否真正可用
|
|
199
206
|
|
|
200
207
|
```ts
|
|
208
|
+
// 轻量检测:仅检查 CLI 存在性和版本
|
|
201
209
|
const result = await porygon.checkBackend("claude");
|
|
202
210
|
// { available: true, version: "1.2.0", supported: true }
|
|
203
|
-
|
|
211
|
+
|
|
212
|
+
// 深度检测:真实调用模型验证可用性
|
|
213
|
+
const deep = await porygon.checkBackend("claude", { deep: true, model: "haiku" });
|
|
214
|
+
// { available: true, version: "1.2.0", supported: true, modelVerified: true }
|
|
215
|
+
// { available: true, version: "1.2.0", supported: true, modelVerified: false, warnings: ["模型验证失败: ..."] }
|
|
204
216
|
```
|
|
205
217
|
|
|
218
|
+
**CheckBackendOptions:**
|
|
219
|
+
|
|
220
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
221
|
+
|------|------|--------|------|
|
|
222
|
+
| `deep` | `boolean` | `false` | 启用深度检测,向模型发送测试消息 |
|
|
223
|
+
| `model` | `string?` | 后端默认 | 深度检测使用的模型 |
|
|
224
|
+
| `timeoutMs` | `number` | `15000` | 深度检测超时(毫秒) |
|
|
225
|
+
|
|
206
226
|
**HealthCheckResult:**
|
|
207
227
|
|
|
208
228
|
| 字段 | 类型 | 说明 |
|
|
@@ -212,6 +232,7 @@ const result = await porygon.checkBackend("claude");
|
|
|
212
232
|
| `supported` | `boolean?` | 版本是否在测试范围内 |
|
|
213
233
|
| `warnings` | `string[]?` | 兼容性警告 |
|
|
214
234
|
| `error` | `string?` | 错误信息 |
|
|
235
|
+
| `modelVerified` | `boolean?` | 深度检测时模型是否真正响应 |
|
|
215
236
|
|
|
216
237
|
---
|
|
217
238
|
|
|
@@ -350,6 +371,23 @@ const models = await porygon.listModels("claude");
|
|
|
350
371
|
|
|
351
372
|
---
|
|
352
373
|
|
|
374
|
+
### `porygon.getTools(force?): Promise<string[]>`
|
|
375
|
+
|
|
376
|
+
获取当前可用的工具列表。首次调用通过发起最小化对话(`maxTurns: 1`)从 `system` 事件中提取,结果会被缓存,后续调用直接返回缓存。
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
const tools = await porygon.getTools();
|
|
380
|
+
console.log(tools);
|
|
381
|
+
// ["Bash", "Read", "Edit", "Write", "Glob", "Grep", "mcp__xxx__yyy", ...]
|
|
382
|
+
|
|
383
|
+
// 强制刷新缓存
|
|
384
|
+
const fresh = await porygon.getTools(true);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
> 仅 Claude 后端支持此能力,其他后端返回空数组。
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
353
391
|
### `porygon.getCapabilities(backend?): AdapterCapabilities`
|
|
354
392
|
|
|
355
393
|
获取后端能力声明。
|
|
@@ -422,7 +460,7 @@ porygon.on("health:degraded", (backend: string, warning: string) => {
|
|
|
422
460
|
```ts
|
|
423
461
|
// 核心
|
|
424
462
|
export { Porygon, createPorygon } from "@snack-kit/porygon";
|
|
425
|
-
export type { PorygonEvents, HealthCheckResult } from "@snack-kit/porygon";
|
|
463
|
+
export type { PorygonEvents, HealthCheckResult, CheckBackendOptions } from "@snack-kit/porygon";
|
|
426
464
|
|
|
427
465
|
// 类型
|
|
428
466
|
export type {
|
|
@@ -446,6 +484,9 @@ export {
|
|
|
446
484
|
// 防护拦截器
|
|
447
485
|
export { createInputGuard, createOutputGuard } from "@snack-kit/porygon";
|
|
448
486
|
|
|
487
|
+
// 交互式会话
|
|
488
|
+
export { InteractiveSession } from "@snack-kit/porygon";
|
|
489
|
+
|
|
449
490
|
// 适配器(自定义扩展用)
|
|
450
491
|
export { AbstractAgentAdapter, ClaudeAdapter, OpenCodeAdapter } from "@snack-kit/porygon";
|
|
451
492
|
```
|
|
@@ -499,13 +540,51 @@ const answer = await porygon.run({
|
|
|
499
540
|
});
|
|
500
541
|
```
|
|
501
542
|
|
|
543
|
+
### 工具控制
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
// 查询所有可用工具
|
|
547
|
+
const tools = await porygon.getTools();
|
|
548
|
+
console.log(tools); // ["Bash", "Read", "Edit", ...]
|
|
549
|
+
|
|
550
|
+
// 黑名单:禁用危险工具
|
|
551
|
+
await porygon.run({
|
|
552
|
+
prompt: "分析代码",
|
|
553
|
+
disallowedTools: ["Bash", "Edit", "Write"],
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// 白名单:只允许只读工具(内部自动转为黑名单,getTools 结果有缓存)
|
|
557
|
+
await porygon.run({
|
|
558
|
+
prompt: "阅读代码并回答",
|
|
559
|
+
onlyTools: ["Read", "Grep", "Glob"],
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// 配置级黑名单:全局 + 后端级 + 请求级三层叠加
|
|
563
|
+
const porygon = createPorygon({
|
|
564
|
+
defaults: { disallowedTools: ["WebSearch"] },
|
|
565
|
+
backends: {
|
|
566
|
+
claude: {
|
|
567
|
+
interactive: false,
|
|
568
|
+
disallowedTools: ["Bash"],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
// 此时 disallowedTools = ["WebSearch", "Bash"],无需每次请求重复传入
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
502
577
|
### 健康检查后选择可用后端
|
|
503
578
|
|
|
504
579
|
```ts
|
|
505
|
-
//
|
|
580
|
+
// 轻量检查:CLI 存在性 + 版本
|
|
506
581
|
const result = await porygon.checkBackend("claude");
|
|
507
582
|
if (!result.available) console.error(result.error);
|
|
508
583
|
|
|
584
|
+
// 深度检查:验证模型真正可用(Token/配额/模型名)
|
|
585
|
+
const deep = await porygon.checkBackend("claude", { deep: true, model: "haiku" });
|
|
586
|
+
if (deep.modelVerified) console.log("模型验证通过");
|
|
587
|
+
|
|
509
588
|
// 批量检查所有后端
|
|
510
589
|
const health = await porygon.healthCheck();
|
|
511
590
|
const backend = health.claude?.available ? "claude" : "opencode";
|
|
@@ -551,6 +630,40 @@ npm run playground # 启动 Playground
|
|
|
551
630
|
|
|
552
631
|
## Changelog
|
|
553
632
|
|
|
633
|
+
### v0.6.0
|
|
634
|
+
|
|
635
|
+
#### 改进
|
|
636
|
+
|
|
637
|
+
- **Windows 跨平台兼容** — 使用 `cross-spawn` 替换原生 `child_process.spawn`,解决 Windows 上因 PATH 解析、PATHEXT 扩展名(`.exe`、`.cmd` 等)导致的 `ENOENT` 错误。所有进程启动(`EphemeralProcess`、`PersistentProcess`)均已适配
|
|
638
|
+
- **`isAvailable()` Windows 适配** — Claude adapter 的 CLI 存在性检测在 Windows 上使用 `where` 命令替代 `which`
|
|
639
|
+
|
|
640
|
+
#### 新增依赖
|
|
641
|
+
|
|
642
|
+
- `cross-spawn` — Node.js 生态标准的跨平台进程启动库,正确处理 Windows 上的命令解析、参数转义和 PATHEXT 查找
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
### v0.5.0
|
|
647
|
+
|
|
648
|
+
#### 新特性
|
|
649
|
+
|
|
650
|
+
- **`porygon.getTools(force?)`** — 获取当前可用的工具列表(包括内置工具和 MCP 插件工具)。首次调用通过最小化对话获取并缓存,后续直接返回缓存,传入 `force: true` 强制刷新
|
|
651
|
+
- **`IAgentAdapter.getTools?(force?)`** — 适配器接口新增可选方法,支持缓存与强制刷新
|
|
652
|
+
- **`onlyTools`(请求参数)** — 仅允许使用的工具白名单,内部通过 `getTools()` 获取全量列表后差集计算转为 `disallowedTools`,工具缓存避免重复对话开销
|
|
653
|
+
- **配置级工具黑名单** — `BackendConfig` 和 `defaults` 新增 `disallowedTools` 字段,支持全局或后端级统一禁用工具,三层叠加去重
|
|
654
|
+
- **`checkBackend(backend, options?)` 深度检测模式** — 新增第二个参数 `CheckBackendOptions`,传入 `{ deep: true }` 时在基础 CLI/版本检查通过后,向模型发送一条极短测试消息(`maxTurns: 1`),验证 Token、模型、配额是否真正可用
|
|
655
|
+
- **`CheckBackendOptions`** — 新增选项类型,支持 `deep`(启用深度检测)、`model`(指定验证模型)、`timeoutMs`(深度检测超时,默认 15s)
|
|
656
|
+
- **`HealthCheckResult.modelVerified`** — 新增字段,深度检测时标识模型是否真正响应
|
|
657
|
+
|
|
658
|
+
#### 破坏性变更
|
|
659
|
+
|
|
660
|
+
- **`PromptRequest.allowedTools` 移除** — Claude CLI 的 `--allowedTools` 实际语义为"免确认"而非"限制范围",已移除避免误用。替代方案:`onlyTools`(白名单,自动转黑名单)或 `disallowedTools`(黑名单)
|
|
661
|
+
- **`porygon.session()` 返回类型变更** — 从 `InteractiveSession` 改为 `Promise<InteractiveSession>`,调用方需 `await`
|
|
662
|
+
|
|
663
|
+
#### Bug Fixes
|
|
664
|
+
|
|
665
|
+
- **Claude adapter 类型定义**: 修复 `ClaudeResultEvent` 缺少 `total_cost_usd` 字段导致的 TS2322 类型错误
|
|
666
|
+
|
|
554
667
|
### v0.4.0
|
|
555
668
|
|
|
556
669
|
#### Bug Fixes
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -134,6 +144,7 @@ var BackendConfigSchema = import_zod.z.object({
|
|
|
134
144
|
apiKey: import_zod.z.string().optional(),
|
|
135
145
|
interactive: import_zod.z.boolean().optional(),
|
|
136
146
|
cliPath: import_zod.z.string().optional(),
|
|
147
|
+
disallowedTools: import_zod.z.array(import_zod.z.string()).optional(),
|
|
137
148
|
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
138
149
|
});
|
|
139
150
|
var PorygonConfigSchema = import_zod.z.object({
|
|
@@ -142,7 +153,8 @@ var PorygonConfigSchema = import_zod.z.object({
|
|
|
142
153
|
defaults: import_zod.z.object({
|
|
143
154
|
appendSystemPrompt: import_zod.z.string().optional(),
|
|
144
155
|
timeoutMs: import_zod.z.number().positive().optional(),
|
|
145
|
-
maxTurns: import_zod.z.number().int().positive().optional()
|
|
156
|
+
maxTurns: import_zod.z.number().int().positive().optional(),
|
|
157
|
+
disallowedTools: import_zod.z.array(import_zod.z.string()).optional()
|
|
146
158
|
}).optional(),
|
|
147
159
|
proxy: ProxyConfigSchema.optional()
|
|
148
160
|
});
|
|
@@ -276,7 +288,7 @@ var InterceptorManager = class {
|
|
|
276
288
|
};
|
|
277
289
|
|
|
278
290
|
// src/process/process-handle.ts
|
|
279
|
-
var
|
|
291
|
+
var import_cross_spawn = __toESM(require("cross-spawn"), 1);
|
|
280
292
|
var import_node_events = require("events");
|
|
281
293
|
var import_node_readline = require("readline");
|
|
282
294
|
var GRACE_PERIOD_MS = 5e3;
|
|
@@ -296,7 +308,7 @@ var EphemeralProcess = class {
|
|
|
296
308
|
return;
|
|
297
309
|
}
|
|
298
310
|
this.aborted = false;
|
|
299
|
-
const child = (0,
|
|
311
|
+
const child = (0, import_cross_spawn.default)(options.command, options.args, {
|
|
300
312
|
cwd: options.cwd,
|
|
301
313
|
env: options.env ? { ...process.env, ...options.env } : void 0,
|
|
302
314
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -352,7 +364,7 @@ var EphemeralProcess = class {
|
|
|
352
364
|
}
|
|
353
365
|
this.aborted = false;
|
|
354
366
|
const useStdin = options.stdinData !== void 0;
|
|
355
|
-
const child = (0,
|
|
367
|
+
const child = (0, import_cross_spawn.default)(options.command, options.args, {
|
|
356
368
|
cwd: options.cwd,
|
|
357
369
|
env: options.env ?? void 0,
|
|
358
370
|
stdio: [useStdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -435,7 +447,7 @@ var PersistentProcess = class extends import_node_events.EventEmitter {
|
|
|
435
447
|
/** 启动持久进程 */
|
|
436
448
|
async start() {
|
|
437
449
|
this.stopped = false;
|
|
438
|
-
const child = (0,
|
|
450
|
+
const child = (0, import_cross_spawn.default)(this.options.command, this.options.args, {
|
|
439
451
|
cwd: this.options.cwd,
|
|
440
452
|
env: this.options.env ? { ...process.env, ...this.options.env } : void 0,
|
|
441
453
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -925,6 +937,8 @@ var CLAUDE_MODELS = [
|
|
|
925
937
|
];
|
|
926
938
|
var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
927
939
|
backend = "claude";
|
|
940
|
+
/** 工具列表缓存 */
|
|
941
|
+
cachedTools = null;
|
|
928
942
|
/** CLI 命令名或路径 */
|
|
929
943
|
get cliCommand() {
|
|
930
944
|
return this.config?.cliPath ?? "claude";
|
|
@@ -935,8 +949,9 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
935
949
|
async isAvailable() {
|
|
936
950
|
try {
|
|
937
951
|
const proc = new EphemeralProcess();
|
|
952
|
+
const findCmd = process.platform === "win32" ? "where" : "which";
|
|
938
953
|
const result = await proc.execute({
|
|
939
|
-
command:
|
|
954
|
+
command: findCmd,
|
|
940
955
|
args: [this.cliCommand]
|
|
941
956
|
});
|
|
942
957
|
return result.exitCode === 0;
|
|
@@ -1049,6 +1064,40 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1049
1064
|
this.processManager.removeEphemeral(sessionId);
|
|
1050
1065
|
}
|
|
1051
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* 获取当前可用的工具列表。
|
|
1069
|
+
* 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
|
|
1070
|
+
* @param force 是否强制刷新缓存
|
|
1071
|
+
*/
|
|
1072
|
+
async getTools(force) {
|
|
1073
|
+
if (!force && this.cachedTools) {
|
|
1074
|
+
return this.cachedTools;
|
|
1075
|
+
}
|
|
1076
|
+
const DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
1077
|
+
let tools = [];
|
|
1078
|
+
for await (const message of this.query({
|
|
1079
|
+
prompt: "hi",
|
|
1080
|
+
maxTurns: 1,
|
|
1081
|
+
timeoutMs: DEFAULT_TIMEOUT_MS2
|
|
1082
|
+
})) {
|
|
1083
|
+
const raw = message.raw;
|
|
1084
|
+
if (message.type === "system" && raw?.tools) {
|
|
1085
|
+
const rawTools = raw.tools;
|
|
1086
|
+
if (Array.isArray(rawTools)) {
|
|
1087
|
+
tools = rawTools.map((t) => {
|
|
1088
|
+
if (typeof t === "string") return t;
|
|
1089
|
+
if (typeof t === "object" && t !== null && "name" in t) {
|
|
1090
|
+
return String(t.name);
|
|
1091
|
+
}
|
|
1092
|
+
return String(t);
|
|
1093
|
+
});
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
this.cachedTools = tools;
|
|
1099
|
+
return tools;
|
|
1100
|
+
}
|
|
1052
1101
|
/**
|
|
1053
1102
|
* 列出 Claude 会话
|
|
1054
1103
|
* @param options 查询选项
|
|
@@ -1177,11 +1226,6 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1177
1226
|
if (appendPrompt) {
|
|
1178
1227
|
args.push("--append-system-prompt", appendPrompt);
|
|
1179
1228
|
}
|
|
1180
|
-
if (request.allowedTools && request.allowedTools.length > 0) {
|
|
1181
|
-
for (const tool of request.allowedTools) {
|
|
1182
|
-
args.push("--allowedTools", tool);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
1229
|
if (request.disallowedTools && request.disallowedTools.length > 0) {
|
|
1186
1230
|
for (const tool of request.disallowedTools) {
|
|
1187
1231
|
args.push("--disallowedTools", tool);
|
|
@@ -1195,16 +1239,17 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1195
1239
|
if (skipPerms) {
|
|
1196
1240
|
args.push("--dangerously-skip-permissions");
|
|
1197
1241
|
}
|
|
1198
|
-
if (request.mcpServers) {
|
|
1242
|
+
if (request.mcpServers && Object.keys(request.mcpServers).length > 0) {
|
|
1243
|
+
const mcpConfig = {};
|
|
1199
1244
|
for (const [name, config] of Object.entries(request.mcpServers)) {
|
|
1200
|
-
|
|
1245
|
+
mcpConfig[name] = {
|
|
1201
1246
|
command: config.command,
|
|
1202
1247
|
...config.args ? { args: config.args } : {},
|
|
1203
1248
|
...config.env ? { env: config.env } : {},
|
|
1204
1249
|
...config.url ? { url: config.url } : {}
|
|
1205
1250
|
};
|
|
1206
|
-
args.push("--mcp-server", `${name}=${JSON.stringify(serverSpec)}`);
|
|
1207
1251
|
}
|
|
1252
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers: mcpConfig }));
|
|
1208
1253
|
}
|
|
1209
1254
|
return args;
|
|
1210
1255
|
}
|
|
@@ -1879,7 +1924,7 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1879
1924
|
async *query(request) {
|
|
1880
1925
|
const adapter = this.getAdapter(request.backend);
|
|
1881
1926
|
const backendName = adapter.backend;
|
|
1882
|
-
const mergedRequest = this.mergeRequest(request, backendName);
|
|
1927
|
+
const mergedRequest = await this.mergeRequest(request, backendName);
|
|
1883
1928
|
const processedPrompt = await this.interceptors.processInput(
|
|
1884
1929
|
mergedRequest.prompt,
|
|
1885
1930
|
{ backend: backendName, sessionId: mergedRequest.resume }
|
|
@@ -1921,10 +1966,10 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1921
1966
|
* 创建交互式多轮对话会话。
|
|
1922
1967
|
* 自动管理 sessionId 和 resume,对调用方透明。
|
|
1923
1968
|
*/
|
|
1924
|
-
session(options) {
|
|
1969
|
+
async session(options) {
|
|
1925
1970
|
const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
|
|
1926
1971
|
const adapter = this.getAdapter(backend);
|
|
1927
|
-
const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
|
|
1972
|
+
const merged = await this.mergeRequest({ ...options, prompt: "" }, backend);
|
|
1928
1973
|
const { prompt: _, ...baseRequest } = merged;
|
|
1929
1974
|
return new InteractiveSession(
|
|
1930
1975
|
crypto.randomUUID(),
|
|
@@ -1932,6 +1977,16 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1932
1977
|
baseRequest
|
|
1933
1978
|
);
|
|
1934
1979
|
}
|
|
1980
|
+
/**
|
|
1981
|
+
* 获取当前可用的工具列表。
|
|
1982
|
+
* 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
|
|
1983
|
+
* @param force 是否强制刷新缓存
|
|
1984
|
+
*/
|
|
1985
|
+
async getTools(force) {
|
|
1986
|
+
const adapter = this.getAdapter();
|
|
1987
|
+
if (!adapter.getTools) return [];
|
|
1988
|
+
return adapter.getTools(force);
|
|
1989
|
+
}
|
|
1935
1990
|
/**
|
|
1936
1991
|
* 注册拦截器
|
|
1937
1992
|
* @param direction 拦截方向
|
|
@@ -1958,8 +2013,9 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1958
2013
|
/**
|
|
1959
2014
|
* 检查单个后端的健康状态
|
|
1960
2015
|
* @param backend 后端名称
|
|
2016
|
+
* @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
|
|
1961
2017
|
*/
|
|
1962
|
-
async checkBackend(backend) {
|
|
2018
|
+
async checkBackend(backend, options) {
|
|
1963
2019
|
const adapter = this.getAdapter(backend);
|
|
1964
2020
|
try {
|
|
1965
2021
|
const available = await adapter.isAvailable();
|
|
@@ -1978,6 +2034,25 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1978
2034
|
if (!compat.supported) {
|
|
1979
2035
|
this.emit("health:degraded", backend, compat.warnings.join("; "));
|
|
1980
2036
|
}
|
|
2037
|
+
if (options?.deep) {
|
|
2038
|
+
try {
|
|
2039
|
+
const response = await this.run({
|
|
2040
|
+
prompt: "Reply with exactly: ok",
|
|
2041
|
+
backend,
|
|
2042
|
+
model: options.model,
|
|
2043
|
+
systemPrompt: "You are a health check probe. Reply with exactly one word: ok",
|
|
2044
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
2045
|
+
maxTurns: 1
|
|
2046
|
+
});
|
|
2047
|
+
result.modelVerified = typeof response === "string" && response.length > 0;
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
result.modelVerified = false;
|
|
2050
|
+
result.warnings = [
|
|
2051
|
+
...result.warnings || [],
|
|
2052
|
+
`\u6A21\u578B\u9A8C\u8BC1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
2053
|
+
];
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
1981
2056
|
return result;
|
|
1982
2057
|
} catch (err) {
|
|
1983
2058
|
return {
|
|
@@ -2057,6 +2132,8 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2057
2132
|
* - timeoutMs: request > defaults
|
|
2058
2133
|
* - maxTurns: request > defaults
|
|
2059
2134
|
* - cwd: request > backendConfig
|
|
2135
|
+
* - disallowedTools: defaults + backendConfig + request 三层叠加去重
|
|
2136
|
+
* - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
|
|
2060
2137
|
* - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
|
|
2061
2138
|
* 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
|
|
2062
2139
|
*
|
|
@@ -2064,7 +2141,7 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2064
2141
|
* @param backend 后端名称
|
|
2065
2142
|
* @returns 合并后的请求
|
|
2066
2143
|
*/
|
|
2067
|
-
mergeRequest(request, backend) {
|
|
2144
|
+
async mergeRequest(request, backend) {
|
|
2068
2145
|
const backendConfig = this.config.backends?.[backend];
|
|
2069
2146
|
const defaults = this.config.defaults;
|
|
2070
2147
|
const appendParts = [];
|
|
@@ -2077,12 +2154,28 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2077
2154
|
if (request.appendSystemPrompt) {
|
|
2078
2155
|
appendParts.push(request.appendSystemPrompt);
|
|
2079
2156
|
}
|
|
2157
|
+
let mergedDisallowedTools;
|
|
2158
|
+
if (request.onlyTools) {
|
|
2159
|
+
const adapter = this.getAdapter(backend);
|
|
2160
|
+
const allTools = adapter.getTools ? await adapter.getTools() : [];
|
|
2161
|
+
const allowSet = new Set(request.onlyTools);
|
|
2162
|
+
const disallowed = allTools.filter((t) => !allowSet.has(t));
|
|
2163
|
+
mergedDisallowedTools = disallowed.length > 0 ? disallowed : void 0;
|
|
2164
|
+
} else {
|
|
2165
|
+
const disallowParts = [
|
|
2166
|
+
...defaults?.disallowedTools ?? [],
|
|
2167
|
+
...backendConfig?.disallowedTools ?? [],
|
|
2168
|
+
...request.disallowedTools ?? []
|
|
2169
|
+
];
|
|
2170
|
+
mergedDisallowedTools = disallowParts.length > 0 ? [...new Set(disallowParts)] : void 0;
|
|
2171
|
+
}
|
|
2080
2172
|
return {
|
|
2081
2173
|
...request,
|
|
2082
2174
|
model: request.model ?? backendConfig?.model,
|
|
2083
2175
|
timeoutMs: request.timeoutMs ?? defaults?.timeoutMs,
|
|
2084
2176
|
maxTurns: request.maxTurns ?? defaults?.maxTurns,
|
|
2085
2177
|
cwd: request.cwd ?? backendConfig?.cwd,
|
|
2178
|
+
disallowedTools: mergedDisallowedTools,
|
|
2086
2179
|
appendSystemPrompt: request.systemPrompt ? void 0 : appendParts.length > 0 ? appendParts.join("\n") : void 0
|
|
2087
2180
|
};
|
|
2088
2181
|
}
|