@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/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
  });
@@ -817,14 +819,15 @@ function mapClaudeEvent(event, sessionId) {
817
819
  return [];
818
820
  }
819
821
  if (isResultEvent(event)) {
822
+ const usage = event.usage;
820
823
  return [{
821
824
  ...baseFields,
822
825
  type: "result",
823
826
  text: event.result,
824
- costUsd: event.cost_usd,
827
+ costUsd: event.total_cost_usd ?? event.cost_usd,
825
828
  durationMs: event.duration_ms,
826
- inputTokens: event.input_tokens,
827
- outputTokens: event.output_tokens
829
+ inputTokens: usage?.input_tokens ?? event.input_tokens,
830
+ outputTokens: usage?.output_tokens ?? event.output_tokens
828
831
  }];
829
832
  }
830
833
  return [];
@@ -882,6 +885,8 @@ var CLAUDE_MODELS = [
882
885
  ];
883
886
  var ClaudeAdapter = class extends AbstractAgentAdapter {
884
887
  backend = "claude";
888
+ /** 工具列表缓存 */
889
+ cachedTools = null;
885
890
  /** CLI 命令名或路径 */
886
891
  get cliCommand() {
887
892
  return this.config?.cliPath ?? "claude";
@@ -1006,6 +1011,40 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1006
1011
  this.processManager.removeEphemeral(sessionId);
1007
1012
  }
1008
1013
  }
1014
+ /**
1015
+ * 获取当前可用的工具列表。
1016
+ * 首次调用通过发起最小化对话从 system 事件中提取 tools 字段,结果会被缓存。
1017
+ * @param force 是否强制刷新缓存
1018
+ */
1019
+ async getTools(force) {
1020
+ if (!force && this.cachedTools) {
1021
+ return this.cachedTools;
1022
+ }
1023
+ const DEFAULT_TIMEOUT_MS2 = 15e3;
1024
+ let tools = [];
1025
+ for await (const message of this.query({
1026
+ prompt: "hi",
1027
+ maxTurns: 1,
1028
+ timeoutMs: DEFAULT_TIMEOUT_MS2
1029
+ })) {
1030
+ const raw = message.raw;
1031
+ if (message.type === "system" && raw?.tools) {
1032
+ const rawTools = raw.tools;
1033
+ if (Array.isArray(rawTools)) {
1034
+ tools = rawTools.map((t) => {
1035
+ if (typeof t === "string") return t;
1036
+ if (typeof t === "object" && t !== null && "name" in t) {
1037
+ return String(t.name);
1038
+ }
1039
+ return String(t);
1040
+ });
1041
+ break;
1042
+ }
1043
+ }
1044
+ }
1045
+ this.cachedTools = tools;
1046
+ return tools;
1047
+ }
1009
1048
  /**
1010
1049
  * 列出 Claude 会话
1011
1050
  * @param options 查询选项
@@ -1134,11 +1173,6 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1134
1173
  if (appendPrompt) {
1135
1174
  args.push("--append-system-prompt", appendPrompt);
1136
1175
  }
1137
- if (request.allowedTools && request.allowedTools.length > 0) {
1138
- for (const tool of request.allowedTools) {
1139
- args.push("--allowedTools", tool);
1140
- }
1141
- }
1142
1176
  if (request.disallowedTools && request.disallowedTools.length > 0) {
1143
1177
  for (const tool of request.disallowedTools) {
1144
1178
  args.push("--disallowedTools", tool);
@@ -1152,16 +1186,17 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1152
1186
  if (skipPerms) {
1153
1187
  args.push("--dangerously-skip-permissions");
1154
1188
  }
1155
- if (request.mcpServers) {
1189
+ if (request.mcpServers && Object.keys(request.mcpServers).length > 0) {
1190
+ const mcpConfig = {};
1156
1191
  for (const [name, config] of Object.entries(request.mcpServers)) {
1157
- const serverSpec = {
1192
+ mcpConfig[name] = {
1158
1193
  command: config.command,
1159
1194
  ...config.args ? { args: config.args } : {},
1160
1195
  ...config.env ? { env: config.env } : {},
1161
1196
  ...config.url ? { url: config.url } : {}
1162
1197
  };
1163
- args.push("--mcp-server", `${name}=${JSON.stringify(serverSpec)}`);
1164
1198
  }
1199
+ args.push("--mcp-config", JSON.stringify({ mcpServers: mcpConfig }));
1165
1200
  }
1166
1201
  return args;
1167
1202
  }
@@ -1836,7 +1871,7 @@ var Porygon = class extends EventEmitter2 {
1836
1871
  async *query(request) {
1837
1872
  const adapter = this.getAdapter(request.backend);
1838
1873
  const backendName = adapter.backend;
1839
- const mergedRequest = this.mergeRequest(request, backendName);
1874
+ const mergedRequest = await this.mergeRequest(request, backendName);
1840
1875
  const processedPrompt = await this.interceptors.processInput(
1841
1876
  mergedRequest.prompt,
1842
1877
  { backend: backendName, sessionId: mergedRequest.resume }
@@ -1878,10 +1913,10 @@ var Porygon = class extends EventEmitter2 {
1878
1913
  * 创建交互式多轮对话会话。
1879
1914
  * 自动管理 sessionId 和 resume,对调用方透明。
1880
1915
  */
