@clinebot/core 0.0.22 → 0.0.24

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.
Files changed (260) hide show
  1. package/dist/ClineCore.d.ts +110 -0
  2. package/dist/ClineCore.d.ts.map +1 -0
  3. package/dist/account/cline-account-service.d.ts +2 -1
  4. package/dist/account/cline-account-service.d.ts.map +1 -1
  5. package/dist/account/index.d.ts +1 -1
  6. package/dist/account/index.d.ts.map +1 -1
  7. package/dist/account/rpc.d.ts +3 -1
  8. package/dist/account/rpc.d.ts.map +1 -1
  9. package/dist/account/types.d.ts +3 -0
  10. package/dist/account/types.d.ts.map +1 -1
  11. package/dist/agents/plugin-loader.d.ts.map +1 -1
  12. package/dist/agents/plugin-sandbox-bootstrap.js +17 -17
  13. package/dist/auth/client.d.ts +1 -1
  14. package/dist/auth/client.d.ts.map +1 -1
  15. package/dist/auth/cline.d.ts +1 -1
  16. package/dist/auth/cline.d.ts.map +1 -1
  17. package/dist/auth/codex.d.ts +1 -1
  18. package/dist/auth/codex.d.ts.map +1 -1
  19. package/dist/auth/oca.d.ts +1 -1
  20. package/dist/auth/oca.d.ts.map +1 -1
  21. package/dist/auth/utils.d.ts +2 -2
  22. package/dist/auth/utils.d.ts.map +1 -1
  23. package/dist/index.d.ts +50 -5
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +949 -0
  26. package/dist/providers/local-provider-service.d.ts +4 -4
  27. package/dist/providers/local-provider-service.d.ts.map +1 -1
  28. package/dist/runtime/runtime-builder.d.ts +1 -0
  29. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  30. package/dist/runtime/session-runtime.d.ts +2 -1
  31. package/dist/runtime/session-runtime.d.ts.map +1 -1
  32. package/dist/runtime/team-runtime-registry.d.ts +13 -0
  33. package/dist/runtime/team-runtime-registry.d.ts.map +1 -0
  34. package/dist/session/default-session-manager.d.ts +2 -2
  35. package/dist/session/default-session-manager.d.ts.map +1 -1
  36. package/dist/session/rpc-runtime-ensure.d.ts +53 -0
  37. package/dist/session/rpc-runtime-ensure.d.ts.map +1 -0
  38. package/dist/session/session-config-builder.d.ts +2 -3
  39. package/dist/session/session-config-builder.d.ts.map +1 -1
  40. package/dist/session/session-host.d.ts +8 -18
  41. package/dist/session/session-host.d.ts.map +1 -1
  42. package/dist/session/session-manager.d.ts +1 -1
  43. package/dist/session/session-manager.d.ts.map +1 -1
  44. package/dist/session/session-manifest.d.ts +1 -2
  45. package/dist/session/session-manifest.d.ts.map +1 -1
  46. package/dist/session/unified-session-persistence-service.d.ts +2 -2
  47. package/dist/session/unified-session-persistence-service.d.ts.map +1 -1
  48. package/dist/session/utils/helpers.d.ts +1 -1
  49. package/dist/session/utils/helpers.d.ts.map +1 -1
  50. package/dist/session/utils/types.d.ts +1 -1
  51. package/dist/session/utils/types.d.ts.map +1 -1
  52. package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  53. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
  54. package/dist/telemetry/distinct-id.d.ts +2 -0
  55. package/dist/telemetry/distinct-id.d.ts.map +1 -0
  56. package/dist/telemetry/{opentelemetry.d.ts → index.d.ts} +1 -1
  57. package/dist/telemetry/index.d.ts.map +1 -0
  58. package/dist/telemetry/index.js +28 -0
  59. package/dist/tools/constants.d.ts +1 -1
  60. package/dist/tools/constants.d.ts.map +1 -1
  61. package/dist/tools/definitions.d.ts +3 -3
  62. package/dist/tools/definitions.d.ts.map +1 -1
  63. package/dist/tools/executors/apply-patch.d.ts +1 -1
  64. package/dist/tools/executors/apply-patch.d.ts.map +1 -1
  65. package/dist/tools/executors/bash.d.ts +1 -1
  66. package/dist/tools/executors/bash.d.ts.map +1 -1
  67. package/dist/tools/executors/editor.d.ts +1 -1
  68. package/dist/tools/executors/editor.d.ts.map +1 -1
  69. package/dist/tools/executors/file-read.d.ts +1 -1
  70. package/dist/tools/executors/file-read.d.ts.map +1 -1
  71. package/dist/tools/executors/index.d.ts +14 -14
  72. package/dist/tools/executors/index.d.ts.map +1 -1
  73. package/dist/tools/executors/search.d.ts +1 -1
  74. package/dist/tools/executors/search.d.ts.map +1 -1
  75. package/dist/tools/executors/web-fetch.d.ts +1 -1
  76. package/dist/tools/executors/web-fetch.d.ts.map +1 -1
  77. package/dist/tools/helpers.d.ts +1 -1
  78. package/dist/tools/helpers.d.ts.map +1 -1
  79. package/dist/tools/index.d.ts +10 -10
  80. package/dist/tools/index.d.ts.map +1 -1
  81. package/dist/tools/model-tool-routing.d.ts +1 -1
  82. package/dist/tools/model-tool-routing.d.ts.map +1 -1
  83. package/dist/tools/presets.d.ts +1 -1
  84. package/dist/tools/presets.d.ts.map +1 -1
  85. package/dist/types/common.d.ts +17 -8
  86. package/dist/types/common.d.ts.map +1 -1
  87. package/dist/types/config.d.ts +4 -3
  88. package/dist/types/config.d.ts.map +1 -1
  89. package/dist/types/provider-settings.d.ts +1 -1
  90. package/dist/types/provider-settings.d.ts.map +1 -1
  91. package/dist/types.d.ts +5 -2
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/version.d.ts +2 -0
  94. package/dist/version.d.ts.map +1 -0
  95. package/package.json +44 -38
  96. package/src/ClineCore.ts +137 -0
  97. package/src/account/cline-account-service.test.ts +101 -0
  98. package/src/account/cline-account-service.ts +300 -0
  99. package/src/account/featurebase-token.test.ts +175 -0
  100. package/src/account/index.ts +23 -0
  101. package/src/account/rpc.test.ts +63 -0
  102. package/src/account/rpc.ts +185 -0
  103. package/src/account/types.ts +102 -0
  104. package/src/agents/agent-config-loader.test.ts +236 -0
  105. package/src/agents/agent-config-loader.ts +108 -0
  106. package/src/agents/agent-config-parser.ts +198 -0
  107. package/src/agents/hooks-config-loader.test.ts +20 -0
  108. package/src/agents/hooks-config-loader.ts +118 -0
  109. package/src/agents/index.ts +85 -0
  110. package/src/agents/plugin-config-loader.test.ts +140 -0
  111. package/src/agents/plugin-config-loader.ts +97 -0
  112. package/src/agents/plugin-loader.test.ts +210 -0
  113. package/src/agents/plugin-loader.ts +175 -0
  114. package/src/agents/plugin-sandbox-bootstrap.ts +448 -0
  115. package/src/agents/plugin-sandbox.test.ts +296 -0
  116. package/src/agents/plugin-sandbox.ts +341 -0
  117. package/src/agents/unified-config-file-watcher.test.ts +196 -0
  118. package/src/agents/unified-config-file-watcher.ts +483 -0
  119. package/src/agents/user-instruction-config-loader.test.ts +158 -0
  120. package/src/agents/user-instruction-config-loader.ts +438 -0
  121. package/src/auth/client.test.ts +40 -0
  122. package/src/auth/client.ts +25 -0
  123. package/src/auth/cline.test.ts +130 -0
  124. package/src/auth/cline.ts +420 -0
  125. package/src/auth/codex.test.ts +170 -0
  126. package/src/auth/codex.ts +491 -0
  127. package/src/auth/oca.test.ts +215 -0
  128. package/src/auth/oca.ts +573 -0
  129. package/src/auth/server.ts +216 -0
  130. package/src/auth/types.ts +81 -0
  131. package/src/auth/utils.test.ts +128 -0
  132. package/src/auth/utils.ts +247 -0
  133. package/src/chat/chat-schema.ts +82 -0
  134. package/src/index.ts +479 -0
  135. package/src/input/file-indexer.d.ts +11 -0
  136. package/src/input/file-indexer.test.ts +127 -0
  137. package/src/input/file-indexer.ts +327 -0
  138. package/src/input/index.ts +7 -0
  139. package/src/input/mention-enricher.test.ts +85 -0
  140. package/src/input/mention-enricher.ts +122 -0
  141. package/src/mcp/config-loader.test.ts +238 -0
  142. package/src/mcp/config-loader.ts +219 -0
  143. package/src/mcp/index.ts +26 -0
  144. package/src/mcp/manager.test.ts +106 -0
  145. package/src/mcp/manager.ts +262 -0
  146. package/src/mcp/types.ts +88 -0
  147. package/src/providers/local-provider-registry.ts +232 -0
  148. package/src/providers/local-provider-service.test.ts +783 -0
  149. package/src/providers/local-provider-service.ts +471 -0
  150. package/src/runtime/commands.test.ts +98 -0
  151. package/src/runtime/commands.ts +83 -0
  152. package/src/runtime/hook-file-hooks.test.ts +237 -0
  153. package/src/runtime/hook-file-hooks.ts +859 -0
  154. package/src/runtime/index.ts +37 -0
  155. package/src/runtime/rules.ts +34 -0
  156. package/src/runtime/runtime-builder.team-persistence.test.ts +245 -0
  157. package/src/runtime/runtime-builder.test.ts +371 -0
  158. package/src/runtime/runtime-builder.ts +631 -0
  159. package/src/runtime/runtime-parity.test.ts +143 -0
  160. package/src/runtime/sandbox/subprocess-sandbox.ts +231 -0
  161. package/src/runtime/session-runtime.ts +49 -0
  162. package/src/runtime/skills.ts +44 -0
  163. package/src/runtime/team-runtime-registry.ts +46 -0
  164. package/src/runtime/tool-approval.ts +104 -0
  165. package/src/runtime/workflows.test.ts +119 -0
  166. package/src/runtime/workflows.ts +45 -0
  167. package/src/session/default-session-manager.e2e.test.ts +384 -0
  168. package/src/session/default-session-manager.test.ts +1931 -0
  169. package/src/session/default-session-manager.ts +1422 -0
  170. package/src/session/file-session-service.ts +280 -0
  171. package/src/session/index.ts +45 -0
  172. package/src/session/rpc-runtime-ensure.ts +521 -0
  173. package/src/session/rpc-session-service.ts +107 -0
  174. package/src/session/rpc-spawn-lease.test.ts +49 -0
  175. package/src/session/rpc-spawn-lease.ts +122 -0
  176. package/src/session/runtime-oauth-token-manager.test.ts +137 -0
  177. package/src/session/runtime-oauth-token-manager.ts +272 -0
  178. package/src/session/session-agent-events.ts +248 -0
  179. package/src/session/session-artifacts.ts +106 -0
  180. package/src/session/session-config-builder.ts +113 -0
  181. package/src/session/session-graph.ts +92 -0
  182. package/src/session/session-host.test.ts +89 -0
  183. package/src/session/session-host.ts +205 -0
  184. package/src/session/session-manager.ts +69 -0
  185. package/src/session/session-manifest.ts +29 -0
  186. package/src/session/session-service.team-persistence.test.ts +48 -0
  187. package/src/session/session-service.ts +673 -0
  188. package/src/session/session-team-coordination.ts +229 -0
  189. package/src/session/session-telemetry.ts +100 -0
  190. package/src/session/sqlite-rpc-session-backend.ts +303 -0
  191. package/src/session/unified-session-persistence-service.test.ts +85 -0
  192. package/src/session/unified-session-persistence-service.ts +994 -0
  193. package/src/session/utils/helpers.ts +139 -0
  194. package/src/session/utils/types.ts +57 -0
  195. package/src/session/utils/usage.ts +32 -0
  196. package/src/session/workspace-manager.ts +98 -0
  197. package/src/session/workspace-manifest.ts +100 -0
  198. package/src/storage/artifact-store.ts +1 -0
  199. package/src/storage/file-team-store.ts +257 -0
  200. package/src/storage/index.ts +11 -0
  201. package/src/storage/provider-settings-legacy-migration.test.ts +424 -0
  202. package/src/storage/provider-settings-legacy-migration.ts +826 -0
  203. package/src/storage/provider-settings-manager.test.ts +191 -0
  204. package/src/storage/provider-settings-manager.ts +152 -0
  205. package/src/storage/session-store.ts +1 -0
  206. package/src/storage/sqlite-session-store.ts +275 -0
  207. package/src/storage/sqlite-team-store.ts +454 -0
  208. package/src/storage/team-store.ts +40 -0
  209. package/src/team/index.ts +4 -0
  210. package/src/team/projections.ts +285 -0
  211. package/src/telemetry/ITelemetryAdapter.ts +94 -0
  212. package/src/telemetry/LoggerTelemetryAdapter.test.ts +42 -0
  213. package/src/telemetry/LoggerTelemetryAdapter.ts +114 -0
  214. package/src/telemetry/OpenTelemetryAdapter.test.ts +157 -0
  215. package/src/telemetry/OpenTelemetryAdapter.ts +348 -0
  216. package/src/telemetry/OpenTelemetryProvider.test.ts +113 -0
  217. package/src/telemetry/OpenTelemetryProvider.ts +325 -0
  218. package/src/telemetry/TelemetryService.test.ts +134 -0
  219. package/src/telemetry/TelemetryService.ts +141 -0
  220. package/src/telemetry/core-events.ts +400 -0
  221. package/src/telemetry/distinct-id.test.ts +57 -0
  222. package/src/telemetry/distinct-id.ts +58 -0
  223. package/src/telemetry/index.ts +20 -0
  224. package/src/tools/constants.ts +35 -0
  225. package/src/tools/definitions.test.ts +704 -0
  226. package/src/tools/definitions.ts +709 -0
  227. package/src/tools/executors/apply-patch-parser.ts +520 -0
  228. package/src/tools/executors/apply-patch.ts +359 -0
  229. package/src/tools/executors/bash.test.ts +87 -0
  230. package/src/tools/executors/bash.ts +207 -0
  231. package/src/tools/executors/editor.test.ts +35 -0
  232. package/src/tools/executors/editor.ts +219 -0
  233. package/src/tools/executors/file-read.test.ts +49 -0
  234. package/src/tools/executors/file-read.ts +110 -0
  235. package/src/tools/executors/index.ts +87 -0
  236. package/src/tools/executors/search.ts +278 -0
  237. package/src/tools/executors/web-fetch.ts +259 -0
  238. package/src/tools/helpers.ts +130 -0
  239. package/src/tools/index.ts +169 -0
  240. package/src/tools/model-tool-routing.test.ts +86 -0
  241. package/src/tools/model-tool-routing.ts +132 -0
  242. package/src/tools/presets.test.ts +62 -0
  243. package/src/tools/presets.ts +168 -0
  244. package/src/tools/schemas.ts +327 -0
  245. package/src/tools/types.ts +329 -0
  246. package/src/types/common.ts +26 -0
  247. package/src/types/config.ts +86 -0
  248. package/src/types/events.ts +74 -0
  249. package/src/types/index.ts +24 -0
  250. package/src/types/provider-settings.ts +43 -0
  251. package/src/types/sessions.ts +16 -0
  252. package/src/types/storage.ts +64 -0
  253. package/src/types/workspace.ts +7 -0
  254. package/src/types.ts +132 -0
  255. package/src/version.ts +3 -0
  256. package/dist/index.node.d.ts +0 -47
  257. package/dist/index.node.d.ts.map +0 -1
  258. package/dist/index.node.js +0 -948
  259. package/dist/telemetry/opentelemetry.d.ts.map +0 -1
  260. package/dist/telemetry/opentelemetry.js +0 -27
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Search Executor
3
+ *
4
+ * Built-in implementation for searching the codebase using regex.
5
+ */
6
+
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
9
+ import type { ToolContext } from "@clinebot/agents";
10
+ import { getFileIndex } from "../../input";
11
+ import type { SearchExecutor } from "../types";
12
+
13
+ /**
14
+ * Options for the search executor
15
+ */
16
+ export interface SearchExecutorOptions {
17
+ /**
18
+ * File extensions to include in search (without dot)
19
+ * @default common code extensions
20
+ */
21
+ includeExtensions?: string[];
22
+
23
+ /**
24
+ * Directories to exclude from search
25
+ * @default ["node_modules", ".git", "dist", "build", ".next", "coverage"]
26
+ */
27
+ excludeDirs?: string[];
28
+
29
+ /**
30
+ * Maximum number of results to return
31
+ * @default 100
32
+ */
33
+ maxResults?: number;
34
+
35
+ /**
36
+ * Number of context lines before and after match
37
+ * @default 2
38
+ */
39
+ contextLines?: number;
40
+
41
+ /**
42
+ * Maximum depth to traverse
43
+ * @default 20
44
+ */
45
+ maxDepth?: number;
46
+ }
47
+
48
+ const DEFAULT_INCLUDE_EXTENSIONS = [
49
+ "ts",
50
+ "tsx",
51
+ "js",
52
+ "jsx",
53
+ "mjs",
54
+ "cjs",
55
+ "json",
56
+ "md",
57
+ "mdx",
58
+ "txt",
59
+ "yaml",
60
+ "yml",
61
+ "toml",
62
+ "py",
63
+ "rb",
64
+ "go",
65
+ "rs",
66
+ "java",
67
+ "kt",
68
+ "swift",
69
+ "c",
70
+ "cpp",
71
+ "h",
72
+ "hpp",
73
+ "css",
74
+ "scss",
75
+ "less",
76
+ "html",
77
+ "vue",
78
+ "svelte",
79
+ "sql",
80
+ "sh",
81
+ "bash",
82
+ "zsh",
83
+ "fish",
84
+ "ps1",
85
+ "env",
86
+ "gitignore",
87
+ "dockerignore",
88
+ "editorconfig",
89
+ ];
90
+
91
+ const DEFAULT_EXCLUDE_DIRS = [
92
+ "node_modules",
93
+ ".git",
94
+ "dist",
95
+ "build",
96
+ ".next",
97
+ "coverage",
98
+ "__pycache__",
99
+ ".venv",
100
+ "venv",
101
+ ".cache",
102
+ ".turbo",
103
+ ".output",
104
+ "out",
105
+ "target",
106
+ "bin",
107
+ "obj",
108
+ ];
109
+
110
+ /**
111
+ * Search result for a single file match
112
+ */
113
+ interface SearchMatch {
114
+ file: string;
115
+ line: number;
116
+ column: number;
117
+ match: string;
118
+ context: string[];
119
+ }
120
+
121
+ function shouldIncludeFile(
122
+ relativePath: string,
123
+ excludeDirs: Set<string>,
124
+ includeExtensions: Set<string>,
125
+ maxDepth: number,
126
+ ): boolean {
127
+ const segments = relativePath.split("/");
128
+ const fileName = segments[segments.length - 1] ?? "";
129
+ const directoryDepth = segments.length - 1;
130
+
131
+ if (directoryDepth > maxDepth) {
132
+ return false;
133
+ }
134
+
135
+ for (let i = 0; i < segments.length - 1; i++) {
136
+ if (excludeDirs.has(segments[i] ?? "")) {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ const ext = path.posix.extname(fileName).slice(1).toLowerCase();
142
+ return includeExtensions.has(ext) || (!ext && !fileName.startsWith("."));
143
+ }
144
+
145
+ /**
146
+ * Create a search executor using regex pattern matching
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const search = createSearchExecutor({
151
+ * maxResults: 50,
152
+ * contextLines: 3,
153
+ * })
154
+ *
155
+ * const results = await search("function\\s+handleClick", "/path/to/project", context)
156
+ * ```
157
+ */
158
+ export function createSearchExecutor(
159
+ options: SearchExecutorOptions = {},
160
+ ): SearchExecutor {
161
+ const {
162
+ includeExtensions = DEFAULT_INCLUDE_EXTENSIONS,
163
+ excludeDirs = DEFAULT_EXCLUDE_DIRS,
164
+ maxResults = 100,
165
+ contextLines = 2,
166
+ maxDepth = 20,
167
+ } = options;
168
+ const excludeDirsSet = new Set(excludeDirs);
169
+ const includeExtensionsSet = new Set(
170
+ includeExtensions.map((extension) => extension.toLowerCase()),
171
+ );
172
+
173
+ return async (
174
+ query: string,
175
+ cwd: string,
176
+ _context: ToolContext,
177
+ ): Promise<string> => {
178
+ // Compile regex
179
+ let regex: RegExp;
180
+ try {
181
+ regex = new RegExp(query, "gim");
182
+ } catch (error) {
183
+ throw new Error(
184
+ `Invalid regex pattern: ${query}. ${error instanceof Error ? error.message : ""}`,
185
+ );
186
+ }
187
+
188
+ const matches: SearchMatch[] = [];
189
+ let totalFilesSearched = 0;
190
+
191
+ const fileList = await getFileIndex(cwd);
192
+
193
+ // Search files from the fast index.
194
+ for (const relativePath of fileList) {
195
+ if (
196
+ !shouldIncludeFile(
197
+ relativePath,
198
+ excludeDirsSet,
199
+ includeExtensionsSet,
200
+ maxDepth,
201
+ )
202
+ ) {
203
+ continue;
204
+ }
205
+
206
+ if (matches.length >= maxResults) break;
207
+
208
+ totalFilesSearched++;
209
+ const filePath = path.join(cwd, relativePath);
210
+
211
+ try {
212
+ const content = await fs.readFile(filePath, "utf-8");
213
+ const lines = content.split("\n");
214
+
215
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
216
+ const line = lines[lineIdx];
217
+ regex.lastIndex = 0; // Reset regex state
218
+
219
+ let match: RegExpExecArray | null;
220
+ while ((match = regex.exec(line)) !== null) {
221
+ if (matches.length >= maxResults) break;
222
+
223
+ // Get context lines
224
+ const contextStart = Math.max(0, lineIdx - contextLines);
225
+ const contextEnd = Math.min(
226
+ lines.length - 1,
227
+ lineIdx + contextLines,
228
+ );
229
+ const contextLinesArr: string[] = [];
230
+
231
+ for (let i = contextStart; i <= contextEnd; i++) {
232
+ const prefix = i === lineIdx ? ">" : " ";
233
+ contextLinesArr.push(`${prefix} ${i + 1}: ${lines[i]}`);
234
+ }
235
+
236
+ matches.push({
237
+ file: relativePath,
238
+ line: lineIdx + 1,
239
+ column: match.index + 1,
240
+ match: match[0],
241
+ context: contextLinesArr,
242
+ });
243
+
244
+ // Prevent infinite loop on zero-length matches
245
+ if (match.index === regex.lastIndex) {
246
+ regex.lastIndex++;
247
+ }
248
+ }
249
+ }
250
+ } catch {}
251
+ }
252
+
253
+ // Format results
254
+ if (matches.length === 0) {
255
+ return `No results found for pattern: ${query}\nSearched ${totalFilesSearched} files.`;
256
+ }
257
+
258
+ const resultLines: string[] = [
259
+ `Found ${matches.length} result${matches.length === 1 ? "" : "s"} for pattern: ${query}`,
260
+ `Searched ${totalFilesSearched} files.`,
261
+ "",
262
+ ];
263
+
264
+ for (const match of matches) {
265
+ resultLines.push(`${match.file}:${match.line}:${match.column}`);
266
+ resultLines.push(...match.context);
267
+ resultLines.push("");
268
+ }
269
+
270
+ if (matches.length >= maxResults) {
271
+ resultLines.push(
272
+ `(Showing first ${maxResults} results. Refine your search for more specific results.)`,
273
+ );
274
+ }
275
+
276
+ return resultLines.join("\n");
277
+ };
278
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Web Fetch Executor
3
+ *
4
+ * Built-in implementation for fetching web content using native fetch.
5
+ */
6
+
7
+ import type { ToolContext } from "@clinebot/agents";
8
+ import type { WebFetchExecutor } from "../types";
9
+
10
+ /**
11
+ * Options for the web fetch executor
12
+ */
13
+ export interface WebFetchExecutorOptions {
14
+ /**
15
+ * Timeout for fetch requests in milliseconds
16
+ * @default 30000 (30 seconds)
17
+ */
18
+ timeoutMs?: number;
19
+
20
+ /**
21
+ * Maximum response size in bytes
22
+ * @default 5_000_000 (5MB)
23
+ */
24
+ maxResponseBytes?: number;
25
+
26
+ /**
27
+ * User agent string
28
+ * @default "Mozilla/5.0 (compatible; AgentBot/1.0)"
29
+ */
30
+ userAgent?: string;
31
+
32
+ /**
33
+ * Additional headers
34
+ */
35
+ headers?: Record<string, string>;
36
+
37
+ /**
38
+ * Whether to follow redirects
39
+ * @default true
40
+ */
41
+ followRedirects?: boolean;
42
+
43
+ /**
44
+ * Maximum number of redirects to follow
45
+ * @default 5
46
+ */
47
+ maxRedirects?: number;
48
+ }
49
+
50
+ /**
51
+ * Extract text content from HTML
52
+ * Simple implementation - strips tags and normalizes whitespace
53
+ */
54
+ function htmlToText(html: string): string {
55
+ return (
56
+ html
57
+ // Remove script and style elements
58
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
59
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
60
+ // Remove HTML comments
61
+ .replace(/<!--[\s\S]*?-->/g, "")
62
+ // Replace block elements with newlines
63
+ .replace(/<(p|div|br|hr|h[1-6]|li|tr)[^>]*>/gi, "\n")
64
+ // Remove all remaining tags
65
+ .replace(/<[^>]+>/g, " ")
66
+ // Decode HTML entities
67
+ .replace(/&nbsp;/g, " ")
68
+ .replace(/&amp;/g, "&")
69
+ .replace(/&lt;/g, "<")
70
+ .replace(/&gt;/g, ">")
71
+ .replace(/&quot;/g, '"')
72
+ .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n, 10)))
73
+ // Normalize whitespace
74
+ .replace(/\s+/g, " ")
75
+ .replace(/\n\s+/g, "\n")
76
+ .replace(/\n{3,}/g, "\n\n")
77
+ .trim()
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Create a web fetch executor using native fetch
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const webFetch = createWebFetchExecutor({
87
+ * timeoutMs: 15000,
88
+ * maxResponseBytes: 2_000_000,
89
+ * })
90
+ *
91
+ * const content = await webFetch(
92
+ * "https://docs.example.com/api",
93
+ * "Extract the authentication section",
94
+ * context
95
+ * )
96
+ * ```
97
+ */
98
+ export function createWebFetchExecutor(
99
+ options: WebFetchExecutorOptions = {},
100
+ ): WebFetchExecutor {
101
+ const {
102
+ timeoutMs = 30000,
103
+ maxResponseBytes = 5_000_000,
104
+ userAgent = "Mozilla/5.0 (compatible; AgentBot/1.0)",
105
+ headers = {},
106
+ followRedirects = true,
107
+ // maxRedirects is available in options but native fetch handles it automatically
108
+ } = options;
109
+
110
+ return async (
111
+ url: string,
112
+ prompt: string,
113
+ context: ToolContext,
114
+ ): Promise<string> => {
115
+ // Validate URL
116
+ let parsedUrl: URL;
117
+ try {
118
+ parsedUrl = new URL(url);
119
+ } catch {
120
+ throw new Error(`Invalid URL: ${url}`);
121
+ }
122
+
123
+ // Only allow http and https
124
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
125
+ throw new Error(
126
+ `Invalid protocol: ${parsedUrl.protocol}. Only http and https are supported.`,
127
+ );
128
+ }
129
+
130
+ // Create abort controller for timeout
131
+ const controller = new AbortController();
132
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
133
+ let contextAbortHandler: (() => void) | undefined;
134
+
135
+ // Combine with context abort signal
136
+ if (context.abortSignal) {
137
+ contextAbortHandler = () => controller.abort();
138
+ context.abortSignal.addEventListener("abort", contextAbortHandler);
139
+ }
140
+
141
+ try {
142
+ const response = await fetch(url, {
143
+ method: "GET",
144
+ headers: {
145
+ "User-Agent": userAgent,
146
+ Accept:
147
+ "text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7",
148
+ "Accept-Language": "en-US,en;q=0.9",
149
+ ...headers,
150
+ },
151
+ redirect: followRedirects ? "follow" : "manual",
152
+ signal: controller.signal,
153
+ });
154
+
155
+ clearTimeout(timeout);
156
+
157
+ // Check for redirect limit (if we're checking manually)
158
+ if (!followRedirects && response.status >= 300 && response.status < 400) {
159
+ const location = response.headers.get("location");
160
+ return `Redirect to: ${location}`;
161
+ }
162
+
163
+ // Check response status
164
+ if (!response.ok) {
165
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
166
+ }
167
+
168
+ // Get content type
169
+ const contentType = response.headers.get("content-type") || "";
170
+
171
+ // Read response body with size limit
172
+ const reader = response.body?.getReader();
173
+ if (!reader) {
174
+ throw new Error("Failed to read response body");
175
+ }
176
+
177
+ const chunks: Uint8Array[] = [];
178
+ let totalSize = 0;
179
+
180
+ while (true) {
181
+ const { done, value } = await reader.read();
182
+ if (done) break;
183
+
184
+ totalSize += value.length;
185
+ if (totalSize > maxResponseBytes) {
186
+ reader.cancel();
187
+ throw new Error(
188
+ `Response too large: exceeded ${maxResponseBytes} bytes`,
189
+ );
190
+ }
191
+
192
+ chunks.push(value);
193
+ }
194
+
195
+ // Combine chunks
196
+ const buffer = new Uint8Array(totalSize);
197
+ let offset = 0;
198
+ for (const chunk of chunks) {
199
+ buffer.set(chunk, offset);
200
+ offset += chunk.length;
201
+ }
202
+
203
+ // Decode as text
204
+ const text = new TextDecoder("utf-8").decode(buffer);
205
+
206
+ // Process content based on type
207
+ let content: string;
208
+ if (
209
+ contentType.includes("text/html") ||
210
+ contentType.includes("application/xhtml")
211
+ ) {
212
+ content = htmlToText(text);
213
+ } else if (contentType.includes("application/json")) {
214
+ try {
215
+ const json = JSON.parse(text);
216
+ content = JSON.stringify(json, null, 2);
217
+ } catch {
218
+ content = text;
219
+ }
220
+ } else {
221
+ content = text;
222
+ }
223
+
224
+ // Format output with metadata
225
+ const outputLines = [
226
+ `URL: ${url}`,
227
+ `Content-Type: ${contentType}`,
228
+ `Size: ${totalSize} bytes`,
229
+ ``,
230
+ `--- Content ---`,
231
+ content.slice(0, 50000), // Limit content size for output
232
+ ];
233
+
234
+ if (content.length > 50000) {
235
+ outputLines.push(
236
+ `\n[Content truncated: showing first 50000 of ${content.length} characters]`,
237
+ );
238
+ }
239
+
240
+ outputLines.push(``, `--- Analysis Request ---`, `Prompt: ${prompt}`);
241
+
242
+ return outputLines.join("\n");
243
+ } catch (error) {
244
+ clearTimeout(timeout);
245
+
246
+ if (error instanceof Error) {
247
+ if (error.name === "AbortError") {
248
+ throw new Error(`Request timed out after ${timeoutMs}ms`);
249
+ }
250
+ throw error;
251
+ }
252
+ throw new Error(`Fetch failed: ${String(error)}`);
253
+ } finally {
254
+ if (context.abortSignal && contextAbortHandler) {
255
+ context.abortSignal.removeEventListener("abort", contextAbortHandler);
256
+ }
257
+ }
258
+ };
259
+ }
@@ -0,0 +1,130 @@
1
+ import { validateWithZod } from "@clinebot/shared";
2
+ import {
3
+ type EditFileInput,
4
+ INPUT_ARG_CHAR_LIMIT,
5
+ type ReadFileRequest,
6
+ ReadFilesInputUnionSchema,
7
+ type StructuredCommandInput,
8
+ StructuredCommandsInputUnionSchema,
9
+ } from "./schemas";
10
+
11
+ /**
12
+ * Format an error into a string message
13
+ */
14
+ export function formatError(error: unknown): string {
15
+ if (error instanceof Error) {
16
+ return error.message;
17
+ }
18
+ return String(error);
19
+ }
20
+
21
+ export function getEditorSizeError(input: EditFileInput): string | null {
22
+ if (
23
+ typeof input.old_text === "string" &&
24
+ input.old_text.length > INPUT_ARG_CHAR_LIMIT
25
+ ) {
26
+ return `Editor input too large: old_text was ${input.old_text.length} characters, exceeding the recommended limit of ${INPUT_ARG_CHAR_LIMIT}. Split the edit into smaller tool calls so later tool calls are less likely to be truncated or time out.`;
27
+ }
28
+
29
+ if (input.new_text.length > INPUT_ARG_CHAR_LIMIT) {
30
+ return `Editor input too large: new_text was ${input.new_text.length} characters, exceeding the recommended limit of ${INPUT_ARG_CHAR_LIMIT}. Split the edit into smaller tool calls so later tool calls are less likely to be truncated or time out.`;
31
+ }
32
+
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Create a timeout-wrapped promise
38
+ */
39
+ export function withTimeout<T>(
40
+ promise: Promise<T>,
41
+ ms: number,
42
+ message: string,
43
+ ): Promise<T> {
44
+ return Promise.race([
45
+ promise,
46
+ new Promise<never>((_, reject) => {
47
+ setTimeout(() => reject(new Error(message)), ms);
48
+ }),
49
+ ]);
50
+ }
51
+
52
+ export function normalizeReadFileRequests(input: unknown): ReadFileRequest[] {
53
+ const validate = validateWithZod(ReadFilesInputUnionSchema, input);
54
+
55
+ if (typeof validate === "string") {
56
+ return [{ path: validate }];
57
+ }
58
+
59
+ if (Array.isArray(validate)) {
60
+ return validate.map((value) =>
61
+ typeof value === "string" ? { path: value } : value,
62
+ );
63
+ }
64
+
65
+ if ("files" in validate) {
66
+ const files = Array.isArray(validate.files)
67
+ ? validate.files
68
+ : [validate.files];
69
+ return files;
70
+ }
71
+
72
+ if ("file_paths" in validate) {
73
+ const filePaths = Array.isArray(validate.file_paths)
74
+ ? validate.file_paths
75
+ : [validate.file_paths];
76
+ return filePaths.map((filePath) => ({ path: filePath }));
77
+ }
78
+
79
+ return [validate];
80
+ }
81
+
82
+ export function formatReadFileQuery(request: ReadFileRequest): string {
83
+ const { path, start_line, end_line } = request;
84
+ if (start_line == null && end_line == null) {
85
+ return path;
86
+ }
87
+ const start = start_line ?? 1;
88
+ const end = end_line ?? "EOF";
89
+ return `${path}:${start}-${end}`;
90
+ }
91
+
92
+ export function normalizeRunCommandsInput(
93
+ input: unknown,
94
+ ): Array<string | StructuredCommandInput> {
95
+ const validate = validateWithZod(StructuredCommandsInputUnionSchema, input);
96
+
97
+ if (typeof validate === "string") {
98
+ return [validate];
99
+ }
100
+
101
+ if (Array.isArray(validate)) {
102
+ return validate;
103
+ }
104
+
105
+ if ("commands" in validate) {
106
+ return Array.isArray(validate.commands)
107
+ ? validate.commands
108
+ : [validate.commands];
109
+ }
110
+
111
+ return [validate];
112
+ }
113
+
114
+ export function formatRunCommandQuery(
115
+ command: string | StructuredCommandInput,
116
+ ): string {
117
+ if (typeof command === "string") {
118
+ return command;
119
+ }
120
+
121
+ const args = command.args ?? [];
122
+ if (args.length === 0) {
123
+ return command.command;
124
+ }
125
+
126
+ const renderedArgs = args.map((arg) =>
127
+ /[\s"]/u.test(arg) ? JSON.stringify(arg) : arg,
128
+ );
129
+ return `${command.command} ${renderedArgs.join(" ")}`;
130
+ }