@snack-kit/porygon 0.3.0 → 0.5.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 +115 -8
- package/dist/index.cjs +100 -17
- 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 +100 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -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,34 @@ npm run playground # 启动 Playground
|
|
|
551
630
|
|
|
552
631
|
## Changelog
|
|
553
632
|
|
|
633
|
+
### v0.5.0
|
|
634
|
+
|
|
635
|
+
#### 新特性
|
|
636
|
+
|
|
637
|
+
- **`porygon.getTools(force?)`** — 获取当前可用的工具列表(包括内置工具和 MCP 插件工具)。首次调用通过最小化对话获取并缓存,后续直接返回缓存,传入 `force: true` 强制刷新
|
|
638
|
+
- **`IAgentAdapter.getTools?(force?)`** — 适配器接口新增可选方法,支持缓存与强制刷新
|
|
639
|
+
- **`onlyTools`(请求参数)** — 仅允许使用的工具白名单,内部通过 `getTools()` 获取全量列表后差集计算转为 `disallowedTools`,工具缓存避免重复对话开销
|
|
640
|
+
- **配置级工具黑名单** — `BackendConfig` 和 `defaults` 新增 `disallowedTools` 字段,支持全局或后端级统一禁用工具,三层叠加去重
|
|
641
|
+
- **`checkBackend(backend, options?)` 深度检测模式** — 新增第二个参数 `CheckBackendOptions`,传入 `{ deep: true }` 时在基础 CLI/版本检查通过后,向模型发送一条极短测试消息(`maxTurns: 1`),验证 Token、模型、配额是否真正可用
|
|
642
|
+
- **`CheckBackendOptions`** — 新增选项类型,支持 `deep`(启用深度检测)、`model`(指定验证模型)、`timeoutMs`(深度检测超时,默认 15s)
|
|
643
|
+
- **`HealthCheckResult.modelVerified`** — 新增字段,深度检测时标识模型是否真正响应
|
|
644
|
+
|
|
645
|
+
#### 破坏性变更
|
|
646
|
+
|
|
647
|
+
- **`PromptRequest.allowedTools` 移除** — Claude CLI 的 `--allowedTools` 实际语义为"免确认"而非"限制范围",已移除避免误用。替代方案:`onlyTools`(白名单,自动转黑名单)或 `disallowedTools`(黑名单)
|
|
648
|
+
- **`porygon.session()` 返回类型变更** — 从 `InteractiveSession` 改为 `Promise<InteractiveSession>`,调用方需 `await`
|
|
649
|
+
|
|
650
|
+
#### Bug Fixes
|
|
651
|
+
|
|
652
|
+
- **Claude adapter 类型定义**: 修复 `ClaudeResultEvent` 缺少 `total_cost_usd` 字段导致的 TS2322 类型错误
|
|
653
|
+
|
|
654
|
+
### v0.4.0
|
|
655
|
+
|
|
656
|
+
#### Bug Fixes
|
|
657
|
+
|
|
658
|
+
- **Claude adapter**: 修复 `result` 事件中 `inputTokens` 和 `outputTokens` 始终为 0 的问题。Claude CLI 的 token 统计位于 `event.usage.input_tokens` / `event.usage.output_tokens`,而非顶层的 `event.input_tokens` / `event.output_tokens`
|
|
659
|
+
- **Claude adapter**: 修复 `costUsd` 始终为 `undefined` 的问题。Claude CLI 使用 `total_cost_usd` 字段名,而非 `cost_usd`
|
|
660
|
+
|
|
554
661
|
### v0.3.0
|
|
555
662
|
|
|
556
663
|
#### Bug Fixes
|
package/dist/index.cjs
CHANGED
|
@@ -134,6 +134,7 @@ var BackendConfigSchema = import_zod.z.object({
|
|
|
134
134
|
apiKey: import_zod.z.string().optional(),
|
|
135
135
|
interactive: import_zod.z.boolean().optional(),
|
|
136
136
|
cliPath: import_zod.z.string().optional(),
|
|
137
|
+
disallowedTools: import_zod.z.array(import_zod.z.string()).optional(),
|
|
137
138
|
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
138
139
|
});
|
|
139
140
|
var PorygonConfigSchema = import_zod.z.object({
|
|
@@ -142,7 +143,8 @@ var PorygonConfigSchema = import_zod.z.object({
|
|
|
142
143
|
defaults: import_zod.z.object({
|
|
143
144
|
appendSystemPrompt: import_zod.z.string().optional(),
|
|
144
145
|
timeoutMs: import_zod.z.number().positive().optional(),
|
|
145
|
-
maxTurns: import_zod.z.number().int().positive().optional()
|
|
146
|
+
maxTurns: import_zod.z.number().int().positive().optional(),
|
|
147
|
+
disallowedTools: import_zod.z.array(import_zod.z.string()).optional()
|
|
146
148
|
}).optional(),
|
|
147
149
|
proxy: ProxyConfigSchema.optional()
|
|
148
150
|
});
|
|
@@ -859,14 +861,15 @@ function mapClaudeEvent(event, sessionId) {
|
|
|
859
861
|
return [];
|
|
860
862
|
}
|
|
861
863
|
if (isResultEvent(event)) {
|
|
864
|
+
const usage = event.usage;
|
|
862
865
|
return [{
|
|
863
866
|
...baseFields,
|
|
864
867
|
type: "result",
|
|
865
868
|
text: event.result,
|
|
866
|
-
costUsd: event.cost_usd,
|
|
869
|
+
costUsd: event.total_cost_usd ?? event.cost_usd,
|
|
867
870
|
durationMs: event.duration_ms,
|
|
868
|
-
inputTokens: event.input_tokens,
|
|
869
|
-
outputTokens: event.output_tokens
|
|
871
|
+
inputTokens: usage?.input_tokens ?? event.input_tokens,
|
|
872
|
+
outputTokens: usage?.output_tokens ?? event.output_tokens
|
|
870
873
|
}];
|
|
871
874
|
}
|
|
872
875
|
return [];
|
|
@@ -924,6 +927,8 @@ var CLAUDE_MODELS = [
|
|
|
924
927
|
];
|
|
925
928
|
var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
926
929
|
backend = "claude";
|
|
930
|
+
/** 工具列表缓存 */
|
|
931
|
+
cachedTools = null;
|
|
927
932
|
/** CLI 命令名或路径 */
|
|
928
933
|
get cliCommand() {
|
|
929
934
|
return this.config?.cliPath ?? "claude";
|
|
@@ -1048,6 +1053,40 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1048
1053
|
this.processManager.removeEphemeral(sessionId);
|
|
1049
1054
|
}
|
|
1050
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* 获取当前可用的工具列表。
|
|
1058
|
+
* 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
|
|
1059
|
+
* @param force 是否强制刷新缓存
|
|
1060
|
+
*/
|
|
1061
|
+
async getTools(force) {
|
|
1062
|
+
if (!force && this.cachedTools) {
|
|
1063
|
+
return this.cachedTools;
|
|
1064
|
+
}
|
|
1065
|
+
const DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
1066
|
+
let tools = [];
|
|
1067
|
+
for await (const message of this.query({
|
|
1068
|
+
prompt: "hi",
|
|
1069
|
+
maxTurns: 1,
|
|
1070
|
+
timeoutMs: DEFAULT_TIMEOUT_MS2
|
|
1071
|
+
})) {
|
|
1072
|
+
const raw = message.raw;
|
|
1073
|
+
if (message.type === "system" && raw?.tools) {
|
|
1074
|
+
const rawTools = raw.tools;
|
|
1075
|
+
if (Array.isArray(rawTools)) {
|
|
1076
|
+
tools = rawTools.map((t) => {
|
|
1077
|
+
if (typeof t === "string") return t;
|
|
1078
|
+
if (typeof t === "object" && t !== null && "name" in t) {
|
|
1079
|
+
return String(t.name);
|
|
1080
|
+
}
|
|
1081
|
+
return String(t);
|
|
1082
|
+
});
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
this.cachedTools = tools;
|
|
1088
|
+
return tools;
|
|
1089
|
+
}
|
|
1051
1090
|
/**
|
|
1052
1091
|
* 列出 Claude 会话
|
|
1053
1092
|
* @param options 查询选项
|
|
@@ -1176,11 +1215,6 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1176
1215
|
if (appendPrompt) {
|
|
1177
1216
|
args.push("--append-system-prompt", appendPrompt);
|
|
1178
1217
|
}
|
|
1179
|
-
if (request.allowedTools && request.allowedTools.length > 0) {
|
|
1180
|
-
for (const tool of request.allowedTools) {
|
|
1181
|
-
args.push("--allowedTools", tool);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
1218
|
if (request.disallowedTools && request.disallowedTools.length > 0) {
|
|
1185
1219
|
for (const tool of request.disallowedTools) {
|
|
1186
1220
|
args.push("--disallowedTools", tool);
|
|
@@ -1194,16 +1228,17 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1194
1228
|
if (skipPerms) {
|
|
1195
1229
|
args.push("--dangerously-skip-permissions");
|
|
1196
1230
|
}
|
|
1197
|
-
if (request.mcpServers) {
|
|
1231
|
+
if (request.mcpServers && Object.keys(request.mcpServers).length > 0) {
|
|
1232
|
+
const mcpConfig = {};
|
|
1198
1233
|
for (const [name, config] of Object.entries(request.mcpServers)) {
|
|
1199
|
-
|
|
1234
|
+
mcpConfig[name] = {
|
|
1200
1235
|
command: config.command,
|
|
1201
1236
|
...config.args ? { args: config.args } : {},
|
|
1202
1237
|
...config.env ? { env: config.env } : {},
|
|
1203
1238
|
...config.url ? { url: config.url } : {}
|
|
1204
1239
|
};
|
|
1205
|
-
args.push("--mcp-server", `${name}=${JSON.stringify(serverSpec)}`);
|
|
1206
1240
|
}
|
|
1241
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers: mcpConfig }));
|
|
1207
1242
|
}
|
|
1208
1243
|
return args;
|
|
1209
1244
|
}
|
|
@@ -1878,7 +1913,7 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1878
1913
|
async *query(request) {
|
|
1879
1914
|
const adapter = this.getAdapter(request.backend);
|
|
1880
1915
|
const backendName = adapter.backend;
|
|
1881
|
-
const mergedRequest = this.mergeRequest(request, backendName);
|
|
1916
|
+
const mergedRequest = await this.mergeRequest(request, backendName);
|
|
1882
1917
|
const processedPrompt = await this.interceptors.processInput(
|
|
1883
1918
|
mergedRequest.prompt,
|
|
1884
1919
|
{ backend: backendName, sessionId: mergedRequest.resume }
|
|
@@ -1920,10 +1955,10 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1920
1955
|
* 创建交互式多轮对话会话。
|
|
1921
1956
|
* 自动管理 sessionId 和 resume,对调用方透明。
|
|
1922
1957
|
*/
|
|
1923
|
-
session(options) {
|
|
1958
|
+
async session(options) {
|
|
1924
1959
|
const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
|
|
1925
1960
|
const adapter = this.getAdapter(backend);
|
|
1926
|
-
const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
|
|
1961
|
+
const merged = await this.mergeRequest({ ...options, prompt: "" }, backend);
|
|
1927
1962
|
const { prompt: _, ...baseRequest } = merged;
|
|
1928
1963
|
return new InteractiveSession(
|
|
1929
1964
|
crypto.randomUUID(),
|
|
@@ -1931,6 +1966,16 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1931
1966
|
baseRequest
|
|
1932
1967
|
);
|
|
1933
1968
|
}
|
|
1969
|
+
/**
|
|
1970
|
+
* 获取当前可用的工具列表。
|
|
1971
|
+
* 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
|
|
1972
|
+
* @param force 是否强制刷新缓存
|
|
1973
|
+
*/
|
|
1974
|
+
async getTools(force) {
|
|
1975
|
+
const adapter = this.getAdapter();
|
|
1976
|
+
if (!adapter.getTools) return [];
|
|
1977
|
+
return adapter.getTools(force);
|
|
1978
|
+
}
|
|
1934
1979
|
/**
|
|
1935
1980
|
* 注册拦截器
|
|
1936
1981
|
* @param direction 拦截方向
|
|
@@ -1957,8 +2002,9 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1957
2002
|
/**
|
|
1958
2003
|
* 检查单个后端的健康状态
|
|
1959
2004
|
* @param backend 后端名称
|
|
2005
|
+
* @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
|
|
1960
2006
|
*/
|
|
1961
|
-
async checkBackend(backend) {
|
|
2007
|
+
async checkBackend(backend, options) {
|
|
1962
2008
|
const adapter = this.getAdapter(backend);
|
|
1963
2009
|
try {
|
|
1964
2010
|
const available = await adapter.isAvailable();
|
|
@@ -1977,6 +2023,25 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
1977
2023
|
if (!compat.supported) {
|
|
1978
2024
|
this.emit("health:degraded", backend, compat.warnings.join("; "));
|
|
1979
2025
|
}
|
|
2026
|
+
if (options?.deep) {
|
|
2027
|
+
try {
|
|
2028
|
+
const response = await this.run({
|
|
2029
|
+
prompt: "Reply with exactly: ok",
|
|
2030
|
+
backend,
|
|
2031
|
+
model: options.model,
|
|
2032
|
+
systemPrompt: "You are a health check probe. Reply with exactly one word: ok",
|
|
2033
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
2034
|
+
maxTurns: 1
|
|
2035
|
+
});
|
|
2036
|
+
result.modelVerified = typeof response === "string" && response.length > 0;
|
|
2037
|
+
} catch (err) {
|
|
2038
|
+
result.modelVerified = false;
|
|
2039
|
+
result.warnings = [
|
|
2040
|
+
...result.warnings || [],
|
|
2041
|
+
`\u6A21\u578B\u9A8C\u8BC1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
2042
|
+
];
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
1980
2045
|
return result;
|
|
1981
2046
|
} catch (err) {
|
|
1982
2047
|
return {
|
|
@@ -2056,6 +2121,8 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2056
2121
|
* - timeoutMs: request > defaults
|
|
2057
2122
|
* - maxTurns: request > defaults
|
|
2058
2123
|
* - cwd: request > backendConfig
|
|
2124
|
+
* - disallowedTools: defaults + backendConfig + request 三层叠加去重
|
|
2125
|
+
* - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
|
|
2059
2126
|
* - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
|
|
2060
2127
|
* 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
|
|
2061
2128
|
*
|
|
@@ -2063,7 +2130,7 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2063
2130
|
* @param backend 后端名称
|
|
2064
2131
|
* @returns 合并后的请求
|
|
2065
2132
|
*/
|
|
2066
|
-
mergeRequest(request, backend) {
|
|
2133
|
+
async mergeRequest(request, backend) {
|
|
2067
2134
|
const backendConfig = this.config.backends?.[backend];
|
|
2068
2135
|
const defaults = this.config.defaults;
|
|
2069
2136
|
const appendParts = [];
|
|
@@ -2076,12 +2143,28 @@ var Porygon = class extends import_node_events2.EventEmitter {
|
|
|
2076
2143
|
if (request.appendSystemPrompt) {
|
|
2077
2144
|
appendParts.push(request.appendSystemPrompt);
|
|
2078
2145
|
}
|
|
2146
|
+
let mergedDisallowedTools;
|
|
2147
|
+
if (request.onlyTools) {
|
|
2148
|
+
const adapter = this.getAdapter(backend);
|
|
2149
|
+
const allTools = adapter.getTools ? await adapter.getTools() : [];
|
|
2150
|
+
const allowSet = new Set(request.onlyTools);
|
|
2151
|
+
const disallowed = allTools.filter((t) => !allowSet.has(t));
|
|
2152
|
+
mergedDisallowedTools = disallowed.length > 0 ? disallowed : void 0;
|
|
2153
|
+
} else {
|
|
2154
|
+
const disallowParts = [
|
|
2155
|
+
...defaults?.disallowedTools ?? [],
|
|
2156
|
+
...backendConfig?.disallowedTools ?? [],
|
|
2157
|
+
...request.disallowedTools ?? []
|
|
2158
|
+
];
|
|
2159
|
+
mergedDisallowedTools = disallowParts.length > 0 ? [...new Set(disallowParts)] : void 0;
|
|
2160
|
+
}
|
|
2079
2161
|
return {
|
|
2080
2162
|
...request,
|
|
2081
2163
|
model: request.model ?? backendConfig?.model,
|
|
2082
2164
|
timeoutMs: request.timeoutMs ?? defaults?.timeoutMs,
|
|
2083
2165
|
maxTurns: request.maxTurns ?? defaults?.maxTurns,
|
|
2084
2166
|
cwd: request.cwd ?? backendConfig?.cwd,
|
|
2167
|
+
disallowedTools: mergedDisallowedTools,
|
|
2085
2168
|
appendSystemPrompt: request.systemPrompt ? void 0 : appendParts.length > 0 ? appendParts.join("\n") : void 0
|
|
2086
2169
|
};
|
|
2087
2170
|
}
|