@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/dist/index.d.cts CHANGED
@@ -23,6 +23,8 @@ interface BackendConfig {
23
23
  interactive?: boolean;
24
24
  /** CLI 可执行文件路径(如 Claude CLI 的自定义安装路径) */
25
25
  cliPath?: string;
26
+ /** 禁止使用的工具黑名单(后端级) */
27
+ disallowedTools?: string[];
26
28
  /** 透传给 CLI 的后端特定选项(向后兼容,推荐使用上方的显式字段) */
27
29
  options?: Record<string, unknown>;
28
30
  }
@@ -36,6 +38,8 @@ interface PorygonConfig {
36
38
  appendSystemPrompt?: string;
37
39
  timeoutMs?: number;
38
40
  maxTurns?: number;
41
+ /** 禁止使用的工具黑名单(全局默认) */
42
+ disallowedTools?: string[];
39
43
  };
40
44
  proxy?: ProxyConfig;
41
45
  }
@@ -63,7 +67,13 @@ interface PromptRequest {
63
67
  appendSystemPrompt?: string;
64
68
  model?: string;
65
69
  timeoutMs?: number;
66
- allowedTools?: string[];
70
+ /**
71
+ * 仅允许使用的工具白名单。
72
+ * 运行时通过 getTools() 获取全量工具列表,差集计算后转为 disallowedTools 传递给 CLI。
73
+ * 与 disallowedTools 互斥,同时设置时 onlyTools 优先。
74
+ */
75
+ onlyTools?: string[];
76
+ /** 禁止使用的工具黑名单 */
67
77
  disallowedTools?: string[];
68
78
  mcpServers?: Record<string, McpServerConfig>;
69
79
  maxTurns?: number;
@@ -180,6 +190,8 @@ interface IAgentAdapter {
180
190
  /** 获取可用模型列表 */
181
191
  listModels(): Promise<ModelInfo[]>;
182
192
  abort(sessionId: string): void;
193
+ /** 获取当前可用的工具列表(可选实现,支持缓存) */
194
+ getTools?(force?: boolean): Promise<string[]>;
183
195
  /** 删除会话及其资源(可选实现) */
184
196
  deleteSession?(sessionId: string): Promise<void>;
185
197
  dispose(): Promise<void>;
@@ -250,6 +262,23 @@ interface HealthCheckResult {
250
262
  supported?: boolean;
251
263
  warnings?: string[];
252
264
  error?: string;
265
+ /** deep 模式下,模型是否真正响应 */
266
+ modelVerified?: boolean;
267
+ }
268
+ /**
269
+ * checkBackend 的选项
270
+ */
271
+ interface CheckBackendOptions {
272
+ /**
273
+ * 启用深度检测:在基础 CLI/版本检查通过后,向模型发送一条极短测试消息,
274
+ * 验证 Token/模型/配额是否真正可用。
275
+ * 默认 false(仅做 CLI 存在性 + 版本检查)。
276
+ */
277
+ deep?: boolean;
278
+ /** deep 模式下使用的模型(可选,不传则用后端默认模型) */
279
+ model?: string;
280
+ /** deep 模式超时(毫秒),默认 15000 */
281
+ timeoutMs?: number;
253
282
  }
254
283
  /**
255
284
  * Porygon 事件类型定义
@@ -306,7 +335,13 @@ declare class Porygon extends EventEmitter {
306
335
  * 创建交互式多轮对话会话。
307
336
  * 自动管理 sessionId 和 resume,对调用方透明。
308
337
  */
309
- session(options?: Omit<PromptRequest, "prompt">): InteractiveSession;
338
+ session(options?: Omit<PromptRequest, "prompt">): Promise<InteractiveSession>;
339
+ /**
340
+ * 获取当前可用的工具列表。
341
+ * 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
342
+ * @param force 是否强制刷新缓存
343
+ */
344
+ getTools(force?: boolean): Promise<string[]>;
310
345
  /**
311
346
  * 注册拦截器
312
347
  * @param direction 拦截方向
@@ -327,8 +362,9 @@ declare class Porygon extends EventEmitter {
327
362
  /**
328
363
  * 检查单个后端的健康状态
329
364
  * @param backend 后端名称
365
+ * @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
330
366
  */
331
- checkBackend(backend: string): Promise<HealthCheckResult>;
367
+ checkBackend(backend: string, options?: CheckBackendOptions): Promise<HealthCheckResult>;
332
368
  /**
333
369
  * 对所有已注册后端进行健康检查。
334
370
  * 返回扁平化结构,包含 version/supported/warnings 等字段。
@@ -365,6 +401,8 @@ declare class Porygon extends EventEmitter {
365
401
  * - timeoutMs: request > defaults
366
402
  * - maxTurns: request > defaults
367
403
  * - cwd: request > backendConfig
404
+ * - disallowedTools: defaults + backendConfig + request 三层叠加去重
405
+ * - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
368
406
  * - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
369
407
  * 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
370
408
  *
@@ -671,6 +709,8 @@ declare abstract class AbstractAgentAdapter implements IAgentAdapter {
671
709
  */
672
710
  declare class ClaudeAdapter extends AbstractAgentAdapter {
673
711
  readonly backend = "claude";
712
+ /** 工具列表缓存 */
713
+ private cachedTools;
674
714
  /** CLI 命令名或路径 */
675
715
  private get cliCommand();
676
716
  /**
@@ -690,6 +730,12 @@ declare class ClaudeAdapter extends AbstractAgentAdapter {
690
730
  * @param request 提示请求参数
691
731
  */
692
732
  query(request: PromptRequest): AsyncGenerator<AgentMessage>;
733
+ /**
734
+ * 获取当前可用的工具列表。
735
+ * 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
736
+ * @param force 是否强制刷新缓存
737
+ */
738
+ getTools(force?: boolean): Promise<string[]>;
693
739
  /**
694
740
  * 列出 Claude 会话
695
741
  * @param options 查询选项
@@ -809,4 +855,4 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
809
855
  private getConfigPath;
810
856
  }
811
857
 
812
- export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
858
+ export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, type CheckBackendOptions, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
package/dist/index.d.ts CHANGED
@@ -23,6 +23,8 @@ interface BackendConfig {
23
23
  interactive?: boolean;
24
24
  /** CLI 可执行文件路径(如 Claude CLI 的自定义安装路径) */
25
25
  cliPath?: string;
26
+ /** 禁止使用的工具黑名单(后端级) */
27
+ disallowedTools?: string[];
26
28
  /** 透传给 CLI 的后端特定选项(向后兼容,推荐使用上方的显式字段) */
27
29
  options?: Record<string, unknown>;
28
30
  }
@@ -36,6 +38,8 @@ interface PorygonConfig {
36
38
  appendSystemPrompt?: string;
37
39
  timeoutMs?: number;
38
40
  maxTurns?: number;
41
+ /** 禁止使用的工具黑名单(全局默认) */
42
+ disallowedTools?: string[];
39
43
  };
40
44
  proxy?: ProxyConfig;
41
45
  }
@@ -63,7 +67,13 @@ interface PromptRequest {
63
67
  appendSystemPrompt?: string;
64
68
  model?: string;
65
69
  timeoutMs?: number;
66
- allowedTools?: string[];
70
+ /**
71
+ * 仅允许使用的工具白名单。
72
+ * 运行时通过 getTools() 获取全量工具列表,差集计算后转为 disallowedTools 传递给 CLI。
73
+ * 与 disallowedTools 互斥,同时设置时 onlyTools 优先。
74
+ */
75
+ onlyTools?: string[];
76
+ /** 禁止使用的工具黑名单 */
67
77
  disallowedTools?: string[];
68
78
  mcpServers?: Record<string, McpServerConfig>;
69
79
  maxTurns?: number;
@@ -180,6 +190,8 @@ interface IAgentAdapter {
180
190
  /** 获取可用模型列表 */
181
191
  listModels(): Promise<ModelInfo[]>;
182
192
  abort(sessionId: string): void;
193
+ /** 获取当前可用的工具列表(可选实现,支持缓存) */
194
+ getTools?(force?: boolean): Promise<string[]>;
183
195
  /** 删除会话及其资源(可选实现) */
184
196
  deleteSession?(sessionId: string): Promise<void>;
185
197
  dispose(): Promise<void>;
@@ -250,6 +262,23 @@ interface HealthCheckResult {
250
262
  supported?: boolean;
251
263
  warnings?: string[];
252
264
  error?: string;
265
+ /** deep 模式下,模型是否真正响应 */
266
+ modelVerified?: boolean;
267
+ }
268
+ /**
269
+ * checkBackend 的选项
270
+ */
271
+ interface CheckBackendOptions {
272
+ /**
273
+ * 启用深度检测:在基础 CLI/版本检查通过后,向模型发送一条极短测试消息,
274
+ * 验证 Token/模型/配额是否真正可用。
275
+ * 默认 false(仅做 CLI 存在性 + 版本检查)。
276
+ */
277
+ deep?: boolean;
278
+ /** deep 模式下使用的模型(可选,不传则用后端默认模型) */
279
+ model?: string;
280
+ /** deep 模式超时(毫秒),默认 15000 */
281
+ timeoutMs?: number;
253
282
  }
254
283
  /**
255
284
  * Porygon 事件类型定义
@@ -306,7 +335,13 @@ declare class Porygon extends EventEmitter {
306
335
  * 创建交互式多轮对话会话。
307
336
  * 自动管理 sessionId 和 resume,对调用方透明。
308
337
  */
309
- session(options?: Omit<PromptRequest, "prompt">): InteractiveSession;
338
+ session(options?: Omit<PromptRequest, "prompt">): Promise<InteractiveSession>;
339
+ /**
340
+ * 获取当前可用的工具列表。
341
+ * 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
342
+ * @param force 是否强制刷新缓存
343
+ */
344
+ getTools(force?: boolean): Promise<string[]>;
310
345
  /**
311
346
  * 注册拦截器
312
347
  * @param direction 拦截方向
@@ -327,8 +362,9 @@ declare class Porygon extends EventEmitter {
327
362
  /**
328
363
  * 检查单个后端的健康状态
329
364
  * @param backend 后端名称
365
+ * @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
330
366
  */
331
- checkBackend(backend: string): Promise<HealthCheckResult>;
367
+ checkBackend(backend: string, options?: CheckBackendOptions): Promise<HealthCheckResult>;
332
368
  /**
333
369
  * 对所有已注册后端进行健康检查。
334
370
  * 返回扁平化结构,包含 version/supported/warnings 等字段。
@@ -365,6 +401,8 @@ declare class Porygon extends EventEmitter {
365
401
  * - timeoutMs: request > defaults
366
402
  * - maxTurns: request > defaults
367
403
  * - cwd: request > backendConfig
404
+ * - disallowedTools: defaults + backendConfig + request 三层叠加去重
405
+ * - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
368
406
  * - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
369
407
  * 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
370
408
  *
@@ -671,6 +709,8 @@ declare abstract class AbstractAgentAdapter implements IAgentAdapter {
671
709
  */
672
710
  declare class ClaudeAdapter extends AbstractAgentAdapter {
673
711
  readonly backend = "claude";
712
+ /** 工具列表缓存 */
713
+ private cachedTools;
674
714
  /** CLI 命令名或路径 */
675
715
  private get cliCommand();
676
716
  /**
@@ -690,6 +730,12 @@ declare class ClaudeAdapter extends AbstractAgentAdapter {
690
730
  * @param request 提示请求参数
691
731
  */
692
732
  query(request: PromptRequest): AsyncGenerator<AgentMessage>;
733
+ /**
734
+ * 获取当前可用的工具列表。
735
+ * 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
736
+ * @param force 是否强制刷新缓存
737
+ */
738
+ getTools(force?: boolean): Promise<string[]>;
693
739
  /**
694
740
  * 列出 Claude 会话
695
741
  * @param options 查询选项
@@ -809,4 +855,4 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
809
855
  private getConfigPath;
810
856
  }
811
857
 
812
- export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
858
+ export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, type CheckBackendOptions, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
package/dist/index.js CHANGED
@@ -92,6 +92,7 @@ var BackendConfigSchema = z.object({
92
92
  apiKey: z.string().optional(),
93
93
  interactive: z.boolean().optional(),
94
94
  cliPath: z.string().optional(),
95
+ disallowedTools: z.array(z.string()).optional(),
95
96
  options: z.record(z.string(), z.unknown()).optional()
96
97
  });
97
98
  var PorygonConfigSchema = z.object({
@@ -100,7 +101,8 @@ var PorygonConfigSchema = z.object({
100
101
  defaults: z.object({
101
102
  appendSystemPrompt: z.string().optional(),
102
103
  timeoutMs: z.number().positive().optional(),
103
- maxTurns: z.number().int().positive().optional()
104
+ maxTurns: z.number().int().positive().optional(),
105
+ disallowedTools: z.array(z.string()).optional()
104
106
  }).optional(),
105
107
  proxy: ProxyConfigSchema.optional()
106
108
  });
@@ -234,7 +236,7 @@ var InterceptorManager = class {
234
236
  };
235
237
 
236
238
  // src/process/process-handle.ts
237
- import { spawn } from "child_process";
239
+ import spawn from "cross-spawn";
238
240
  import { EventEmitter } from "events";
239
241
  import { createInterface } from "readline";
240
242
  var GRACE_PERIOD_MS = 5e3;
@@ -883,6 +885,8 @@ var CLAUDE_MODELS = [
883
885
  ];
884
886
  var ClaudeAdapter = class extends AbstractAgentAdapter {
885
887
  backend = "claude";
888
+ /** 工具列表缓存 */
889
+ cachedTools = null;
886
890
  /** CLI 命令名或路径 */
887
891
  get cliCommand() {
888
892
  return this.config?.cliPath ?? "claude";
@@ -893,8 +897,9 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
893
897
  async isAvailable() {
894
898
  try {
895
899
  const proc = new EphemeralProcess();
900
+ const findCmd = process.platform === "win32" ? "where" : "which";
896
901
  const result = await proc.execute({
897
- command: "which",
902
+ command: findCmd,
898
903
  args: [this.cliCommand]
899
904
  });
900
905
  return result.exitCode === 0;
@@ -1007,6 +1012,40 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1007
1012
  this.processManager.removeEphemeral(sessionId);
1008
1013
  }
1009
1014
  }
1015
+ /**
1016
+ * 获取当前可用的工具列表。
1017
+ * 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
1018
+ * @param force 是否强制刷新缓存
1019
+ */
1020
+ async getTools(force) {
1021
+ if (!force && this.cachedTools) {
1022
+ return this.cachedTools;
1023
+ }
1024
+ const DEFAULT_TIMEOUT_MS2 = 15e3;
1025
+ let tools = [];
1026
+ for await (const message of this.query({
1027
+ prompt: "hi",
1028
+ maxTurns: 1,
1029
+ timeoutMs: DEFAULT_TIMEOUT_MS2
1030
+ })) {
1031
+ const raw = message.raw;
1032
+ if (message.type === "system" && raw?.tools) {
1033
+ const rawTools = raw.tools;
1034
+ if (Array.isArray(rawTools)) {
1035
+ tools = rawTools.map((t) => {
1036
+ if (typeof t === "string") return t;
1037
+ if (typeof t === "object" && t !== null && "name" in t) {
1038
+ return String(t.name);
1039
+ }
1040
+ return String(t);
1041
+ });
1042
+ break;
1043
+ }
1044
+ }
1045
+ }
1046
+ this.cachedTools = tools;
1047
+ return tools;
1048
+ }
1010
1049
  /**
1011
1050
  * 列出 Claude 会话
1012
1051
  * @param options 查询选项
@@ -1135,11 +1174,6 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1135
1174
  if (appendPrompt) {
1136
1175
  args.push("--append-system-prompt", appendPrompt);
1137
1176
  }
1138
- if (request.allowedTools && request.allowedTools.length > 0) {
1139
- for (const tool of request.allowedTools) {
1140
- args.push("--allowedTools", tool);
1141
- }
1142
- }
1143
1177
  if (request.disallowedTools && request.disallowedTools.length > 0) {
1144
1178
  for (const tool of request.disallowedTools) {
1145
1179
  args.push("--disallowedTools", tool);
@@ -1153,16 +1187,17 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1153
1187
  if (skipPerms) {
1154
1188
  args.push("--dangerously-skip-permissions");
1155
1189
  }
1156
- if (request.mcpServers) {
1190
+ if (request.mcpServers && Object.keys(request.mcpServers).length > 0) {
1191
+ const mcpConfig = {};
1157
1192
  for (const [name, config] of Object.entries(request.mcpServers)) {
1158
- const serverSpec = {
1193
+ mcpConfig[name] = {
1159
1194
  command: config.command,
1160
1195
  ...config.args ? { args: config.args } : {},
1161
1196
  ...config.env ? { env: config.env } : {},
1162
1197
  ...config.url ? { url: config.url } : {}
1163
1198
  };
1164
- args.push("--mcp-server", `${name}=${JSON.stringify(serverSpec)}`);
1165
1199
  }
1200
+ args.push("--mcp-config", JSON.stringify({ mcpServers: mcpConfig }));
1166
1201
  }
1167
1202
  return args;
1168
1203
  }
@@ -1837,7 +1872,7 @@ var Porygon = class extends EventEmitter2 {
1837
1872
  async *query(request) {
1838
1873
  const adapter = this.getAdapter(request.backend);
1839
1874
  const backendName = adapter.backend;
1840
- const mergedRequest = this.mergeRequest(request, backendName);
1875
+ const mergedRequest = await this.mergeRequest(request, backendName);
1841
1876
  const processedPrompt = await this.interceptors.processInput(
1842
1877
  mergedRequest.prompt,
1843
1878
  { backend: backendName, sessionId: mergedRequest.resume }
@@ -1879,10 +1914,10 @@ var Porygon = class extends EventEmitter2 {
1879
1914
  * 创建交互式多轮对话会话。
1880
1915
  * 自动管理 sessionId 和 resume,对调用方透明。
1881
1916
  */
1882
- session(options) {
1917
+ async session(options) {
1883
1918
  const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
1884
1919
  const adapter = this.getAdapter(backend);
1885
- const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
1920
+ const merged = await this.mergeRequest({ ...options, prompt: "" }, backend);
1886
1921
  const { prompt: _, ...baseRequest } = merged;
1887
1922
  return new InteractiveSession(
1888
1923
  crypto.randomUUID(),
@@ -1890,6 +1925,16 @@ var Porygon = class extends EventEmitter2 {
1890
1925
  baseRequest
1891
1926
  );
1892
1927
  }
1928
+ /**
1929
+ * 获取当前可用的工具列表。
1930
+ * 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
1931
+ * @param force 是否强制刷新缓存
1932
+ */
1933
+ async getTools(force) {
1934
+ const adapter = this.getAdapter();
1935
+ if (!adapter.getTools) return [];
1936
+ return adapter.getTools(force);
1937
+ }
1893
1938
  /**
1894
1939
  * 注册拦截器
1895
1940
  * @param direction 拦截方向
@@ -1916,8 +1961,9 @@ var Porygon = class extends EventEmitter2 {
1916
1961
  /**
1917
1962
  * 检查单个后端的健康状态
1918
1963
  * @param backend 后端名称
1964
+ * @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
1919
1965
  */
1920
- async checkBackend(backend) {
1966
+ async checkBackend(backend, options) {
1921
1967
  const adapter = this.getAdapter(backend);
1922
1968
  try {
1923
1969
  const available = await adapter.isAvailable();
@@ -1936,6 +1982,25 @@ var Porygon = class extends EventEmitter2 {
1936
1982
  if (!compat.supported) {
1937
1983
  this.emit("health:degraded", backend, compat.warnings.join("; "));
1938
1984
  }
1985
+ if (options?.deep) {
1986
+ try {
1987
+ const response = await this.run({
1988
+ prompt: "Reply with exactly: ok",
1989
+ backend,
1990
+ model: options.model,
1991
+ systemPrompt: "You are a health check probe. Reply with exactly one word: ok",
1992
+ timeoutMs: options.timeoutMs ?? 15e3,
1993
+ maxTurns: 1
1994
+ });
1995
+ result.modelVerified = typeof response === "string" && response.length > 0;
1996
+ } catch (err) {
1997
+ result.modelVerified = false;
1998
+ result.warnings = [
1999
+ ...result.warnings || [],
2000
+ `\u6A21\u578B\u9A8C\u8BC1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
2001
+ ];
2002
+ }
2003
+ }
1939
2004
  return result;
1940
2005
  } catch (err) {
1941
2006
  return {
@@ -2015,6 +2080,8 @@ var Porygon = class extends EventEmitter2 {
2015
2080
  * - timeoutMs: request > defaults
2016
2081
  * - maxTurns: request > defaults
2017
2082
  * - cwd: request > backendConfig
2083
+ * - disallowedTools: defaults + backendConfig + request 三层叠加去重
2084
+ * - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
2018
2085
  * - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
2019
2086
  * 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
2020
2087
  *
@@ -2022,7 +2089,7 @@ var Porygon = class extends EventEmitter2 {
2022
2089
  * @param backend 后端名称
2023
2090
  * @returns 合并后的请求
2024
2091
  */
2025
- mergeRequest(request, backend) {
2092
+ async mergeRequest(request, backend) {
2026
2093
  const backendConfig = this.config.backends?.[backend];
2027
2094
  const defaults = this.config.defaults;
2028
2095
  const appendParts = [];
@@ -2035,12 +2102,28 @@ var Porygon = class extends EventEmitter2 {
2035
2102
  if (request.appendSystemPrompt) {
2036
2103
  appendParts.push(request.appendSystemPrompt);
2037
2104
  }
2105
+ let mergedDisallowedTools;
2106
+ if (request.onlyTools) {
2107
+ const adapter = this.getAdapter(backend);
2108
+ const allTools = adapter.getTools ? await adapter.getTools() : [];
2109
+ const allowSet = new Set(request.onlyTools);
2110
+ const disallowed = allTools.filter((t) => !allowSet.has(t));
2111
+ mergedDisallowedTools = disallowed.length > 0 ? disallowed : void 0;
2112
+ } else {
2113
+ const disallowParts = [
2114
+ ...defaults?.disallowedTools ?? [],
2115
+ ...backendConfig?.disallowedTools ?? [],
2116
+ ...request.disallowedTools ?? []
2117
+ ];
2118
+ mergedDisallowedTools = disallowParts.length > 0 ? [...new Set(disallowParts)] : void 0;
2119
+ }
2038
2120
  return {
2039
2121
  ...request,
2040
2122
  model: request.model ?? backendConfig?.model,
2041
2123
  timeoutMs: request.timeoutMs ?? defaults?.timeoutMs,
2042
2124
  maxTurns: request.maxTurns ?? defaults?.maxTurns,
2043
2125
  cwd: request.cwd ?? backendConfig?.cwd,
2126
+ disallowedTools: mergedDisallowedTools,
2044
2127
  appendSystemPrompt: request.systemPrompt ? void 0 : appendParts.length > 0 ? appendParts.join("\n") : void 0
2045
2128
  };
2046
2129
  }