@renxqoo/renx-code 0.0.4 → 0.0.5
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/bin/renx.cjs +16 -0
- package/package.json +2 -45
- package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
- package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
- package/src/agent/runtime/runtime.test.ts +7 -4
- package/src/agent/runtime/runtime.ts +3 -9
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
- package/src/agent/runtime/source-modules.test.ts +16 -35
- package/src/agent/runtime/source-modules.ts +17 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
- package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
- package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
- package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
- package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
- package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
- package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
- package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
- package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
- package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
- package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
- package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
- package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
- package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
- package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
- package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
- package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
- package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
- package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
- package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
- package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
- package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
- package/vendor/agent-root/src/agent/agent/error.ts +198 -0
- package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
- package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
- package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
- package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
- package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
- package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
- package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
- package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
- package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
- package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
- package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
- package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
- package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
- package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
- package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
- package/vendor/agent-root/src/agent/app/index.ts +5 -0
- package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
- package/vendor/agent-root/src/agent/app/ports.ts +72 -0
- package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
- package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
- package/vendor/agent-root/src/agent/error-contract.ts +154 -0
- package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
- package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
- package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
- package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
- package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
- package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
- package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
- package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
- package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
- package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
- package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
- package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
- package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
- package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
- package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
- package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
- package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
- package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
- package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
- package/vendor/agent-root/src/agent/tool/error.ts +131 -0
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
- package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
- package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
- package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
- package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
- package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
- package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
- package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
- package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
- package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
- package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
- package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
- package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
- package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
- package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
- package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
- package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
- package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
- package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
- package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
- package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
- package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
- package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
- package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
- package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
- package/vendor/agent-root/src/agent/tool/task.ts +209 -0
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
- package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
- package/vendor/agent-root/src/agent/tool/types.ts +116 -0
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
- package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
- package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
- package/vendor/agent-root/src/agent/types.ts +232 -0
- package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
- package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
- package/vendor/agent-root/src/agent/utils/index.ts +16 -0
- package/vendor/agent-root/src/agent/utils/message.ts +171 -0
- package/vendor/agent-root/src/agent/utils/token.ts +28 -0
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
- package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
- package/vendor/agent-root/src/config/index.ts +54 -0
- package/vendor/agent-root/src/config/loader.ts +431 -0
- package/vendor/agent-root/src/config/paths.ts +30 -0
- package/vendor/agent-root/src/config/runtime.ts +163 -0
- package/vendor/agent-root/src/config/types.ts +70 -0
- package/vendor/agent-root/src/logger/index.ts +57 -0
- package/vendor/agent-root/src/logger/logger.ts +819 -0
- package/vendor/agent-root/src/logger/types.ts +150 -0
- package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
- package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
- package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
- package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
- package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
- package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
- package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
- package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
- package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
- package/vendor/agent-root/src/providers/http/client.ts +289 -0
- package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
- package/vendor/agent-root/src/providers/index.ts +76 -0
- package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
- package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
- package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
- package/vendor/agent-root/src/providers/registry.ts +135 -0
- package/vendor/agent-root/src/providers/types/api.ts +284 -0
- package/vendor/agent-root/src/providers/types/config.ts +58 -0
- package/vendor/agent-root/src/providers/types/errors.ts +323 -0
- package/vendor/agent-root/src/providers/types/index.ts +72 -0
- package/vendor/agent-root/src/providers/types/provider.ts +45 -0
- package/vendor/agent-root/src/providers/types/registry.ts +88 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* v8 ignore file */
|
|
2
|
+
/* c8 ignore file */
|
|
3
|
+
export interface Tool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LLMTool {
|
|
10
|
+
type: 'function';
|
|
11
|
+
function: Tool;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ToolCallFunction {
|
|
15
|
+
name: string;
|
|
16
|
+
arguments: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ToolCall {
|
|
20
|
+
id: string;
|
|
21
|
+
type: string;
|
|
22
|
+
index: number;
|
|
23
|
+
function: ToolCallFunction;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToolStreamChunk {
|
|
27
|
+
type: 'stdout' | 'stderr' | 'progress';
|
|
28
|
+
data: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ToolConfirmInfo {
|
|
32
|
+
toolCallId: string;
|
|
33
|
+
toolName: string;
|
|
34
|
+
arguments: string;
|
|
35
|
+
reason?: string;
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ToolDecision {
|
|
40
|
+
approved: boolean;
|
|
41
|
+
message?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ToolPolicyCheckInfo {
|
|
45
|
+
toolCallId: string;
|
|
46
|
+
toolName: string;
|
|
47
|
+
arguments: string;
|
|
48
|
+
parsedArguments: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ToolPolicyDecision {
|
|
52
|
+
allowed: boolean;
|
|
53
|
+
code?: string;
|
|
54
|
+
message?: string;
|
|
55
|
+
audit?: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ToolConcurrencyMode = 'parallel-safe' | 'exclusive';
|
|
59
|
+
|
|
60
|
+
export interface ToolConcurrencyPolicy {
|
|
61
|
+
mode: ToolConcurrencyMode;
|
|
62
|
+
lockKey?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 工具流式事件类型
|
|
67
|
+
*/
|
|
68
|
+
export type ToolStreamEventType =
|
|
69
|
+
| 'start'
|
|
70
|
+
| 'stdout'
|
|
71
|
+
| 'stderr'
|
|
72
|
+
| 'progress'
|
|
73
|
+
| 'artifact'
|
|
74
|
+
| 'info'
|
|
75
|
+
| 'end'
|
|
76
|
+
| 'error';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 工具事件输入
|
|
80
|
+
*/
|
|
81
|
+
export interface ToolStreamEventInput {
|
|
82
|
+
/** 事件类型 */
|
|
83
|
+
type: ToolStreamEventType;
|
|
84
|
+
/** 文本内容 */
|
|
85
|
+
content?: string;
|
|
86
|
+
/** 结构化数据 */
|
|
87
|
+
data?: unknown;
|
|
88
|
+
/** 自定义序号(可选) */
|
|
89
|
+
sequence?: number;
|
|
90
|
+
/** 自定义时间戳(可选) */
|
|
91
|
+
timestamp?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 工具执行上下文
|
|
96
|
+
*
|
|
97
|
+
* 传递给工具执行时的上下文信息
|
|
98
|
+
*/
|
|
99
|
+
export interface ToolExecutionContext {
|
|
100
|
+
/** 工具调用 ID */
|
|
101
|
+
toolCallId: string;
|
|
102
|
+
/** 循环索引 */
|
|
103
|
+
loopIndex: number;
|
|
104
|
+
/** Agent 实例引用 */
|
|
105
|
+
agent: unknown;
|
|
106
|
+
/** 工具流式事件发射器(可选) */
|
|
107
|
+
onChunk?: (event: ToolStreamEventInput) => void | Promise<void>;
|
|
108
|
+
onConfirm?: (info: ToolConfirmInfo) => Promise<ToolDecision>;
|
|
109
|
+
onPolicyCheck?: (info: ToolPolicyCheckInfo) => ToolPolicyDecision | Promise<ToolPolicyDecision>;
|
|
110
|
+
/** 当前调用是否已通过用户确认 */
|
|
111
|
+
confirmationApproved?: boolean;
|
|
112
|
+
/** 工具执行中断信号(超时/取消时触发) */
|
|
113
|
+
toolAbortSignal?: AbortSignal;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const AGENT_TOOL_TYPES_MODULE = 'renx-tool-types';
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseTool, ToolResult } from './base-tool';
|
|
3
|
+
import { ToolExecutionError } from './error';
|
|
4
|
+
import { WEB_FETCH_TOOL_DESCRIPTION } from './tool-prompts';
|
|
5
|
+
import type { ToolExecutionContext } from './types';
|
|
6
|
+
|
|
7
|
+
// 安全常量
|
|
8
|
+
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
10
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
11
|
+
|
|
12
|
+
// SSRF 防护:禁止访问的地址模式
|
|
13
|
+
const BLOCKED_HOST_PATTERNS: RegExp[] = [
|
|
14
|
+
// localhost 和回环地址
|
|
15
|
+
/^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])$/i,
|
|
16
|
+
// 内网 IP
|
|
17
|
+
/^10\./,
|
|
18
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
19
|
+
/^192\.168\./,
|
|
20
|
+
// 链路本地和云元数据地址
|
|
21
|
+
/^169\.254\./,
|
|
22
|
+
/^(metadata\.google\.internal|metadata\.azure|169\.254\.169\.254)$/i,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 检查 URL 是否为内网或敏感地址(SSRF 防护)
|
|
27
|
+
*/
|
|
28
|
+
function isBlockedAddress(url: string): { blocked: boolean; reason?: string } {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(url);
|
|
31
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
32
|
+
|
|
33
|
+
for (const pattern of BLOCKED_HOST_PATTERNS) {
|
|
34
|
+
if (pattern.test(hostname)) {
|
|
35
|
+
return { blocked: true, reason: `Blocked address: ${hostname}` };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 检查协议
|
|
40
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
41
|
+
return { blocked: true, reason: `Unsupported protocol: ${parsed.protocol}` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { blocked: false };
|
|
45
|
+
} catch {
|
|
46
|
+
return { blocked: true, reason: 'Invalid URL format' };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 安全地截断输出,避免内存溢出
|
|
52
|
+
*/
|
|
53
|
+
function truncateOutput(
|
|
54
|
+
content: string,
|
|
55
|
+
maxLength: number
|
|
56
|
+
): { output: string; truncated: boolean } {
|
|
57
|
+
if (content.length <= maxLength) {
|
|
58
|
+
return { output: content, truncated: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const marker = '\n\n[... Content Truncated ...]\n\n';
|
|
62
|
+
const available = maxLength - marker.length;
|
|
63
|
+
if (available <= 100) {
|
|
64
|
+
return { output: content.slice(0, maxLength), truncated: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const headLength = Math.floor(available * 0.7);
|
|
68
|
+
const tailLength = available - headLength;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
output: content.slice(0, headLength) + marker + content.slice(content.length - tailLength),
|
|
72
|
+
truncated: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 简单的 HTML 到纯文本转换(无外部依赖)
|
|
78
|
+
*/
|
|
79
|
+
function htmlToText(html: string): string {
|
|
80
|
+
let text = html;
|
|
81
|
+
// 移除 script 和 style 标签
|
|
82
|
+
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
83
|
+
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
84
|
+
// 移除 HTML 标签
|
|
85
|
+
text = text.replace(/<[^>]+>/g, ' ');
|
|
86
|
+
// 解码常见 HTML 实体
|
|
87
|
+
const entities: Record<string, string> = {
|
|
88
|
+
' ': ' ',
|
|
89
|
+
'&': '&',
|
|
90
|
+
'<': '<',
|
|
91
|
+
'>': '>',
|
|
92
|
+
'"': '"',
|
|
93
|
+
''': "'",
|
|
94
|
+
''': "'",
|
|
95
|
+
};
|
|
96
|
+
for (const [entity, char] of Object.entries(entities)) {
|
|
97
|
+
text = text.replace(new RegExp(entity, 'g'), char);
|
|
98
|
+
}
|
|
99
|
+
// 清理多余空白
|
|
100
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const schema = z
|
|
104
|
+
.object({
|
|
105
|
+
url: z.string().url().describe('The URL to fetch'),
|
|
106
|
+
extractMode: z
|
|
107
|
+
.enum(['text', 'markdown', 'html'])
|
|
108
|
+
.default('text')
|
|
109
|
+
.describe('Content extraction mode: text (plain text), markdown (simplified), or html (raw)'),
|
|
110
|
+
maxChars: z
|
|
111
|
+
.number()
|
|
112
|
+
.int()
|
|
113
|
+
.min(100)
|
|
114
|
+
.max(100_000)
|
|
115
|
+
.default(30_000)
|
|
116
|
+
.describe('Maximum characters to return'),
|
|
117
|
+
timeout: z
|
|
118
|
+
.number()
|
|
119
|
+
.int()
|
|
120
|
+
.min(1000)
|
|
121
|
+
.max(MAX_TIMEOUT_MS)
|
|
122
|
+
.default(DEFAULT_TIMEOUT_MS)
|
|
123
|
+
.describe('Request timeout in milliseconds'),
|
|
124
|
+
})
|
|
125
|
+
.strict();
|
|
126
|
+
|
|
127
|
+
export class WebFetchTool extends BaseTool<typeof schema> {
|
|
128
|
+
readonly name = 'web_fetch';
|
|
129
|
+
readonly description = WEB_FETCH_TOOL_DESCRIPTION;
|
|
130
|
+
readonly parameters = schema;
|
|
131
|
+
|
|
132
|
+
async execute(
|
|
133
|
+
args: z.input<typeof schema>,
|
|
134
|
+
_context?: ToolExecutionContext
|
|
135
|
+
): Promise<ToolResult> {
|
|
136
|
+
// Apply defaults (zod defaults may not be applied when calling execute directly)
|
|
137
|
+
const url = args.url;
|
|
138
|
+
const extractMode = args.extractMode ?? 'text';
|
|
139
|
+
const maxChars = args.maxChars ?? 30_000;
|
|
140
|
+
const timeout = args.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
141
|
+
|
|
142
|
+
// SSRF 防护检查
|
|
143
|
+
const blockCheck = isBlockedAddress(url);
|
|
144
|
+
if (blockCheck.blocked) {
|
|
145
|
+
throw new ToolExecutionError(`Security: ${blockCheck.reason}`, 2010);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(url, {
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
headers: {
|
|
155
|
+
'User-Agent': 'Mozilla/5.0 (compatible; AgentBot/1.0)',
|
|
156
|
+
Accept: 'text/html,text/plain,application/json,*/*',
|
|
157
|
+
},
|
|
158
|
+
redirect: 'follow',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw new ToolExecutionError(`HTTP ${response.status}: ${response.statusText}`, 2011);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 检查内容大小
|
|
166
|
+
const contentLength = response.headers.get('content-length');
|
|
167
|
+
if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE) {
|
|
168
|
+
throw new ToolExecutionError(
|
|
169
|
+
`Response too large: ${contentLength} bytes exceeds limit of ${MAX_RESPONSE_SIZE}`,
|
|
170
|
+
2012
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const contentType = response.headers.get('content-type') || '';
|
|
175
|
+
let body = await response.text();
|
|
176
|
+
|
|
177
|
+
// 限制响应大小
|
|
178
|
+
if (body.length > MAX_RESPONSE_SIZE) {
|
|
179
|
+
body = body.slice(0, MAX_RESPONSE_SIZE);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 根据模式提取内容
|
|
183
|
+
let extractedContent: string;
|
|
184
|
+
switch (extractMode) {
|
|
185
|
+
case 'html':
|
|
186
|
+
extractedContent = body;
|
|
187
|
+
break;
|
|
188
|
+
case 'markdown':
|
|
189
|
+
case 'text':
|
|
190
|
+
default:
|
|
191
|
+
extractedContent = htmlToText(body);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 截断到指定长度
|
|
196
|
+
const { output, truncated } = truncateOutput(extractedContent, maxChars);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
output: `URL: ${url}\nContent-Type: ${contentType}\nExtracted: ${extractMode}\n${truncated ? '(Content truncated)\n' : ''}\n${output}`,
|
|
201
|
+
metadata: {
|
|
202
|
+
url,
|
|
203
|
+
contentType,
|
|
204
|
+
extractMode,
|
|
205
|
+
truncated,
|
|
206
|
+
originalLength: extractedContent.length,
|
|
207
|
+
returnedLength: output.length,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof ToolExecutionError) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
215
|
+
throw new ToolExecutionError(`Request timeout after ${timeout}ms`, 2013);
|
|
216
|
+
}
|
|
217
|
+
throw new ToolExecutionError(
|
|
218
|
+
`Fetch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
219
|
+
2014
|
|
220
|
+
);
|
|
221
|
+
} finally {
|
|
222
|
+
clearTimeout(timeoutId);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default WebFetchTool;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseTool, ToolResult } from './base-tool';
|
|
3
|
+
import { ToolExecutionError } from './error';
|
|
4
|
+
import { WEB_SEARCH_TOOL_DESCRIPTION } from './tool-prompts';
|
|
5
|
+
import type { ToolExecutionContext } from './types';
|
|
6
|
+
|
|
7
|
+
// 搜索结果接口
|
|
8
|
+
interface SearchResult {
|
|
9
|
+
title: string;
|
|
10
|
+
url: string;
|
|
11
|
+
snippet: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SearchResponse {
|
|
16
|
+
query: string;
|
|
17
|
+
results: SearchResult[];
|
|
18
|
+
provider: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tavily 搜索实现
|
|
23
|
+
*/
|
|
24
|
+
async function searchWithTavily(query: string, maxResults: number): Promise<SearchResponse> {
|
|
25
|
+
const apiKey = process.env.TAVILY_API_KEY;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
throw new ToolExecutionError('TAVILY_API_KEY environment variable is not set', 2020);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const response = await fetch('https://api.tavily.com/search', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
Authorization: `Bearer ${apiKey}`,
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
query,
|
|
38
|
+
max_results: maxResults,
|
|
39
|
+
search_depth: 'basic',
|
|
40
|
+
include_answer: false,
|
|
41
|
+
include_raw_content: false,
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new ToolExecutionError(
|
|
47
|
+
`Tavily API error: ${response.status} ${response.statusText}`,
|
|
48
|
+
2021
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const data = (await response.json()) as {
|
|
53
|
+
query?: string;
|
|
54
|
+
results?: Array<{ title?: string; url?: string; content?: string; score?: number }>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
query: data.query || query,
|
|
59
|
+
provider: 'tavily',
|
|
60
|
+
results: (data.results || []).map((r) => ({
|
|
61
|
+
title: r.title || 'No title',
|
|
62
|
+
url: r.url || '',
|
|
63
|
+
snippet: r.content || '',
|
|
64
|
+
score: r.score,
|
|
65
|
+
})),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Brave Search 实现
|
|
71
|
+
*/
|
|
72
|
+
async function searchWithBrave(query: string, maxResults: number): Promise<SearchResponse> {
|
|
73
|
+
const apiKey = process.env.BRAVE_SEARCH_API_KEY;
|
|
74
|
+
if (!apiKey) {
|
|
75
|
+
throw new ToolExecutionError('BRAVE_SEARCH_API_KEY environment variable is not set', 2022);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const url = new URL('https://api.search.brave.com/res/v1/web/search');
|
|
79
|
+
url.searchParams.set('q', query);
|
|
80
|
+
url.searchParams.set('count', String(maxResults));
|
|
81
|
+
|
|
82
|
+
const response = await fetch(url.toString(), {
|
|
83
|
+
headers: {
|
|
84
|
+
Accept: 'application/json',
|
|
85
|
+
'Accept-Encoding': 'identity',
|
|
86
|
+
'X-Subscription-Token': apiKey,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new ToolExecutionError(
|
|
92
|
+
`Brave Search API error: ${response.status} ${response.statusText}`,
|
|
93
|
+
2023
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = (await response.json()) as {
|
|
98
|
+
query?: { original?: string };
|
|
99
|
+
web?: { results?: Array<{ title?: string; url?: string; description?: string }> };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
query: data.query?.original || query,
|
|
104
|
+
provider: 'brave',
|
|
105
|
+
results: (data.web?.results || []).map((r) => ({
|
|
106
|
+
title: r.title || 'No title',
|
|
107
|
+
url: r.url || '',
|
|
108
|
+
snippet: r.description || '',
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 根据可用的 API Key 选择搜索提供商
|
|
115
|
+
*/
|
|
116
|
+
function getSearchProvider(): 'tavily' | 'brave' {
|
|
117
|
+
if (process.env.TAVILY_API_KEY) return 'tavily';
|
|
118
|
+
if (process.env.BRAVE_SEARCH_API_KEY) return 'brave';
|
|
119
|
+
throw new ToolExecutionError(
|
|
120
|
+
'No search API key configured. Set TAVILY_API_KEY or BRAVE_SEARCH_API_KEY environment variable.',
|
|
121
|
+
2024
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const schema = z
|
|
126
|
+
.object({
|
|
127
|
+
query: z.string().min(1).max(500).describe('Search query'),
|
|
128
|
+
maxResults: z
|
|
129
|
+
.number()
|
|
130
|
+
.int()
|
|
131
|
+
.min(1)
|
|
132
|
+
.max(10)
|
|
133
|
+
.default(5)
|
|
134
|
+
.describe('Maximum number of results (1-10)'),
|
|
135
|
+
provider: z
|
|
136
|
+
.enum(['tavily', 'brave', 'auto'])
|
|
137
|
+
.default('auto')
|
|
138
|
+
.describe('Search provider: tavily, brave, or auto (uses first available)'),
|
|
139
|
+
})
|
|
140
|
+
.strict();
|
|
141
|
+
|
|
142
|
+
export class WebSearchTool extends BaseTool<typeof schema> {
|
|
143
|
+
readonly name = 'web_search';
|
|
144
|
+
readonly description = WEB_SEARCH_TOOL_DESCRIPTION;
|
|
145
|
+
readonly parameters = schema;
|
|
146
|
+
|
|
147
|
+
async execute(
|
|
148
|
+
args: z.input<typeof schema>,
|
|
149
|
+
_context?: ToolExecutionContext
|
|
150
|
+
): Promise<ToolResult> {
|
|
151
|
+
// Apply defaults (zod defaults may not be applied when calling execute directly)
|
|
152
|
+
const query = args.query;
|
|
153
|
+
const maxResults = args.maxResults ?? 5;
|
|
154
|
+
const provider = args.provider ?? 'auto';
|
|
155
|
+
|
|
156
|
+
// 确定使用的搜索提供商
|
|
157
|
+
const selectedProvider = provider === 'auto' ? getSearchProvider() : provider;
|
|
158
|
+
|
|
159
|
+
// 执行搜索
|
|
160
|
+
let response: SearchResponse;
|
|
161
|
+
switch (selectedProvider) {
|
|
162
|
+
case 'tavily':
|
|
163
|
+
response = await searchWithTavily(query, maxResults);
|
|
164
|
+
break;
|
|
165
|
+
case 'brave':
|
|
166
|
+
response = await searchWithBrave(query, maxResults);
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
throw new ToolExecutionError(`Unknown search provider: ${selectedProvider}`, 2025);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 格式化输出
|
|
173
|
+
const lines: string[] = [
|
|
174
|
+
`Search: "${response.query}"`,
|
|
175
|
+
`Provider: ${response.provider}`,
|
|
176
|
+
`Results: ${response.results.length}`,
|
|
177
|
+
'',
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < response.results.length; i++) {
|
|
181
|
+
const r = response.results[i];
|
|
182
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
183
|
+
lines.push(` URL: ${r.url}`);
|
|
184
|
+
if (r.score !== undefined) {
|
|
185
|
+
lines.push(` Score: ${r.score.toFixed(2)}`);
|
|
186
|
+
}
|
|
187
|
+
if (r.snippet) {
|
|
188
|
+
// 截断过长的摘要
|
|
189
|
+
const snippet = r.snippet.length > 300 ? r.snippet.slice(0, 300) + '...' : r.snippet;
|
|
190
|
+
lines.push(` ${snippet}`);
|
|
191
|
+
}
|
|
192
|
+
lines.push('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
output: lines.join('\n'),
|
|
198
|
+
metadata: {
|
|
199
|
+
query: response.query,
|
|
200
|
+
provider: response.provider,
|
|
201
|
+
resultCount: response.results.length,
|
|
202
|
+
results: response.results,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default WebSearchTool;
|