1881
- session(options) {
1916
+ async session(options) {
1882
1917
  const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
1883
1918
  const adapter = this.getAdapter(backend);
1884
- const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
1919
+ const merged = await this.mergeRequest({ ...options, prompt: "" }, backend);
1885
1920
  const { prompt: _, ...baseRequest } = merged;
1886
1921
  return new InteractiveSession(
1887
1922
  crypto.randomUUID(),
@@ -1889,6 +1924,16 @@ var Porygon = class extends EventEmitter2 {
1889
1924
  baseRequest
1890
1925
  );
1891
1926
  }
1927
+ /**
1928
+ * 获取当前可用的工具列表。
1929
+ * 首次调用通过发起最小化对话从 system 事件中提取工具清单,结果会被缓存。
1930
+ * @param force 是否强制刷新缓存
1931
+ */
1932
+ async getTools(force) {
1933
+ const adapter = this.getAdapter();
1934
+ if (!adapter.getTools) return [];
1935
+ return adapter.getTools(force);
1936
+ }
1892
1937
  /**
1893
1938
  * 注册拦截器
1894
1939
  * @param direction 拦截方向
@@ -1915,8 +1960,9 @@ var Porygon = class extends EventEmitter2 {
1915
1960
  /**
1916
1961
  * 检查单个后端的健康状态
1917
1962
  * @param backend 后端名称
1963
+ * @param options 检测选项。传入 `{ deep: true }` 时会向模型发送一条测试消息验证可用性。
1918
1964
  */
1919
- async checkBackend(backend) {
1965
+ async checkBackend(backend, options) {
1920
1966
  const adapter = this.getAdapter(backend);
1921
1967
  try {
1922
1968
  const available = await adapter.isAvailable();
@@ -1935,6 +1981,25 @@ var Porygon = class extends EventEmitter2 {
1935
1981
  if (!compat.supported) {
1936
1982
  this.emit("health:degraded", backend, compat.warnings.join("; "));
1937
1983
  }
1984
+ if (options?.deep) {
1985
+ try {
1986
+ const response = await this.run({
1987
+ prompt: "Reply with exactly: ok",
1988
+ backend,
1989
+ model: options.model,
1990
+ systemPrompt: "You are a health check probe. Reply with exactly one word: ok",
1991
+ timeoutMs: options.timeoutMs ?? 15e3,
1992
+ maxTurns: 1
1993
+ });
1994
+ result.modelVerified = typeof response === "string" && response.length > 0;
1995
+ } catch (err) {
1996
+ result.modelVerified = false;
1997
+ result.warnings = [
1998
+ ...result.warnings || [],
1999
+ `\u6A21\u578B\u9A8C\u8BC1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
2000
+ ];
2001
+ }
2002
+ }
1938
2003
  return result;
1939
2004
  } catch (err) {
1940
2005
  return {
@@ -2014,6 +2079,8 @@ var Porygon = class extends EventEmitter2 {
2014
2079
  * - timeoutMs: request > defaults
2015
2080
  * - maxTurns: request > defaults
2016
2081
  * - cwd: request > backendConfig
2082
+ * - disallowedTools: defaults + backendConfig + request 三层叠加去重
2083
+ * - onlyTools: 通过 getTools() 获取全量列表,差集计算转为 disallowedTools(与 disallowedTools 互斥,onlyTools 优先)
2017
2084
  * - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
2018
2085
  * 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
2019
2086
  *
@@ -2021,7 +2088,7 @@ var Porygon = class extends EventEmitter2 {
2021
2088
  * @param backend 后端名称
2022
2089
  * @returns 合并后的请求
2023
2090
  */
2024
- mergeRequest(request, backend) {
2091
+ async mergeRequest(request, backend) {
2025
2092
  const backendConfig = this.config.backends?.[backend];
2026
2093
  const defaults = this.config.defaults;
2027
2094
  const appendParts = [];
@@ -2034,12 +2101,28 @@ var Porygon = class extends EventEmitter2 {
2034
2101
  if (request.appendSystemPrompt) {
2035
2102
  appendParts.push(request.appendSystemPrompt);
2036
2103
  }
2104
+ let mergedDisallowedTools;
2105
+ if (request.onlyTools) {
2106
+ const adapter = this.getAdapter(backend);
2107
+ const allTools = adapter.getTools ? await adapter.getTools() : [];
2108
+ const allowSet = new Set(request.onlyTools);
2109
+ const disallowed = allTools.filter((t) => !allowSet.has(t));
2110
+ mergedDisallowedTools = disallowed.length > 0 ? disallowed : void 0;
2111
+ } else {
2112
+ const disallowParts = [
2113
+ ...defaults?.disallowedTools ?? [],
2114
+ ...backendConfig?.disallowedTools ?? [],
2115
+ ...request.disallowedTools ?? []
2116
+ ];
2117
+ mergedDisallowedTools = disallowParts.length > 0 ? [...new Set(disallowParts)] : void 0;
2118
+ }
2037
2119
  return {
2038
2120
  ...request,
2039
2121
  model: request.model ?? backendConfig?.model,
2040
2122
  timeoutMs: request.timeoutMs ?? defaults?.timeoutMs,
2041
2123
  maxTurns: request.maxTurns ?? defaults?.maxTurns,
2042
2124
  cwd: request.cwd ?? backendConfig?.cwd,
2125
+ disallowedTools: mergedDisallowedTools,
2043
2126
  appendSystemPrompt: request.systemPrompt ? void 0 : appendParts.length > 0 ? appendParts.join("\n") : void 0
2044
2127
  };
2045
2128
  }