c0de-agent 0.0.0 → 1.1.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.
Files changed (336) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/cli/commands/acp.d.ts +19 -0
  4. package/dist/cli/commands/acp.js +50 -0
  5. package/dist/cli/commands/chat.d.ts +13 -0
  6. package/dist/cli/commands/chat.js +26 -0
  7. package/dist/cli/commands/config.d.ts +17 -0
  8. package/dist/cli/commands/config.js +77 -0
  9. package/dist/cli/commands/init.d.ts +9 -0
  10. package/dist/cli/commands/init.js +14 -0
  11. package/dist/cli/commands/plugin.d.ts +11 -0
  12. package/dist/cli/commands/plugin.js +31 -0
  13. package/dist/cli/commands/serve.d.ts +18 -0
  14. package/dist/cli/commands/serve.js +35 -0
  15. package/dist/cli/commands/update.d.ts +24 -0
  16. package/dist/cli/commands/update.js +40 -0
  17. package/dist/cli/deps.d.ts +30 -0
  18. package/dist/cli/deps.js +70 -0
  19. package/dist/cli/index.d.ts +8 -0
  20. package/dist/cli/index.js +155 -0
  21. package/dist/cli/modes/acp.d.ts +18 -0
  22. package/dist/cli/modes/acp.js +57 -0
  23. package/dist/cli/modes/print.d.ts +16 -0
  24. package/dist/cli/modes/print.js +30 -0
  25. package/dist/cli/parser.d.ts +19 -0
  26. package/dist/cli/parser.js +28 -0
  27. package/dist/cli/utils/output.d.ts +11 -0
  28. package/dist/cli/utils/output.js +23 -0
  29. package/dist/cli/utils/prompt.d.ts +12 -0
  30. package/dist/cli/utils/prompt.js +18 -0
  31. package/dist/core/agent.d.ts +12 -0
  32. package/dist/core/agent.js +100 -0
  33. package/dist/core/agents/builtin.d.ts +6 -0
  34. package/dist/core/agents/builtin.js +76 -0
  35. package/dist/core/agents/discovery.d.ts +6 -0
  36. package/dist/core/agents/discovery.js +96 -0
  37. package/dist/core/agents/index.d.ts +6 -0
  38. package/dist/core/agents/index.js +4 -0
  39. package/dist/core/agents/parallel.d.ts +13 -0
  40. package/dist/core/agents/parallel.js +37 -0
  41. package/dist/core/agents/registry.d.ts +4 -0
  42. package/dist/core/agents/registry.js +23 -0
  43. package/dist/core/agents/types.d.ts +37 -0
  44. package/dist/core/agents/types.js +1 -0
  45. package/dist/core/compact.d.ts +11 -0
  46. package/dist/core/compact.js +31 -0
  47. package/dist/core/config.d.ts +7 -0
  48. package/dist/core/config.js +85 -0
  49. package/dist/core/context.d.ts +23 -0
  50. package/dist/core/context.js +93 -0
  51. package/dist/core/index.d.ts +13 -0
  52. package/dist/core/index.js +11 -0
  53. package/dist/core/loop.d.ts +35 -0
  54. package/dist/core/loop.js +915 -0
  55. package/dist/core/metrics.d.ts +27 -0
  56. package/dist/core/metrics.js +94 -0
  57. package/dist/core/prompt-registry.d.ts +21 -0
  58. package/dist/core/prompt-registry.js +200 -0
  59. package/dist/core/prompt.d.ts +11 -0
  60. package/dist/core/prompt.js +13 -0
  61. package/dist/core/secret.d.ts +12 -0
  62. package/dist/core/secret.js +51 -0
  63. package/dist/core/slash.d.ts +15 -0
  64. package/dist/core/slash.js +113 -0
  65. package/dist/core/steering.d.ts +8 -0
  66. package/dist/core/steering.js +15 -0
  67. package/dist/core/title.d.ts +42 -0
  68. package/dist/core/title.js +116 -0
  69. package/dist/core/tool-exec.d.ts +20 -0
  70. package/dist/core/tool-exec.js +64 -0
  71. package/dist/core/types.d.ts +97 -0
  72. package/dist/core/types.js +1 -0
  73. package/dist/core/worktree.d.ts +24 -0
  74. package/dist/core/worktree.js +70 -0
  75. package/dist/dap/index.d.ts +6 -0
  76. package/dist/dap/index.js +4 -0
  77. package/dist/dap/protocol.d.ts +40 -0
  78. package/dist/dap/protocol.js +132 -0
  79. package/dist/dap/session.d.ts +23 -0
  80. package/dist/dap/session.js +130 -0
  81. package/dist/dap/transport.d.ts +8 -0
  82. package/dist/dap/transport.js +30 -0
  83. package/dist/dap/types.d.ts +50 -0
  84. package/dist/dap/types.js +2 -0
  85. package/dist/db/client.d.ts +22 -0
  86. package/dist/db/client.js +39 -0
  87. package/dist/db/index.d.ts +6 -0
  88. package/dist/db/index.js +4 -0
  89. package/dist/db/migrate.d.ts +9 -0
  90. package/dist/db/migrate.js +15 -0
  91. package/dist/db/schema.d.ts +965 -0
  92. package/dist/db/schema.js +112 -0
  93. package/dist/db/types.d.ts +11 -0
  94. package/dist/db/types.js +1 -0
  95. package/dist/llm/index.d.ts +13 -0
  96. package/dist/llm/index.js +8 -0
  97. package/dist/llm/protocols/openai-compat.d.ts +139 -0
  98. package/dist/llm/protocols/openai-compat.js +214 -0
  99. package/dist/llm/protocols/utils/index.d.ts +2 -0
  100. package/dist/llm/protocols/utils/index.js +2 -0
  101. package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
  102. package/dist/llm/protocols/utils/lifecycle.js +46 -0
  103. package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
  104. package/dist/llm/protocols/utils/tool-stream.js +94 -0
  105. package/dist/llm/provider-error.d.ts +5 -0
  106. package/dist/llm/provider-error.js +33 -0
  107. package/dist/llm/provider.d.ts +32 -0
  108. package/dist/llm/provider.js +195 -0
  109. package/dist/llm/registry.d.ts +44 -0
  110. package/dist/llm/registry.js +107 -0
  111. package/dist/llm/retry.d.ts +37 -0
  112. package/dist/llm/retry.js +82 -0
  113. package/dist/llm/routing.d.ts +36 -0
  114. package/dist/llm/routing.js +53 -0
  115. package/dist/llm/schema/errors.d.ts +88 -0
  116. package/dist/llm/schema/errors.js +33 -0
  117. package/dist/llm/schema/events.d.ts +135 -0
  118. package/dist/llm/schema/events.js +35 -0
  119. package/dist/llm/schema/ids.d.ts +22 -0
  120. package/dist/llm/schema/ids.js +3 -0
  121. package/dist/llm/schema/index.d.ts +5 -0
  122. package/dist/llm/schema/index.js +5 -0
  123. package/dist/llm/schema/messages.d.ts +97 -0
  124. package/dist/llm/schema/messages.js +18 -0
  125. package/dist/llm/schema/options.d.ts +54 -0
  126. package/dist/llm/schema/options.js +54 -0
  127. package/dist/llm/token.d.ts +2 -0
  128. package/dist/llm/token.js +13 -0
  129. package/dist/llm/transport.d.ts +31 -0
  130. package/dist/llm/transport.js +165 -0
  131. package/dist/mcp/index.d.ts +1 -0
  132. package/dist/mcp/index.js +3 -0
  133. package/dist/plugins/builtin.d.ts +6 -0
  134. package/dist/plugins/builtin.js +54 -0
  135. package/dist/plugins/hooks.d.ts +3 -0
  136. package/dist/plugins/hooks.js +79 -0
  137. package/dist/plugins/index.d.ts +9 -0
  138. package/dist/plugins/index.js +8 -0
  139. package/dist/plugins/init.d.ts +23 -0
  140. package/dist/plugins/init.js +30 -0
  141. package/dist/plugins/lifecycle.d.ts +6 -0
  142. package/dist/plugins/lifecycle.js +74 -0
  143. package/dist/plugins/loader.d.ts +16 -0
  144. package/dist/plugins/loader.js +69 -0
  145. package/dist/plugins/logger.d.ts +4 -0
  146. package/dist/plugins/logger.js +31 -0
  147. package/dist/plugins/registry.d.ts +7 -0
  148. package/dist/plugins/registry.js +24 -0
  149. package/dist/plugins/types.d.ts +113 -0
  150. package/dist/plugins/types.js +1 -0
  151. package/dist/project/detect.d.ts +4 -0
  152. package/dist/project/detect.js +131 -0
  153. package/dist/project/index.d.ts +4 -0
  154. package/dist/project/index.js +2 -0
  155. package/dist/project/project.d.ts +23 -0
  156. package/dist/project/project.js +87 -0
  157. package/dist/project/resolve.d.ts +8 -0
  158. package/dist/project/resolve.js +81 -0
  159. package/dist/server/agent-manager.d.ts +34 -0
  160. package/dist/server/agent-manager.js +62 -0
  161. package/dist/server/app.d.ts +5 -0
  162. package/dist/server/app.js +78 -0
  163. package/dist/server/context.d.ts +20 -0
  164. package/dist/server/context.js +49 -0
  165. package/dist/server/dev.d.ts +14 -0
  166. package/dist/server/dev.js +148 -0
  167. package/dist/server/index.d.ts +11 -0
  168. package/dist/server/index.js +7 -0
  169. package/dist/server/middleware/auth.d.ts +15 -0
  170. package/dist/server/middleware/auth.js +29 -0
  171. package/dist/server/middleware/cors.d.ts +16 -0
  172. package/dist/server/middleware/cors.js +44 -0
  173. package/dist/server/middleware/error.d.ts +15 -0
  174. package/dist/server/middleware/error.js +11 -0
  175. package/dist/server/permission/interactive.d.ts +24 -0
  176. package/dist/server/permission/interactive.js +56 -0
  177. package/dist/server/permission/store.d.ts +32 -0
  178. package/dist/server/permission/store.js +56 -0
  179. package/dist/server/routes/agent.d.ts +4 -0
  180. package/dist/server/routes/agent.js +19 -0
  181. package/dist/server/routes/catalog.d.ts +42 -0
  182. package/dist/server/routes/catalog.js +194 -0
  183. package/dist/server/routes/chat.d.ts +8 -0
  184. package/dist/server/routes/chat.js +318 -0
  185. package/dist/server/routes/commands.d.ts +5 -0
  186. package/dist/server/routes/commands.js +17 -0
  187. package/dist/server/routes/config.d.ts +4 -0
  188. package/dist/server/routes/config.js +27 -0
  189. package/dist/server/routes/files.d.ts +4 -0
  190. package/dist/server/routes/files.js +183 -0
  191. package/dist/server/routes/filesystem.d.ts +29 -0
  192. package/dist/server/routes/filesystem.js +108 -0
  193. package/dist/server/routes/health.d.ts +3 -0
  194. package/dist/server/routes/health.js +13 -0
  195. package/dist/server/routes/permissions.d.ts +5 -0
  196. package/dist/server/routes/permissions.js +23 -0
  197. package/dist/server/routes/project.d.ts +4 -0
  198. package/dist/server/routes/project.js +51 -0
  199. package/dist/server/routes/provider.d.ts +16 -0
  200. package/dist/server/routes/provider.js +66 -0
  201. package/dist/server/routes/session.d.ts +4 -0
  202. package/dist/server/routes/session.js +204 -0
  203. package/dist/server/routes/terminal.d.ts +5 -0
  204. package/dist/server/routes/terminal.js +66 -0
  205. package/dist/server/routes/tool.d.ts +4 -0
  206. package/dist/server/routes/tool.js +28 -0
  207. package/dist/server/routes/update.d.ts +14 -0
  208. package/dist/server/routes/update.js +49 -0
  209. package/dist/server/server.d.ts +56 -0
  210. package/dist/server/server.js +269 -0
  211. package/dist/server/terminal/pty-manager.d.ts +53 -0
  212. package/dist/server/terminal/pty-manager.js +160 -0
  213. package/dist/server/types.d.ts +76 -0
  214. package/dist/server/types.js +2 -0
  215. package/dist/server/util/safe-path.d.ts +7 -0
  216. package/dist/server/util/safe-path.js +7 -0
  217. package/dist/session/archive.d.ts +15 -0
  218. package/dist/session/archive.js +129 -0
  219. package/dist/session/branch.d.ts +9 -0
  220. package/dist/session/branch.js +63 -0
  221. package/dist/session/compaction.d.ts +33 -0
  222. package/dist/session/compaction.js +261 -0
  223. package/dist/session/context.d.ts +16 -0
  224. package/dist/session/context.js +173 -0
  225. package/dist/session/index.d.ts +10 -0
  226. package/dist/session/index.js +10 -0
  227. package/dist/session/message.d.ts +22 -0
  228. package/dist/session/message.js +145 -0
  229. package/dist/session/session.d.ts +37 -0
  230. package/dist/session/session.js +144 -0
  231. package/dist/session/shake.d.ts +66 -0
  232. package/dist/session/shake.js +304 -0
  233. package/dist/session/snapshot.d.ts +11 -0
  234. package/dist/session/snapshot.js +60 -0
  235. package/dist/session/squash.d.ts +9 -0
  236. package/dist/session/squash.js +88 -0
  237. package/dist/session/token.d.ts +10 -0
  238. package/dist/session/token.js +33 -0
  239. package/dist/session/types.d.ts +122 -0
  240. package/dist/session/types.js +1 -0
  241. package/dist/shared/index.d.ts +2 -0
  242. package/dist/shared/index.js +1 -0
  243. package/dist/shared/types/agent.d.ts +229 -0
  244. package/dist/shared/types/agent.js +1 -0
  245. package/dist/shared/types/base.d.ts +32 -0
  246. package/dist/shared/types/base.js +1 -0
  247. package/dist/shared/types/config.d.ts +114 -0
  248. package/dist/shared/types/config.js +1 -0
  249. package/dist/shared/types/index.d.ts +6 -0
  250. package/dist/shared/types/index.js +1 -0
  251. package/dist/shared/types/llm.d.ts +123 -0
  252. package/dist/shared/types/llm.js +1 -0
  253. package/dist/shared/types/message.d.ts +71 -0
  254. package/dist/shared/types/message.js +1 -0
  255. package/dist/shared/types/tool.d.ts +137 -0
  256. package/dist/shared/types/tool.js +1 -0
  257. package/dist/shared/utils/path.d.ts +15 -0
  258. package/dist/shared/utils/path.js +23 -0
  259. package/dist/shared/utils.d.ts +5 -0
  260. package/dist/shared/utils.js +10 -0
  261. package/dist/tools/builtin/bash.d.ts +12 -0
  262. package/dist/tools/builtin/bash.js +121 -0
  263. package/dist/tools/builtin/dap.d.ts +23 -0
  264. package/dist/tools/builtin/dap.js +231 -0
  265. package/dist/tools/builtin/edit.d.ts +12 -0
  266. package/dist/tools/builtin/edit.js +151 -0
  267. package/dist/tools/builtin/glob.d.ts +11 -0
  268. package/dist/tools/builtin/glob.js +120 -0
  269. package/dist/tools/builtin/grep.d.ts +6 -0
  270. package/dist/tools/builtin/grep.js +141 -0
  271. package/dist/tools/builtin/read.d.ts +6 -0
  272. package/dist/tools/builtin/read.js +73 -0
  273. package/dist/tools/builtin/resolvers.d.ts +12 -0
  274. package/dist/tools/builtin/resolvers.js +79 -0
  275. package/dist/tools/builtin/task.d.ts +9 -0
  276. package/dist/tools/builtin/task.js +100 -0
  277. package/dist/tools/builtin/write.d.ts +6 -0
  278. package/dist/tools/builtin/write.js +36 -0
  279. package/dist/tools/builtin/yield.d.ts +9 -0
  280. package/dist/tools/builtin/yield.js +40 -0
  281. package/dist/tools/executor.d.ts +9 -0
  282. package/dist/tools/executor.js +54 -0
  283. package/dist/tools/hashline/index.d.ts +2 -0
  284. package/dist/tools/hashline/index.js +3 -0
  285. package/dist/tools/hashline/patch.d.ts +48 -0
  286. package/dist/tools/hashline/patch.js +201 -0
  287. package/dist/tools/index.d.ts +30 -0
  288. package/dist/tools/index.js +55 -0
  289. package/dist/tools/permission.d.ts +13 -0
  290. package/dist/tools/permission.js +39 -0
  291. package/dist/tools/registry.d.ts +15 -0
  292. package/dist/tools/registry.js +54 -0
  293. package/dist/tools/resolver.d.ts +15 -0
  294. package/dist/tools/resolver.js +47 -0
  295. package/dist/tools/truncate.d.ts +8 -0
  296. package/dist/tools/truncate.js +50 -0
  297. package/dist/tools/types.d.ts +101 -0
  298. package/dist/tools/types.js +1 -0
  299. package/dist/tools/validate.d.ts +8 -0
  300. package/dist/tools/validate.js +130 -0
  301. package/dist/tools/websearch/fetch.d.ts +11 -0
  302. package/dist/tools/websearch/fetch.js +21 -0
  303. package/dist/tools/websearch/index.d.ts +19 -0
  304. package/dist/tools/websearch/index.js +78 -0
  305. package/dist/tools/websearch/providers/brave.d.ts +5 -0
  306. package/dist/tools/websearch/providers/brave.js +53 -0
  307. package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
  308. package/dist/tools/websearch/providers/duckduckgo.js +75 -0
  309. package/dist/tools/websearch/providers/tavily.d.ts +11 -0
  310. package/dist/tools/websearch/providers/tavily.js +89 -0
  311. package/dist/tools/websearch/types.d.ts +51 -0
  312. package/dist/tools/websearch/types.js +18 -0
  313. package/dist/tools/websearch/websearch.d.ts +10 -0
  314. package/dist/tools/websearch/websearch.js +48 -0
  315. package/dist/update/index.d.ts +10 -0
  316. package/dist/update/index.js +6 -0
  317. package/dist/update/ipc.d.ts +15 -0
  318. package/dist/update/ipc.js +41 -0
  319. package/dist/update/scheduler.d.ts +34 -0
  320. package/dist/update/scheduler.js +57 -0
  321. package/dist/update/snapshot.d.ts +39 -0
  322. package/dist/update/snapshot.js +99 -0
  323. package/dist/update/updater.d.ts +48 -0
  324. package/dist/update/updater.js +83 -0
  325. package/dist/update/version.d.ts +18 -0
  326. package/dist/update/version.js +49 -0
  327. package/drizzle/0000_magical_nightshade.sql +55 -0
  328. package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
  329. package/drizzle/0002_amusing_micromacro.sql +12 -0
  330. package/drizzle/0003_regular_purifiers.sql +2 -0
  331. package/drizzle/meta/0000_snapshot.json +448 -0
  332. package/drizzle/meta/0001_snapshot.json +539 -0
  333. package/drizzle/meta/0002_snapshot.json +633 -0
  334. package/drizzle/meta/0003_snapshot.json +645 -0
  335. package/drizzle/meta/_journal.json +34 -0
  336. package/package.json +108 -8
@@ -0,0 +1,15 @@
1
+ import type { ChatTool } from '../shared/types/llm.js';
2
+ import type { ToolDef } from '../shared/types/tool.js';
3
+ import type { ToolFactory, ToolFactoryContext, ToolRegistry } from './types.js';
4
+ /** Create an empty tool registry. */
5
+ export declare function createToolRegistry(): ToolRegistry;
6
+ /** Register a fully-constructed tool definition (eager). */
7
+ export declare function registerTool(registry: ToolRegistry, tool: ToolDef): void;
8
+ /** Register a lazy factory that constructs the tool on first access. */
9
+ export declare function registerToolFactory(registry: ToolRegistry, name: string, factory: ToolFactory): void;
10
+ /** Get a tool by name. Triggers and caches lazy factories. Returns undefined if not found. */
11
+ export declare function getTool(registry: ToolRegistry, name: string, ctx?: ToolFactoryContext): ToolDef | undefined;
12
+ /** List all tools. Triggers and caches all lazy factories. */
13
+ export declare function listTools(registry: ToolRegistry, ctx?: ToolFactoryContext): ToolDef[];
14
+ /** Convert registered tools to ChatTool[] for sending to the LLM. */
15
+ export declare function getToolSchemas(registry: ToolRegistry, ctx?: ToolFactoryContext): ChatTool[];
@@ -0,0 +1,54 @@
1
+ /** Create an empty tool registry. */
2
+ export function createToolRegistry() {
3
+ return { tools: new Map(), factories: new Map() };
4
+ }
5
+ /** Register a fully-constructed tool definition (eager). */
6
+ export function registerTool(registry, tool) {
7
+ registry.tools.set(tool.name, tool);
8
+ }
9
+ /** Register a lazy factory that constructs the tool on first access. */
10
+ export function registerToolFactory(registry, name, factory) {
11
+ registry.factories.set(name, factory);
12
+ }
13
+ /** Get a tool by name. Triggers and caches lazy factories. Returns undefined if not found. */
14
+ export function getTool(registry, name, ctx) {
15
+ // Check eager tools first
16
+ const eager = registry.tools.get(name);
17
+ if (eager)
18
+ return eager;
19
+ // Check lazy factories
20
+ const factory = registry.factories.get(name);
21
+ if (factory && ctx) {
22
+ const tool = factory(ctx);
23
+ if (tool) {
24
+ registry.tools.set(name, tool); // cache
25
+ registry.factories.delete(name);
26
+ return tool;
27
+ }
28
+ // Factory returned null → remove
29
+ registry.factories.delete(name);
30
+ }
31
+ return undefined;
32
+ }
33
+ /** List all tools. Triggers and caches all lazy factories. */
34
+ export function listTools(registry, ctx) {
35
+ // Materialize factories if context is provided
36
+ if (ctx) {
37
+ for (const [name, factory] of registry.factories) {
38
+ const tool = factory(ctx);
39
+ if (tool) {
40
+ registry.tools.set(name, tool);
41
+ }
42
+ registry.factories.delete(name);
43
+ }
44
+ }
45
+ return Array.from(registry.tools.values());
46
+ }
47
+ /** Convert registered tools to ChatTool[] for sending to the LLM. */
48
+ export function getToolSchemas(registry, ctx) {
49
+ return listTools(registry, ctx).map((t) => ({
50
+ name: t.name,
51
+ description: t.description,
52
+ parameters: t.parameters,
53
+ }));
54
+ }
@@ -0,0 +1,15 @@
1
+ import type { ResolveResult, URLRegistry, URLResolveContext, URLResolver } from '../shared/types/tool.js';
2
+ /** Create an empty URL resolver registry. */
3
+ declare function createURLRegistry(): URLRegistry;
4
+ /** Register a resolver for its scheme. First registration for a scheme wins;
5
+ * later registrations are ignored (stable dispatch, no accidental overrides). */
6
+ declare function registerURLResolver(registry: URLRegistry, resolver: URLResolver): void;
7
+ /** Does `path` carry a `scheme://` prefix? Plain file paths return false. */
8
+ declare function isURLPath(path: string): boolean;
9
+ /** Extract the scheme from a `scheme://…` path, or null for plain file paths.
10
+ * Scheme is the run of [a-z0-9+.-] before the first `://`. */
11
+ declare function parseScheme(path: string): string | null;
12
+ /** Resolve a `scheme://…` URL via the registry.
13
+ * Returns { _tag: 'error' } for unregistered schemes. */
14
+ declare function resolveURL(registry: URLRegistry, url: string, ctx: URLResolveContext): Promise<ResolveResult>;
15
+ export { createURLRegistry, isURLPath, parseScheme, registerURLResolver, resolveURL };
@@ -0,0 +1,47 @@
1
+ /** Create an empty URL resolver registry. */
2
+ function createURLRegistry() {
3
+ return { resolvers: new Map() };
4
+ }
5
+ /** Register a resolver for its scheme. First registration for a scheme wins;
6
+ * later registrations are ignored (stable dispatch, no accidental overrides). */
7
+ function registerURLResolver(registry, resolver) {
8
+ if (!registry.resolvers.has(resolver.scheme)) {
9
+ registry.resolvers.set(resolver.scheme, resolver);
10
+ }
11
+ }
12
+ /** Does `path` carry a `scheme://` prefix? Plain file paths return false. */
13
+ function isURLPath(path) {
14
+ return parseScheme(path) !== null;
15
+ }
16
+ /** Extract the scheme from a `scheme://…` path, or null for plain file paths.
17
+ * Scheme is the run of [a-z0-9+.-] before the first `://`. */
18
+ function parseScheme(path) {
19
+ const idx = path.indexOf('://');
20
+ if (idx <= 0)
21
+ return null;
22
+ const scheme = path.slice(0, idx);
23
+ // Schemes are ASCII alphanumeric plus +, -, . (RFC 3986).
24
+ if (!/^[a-z0-9+.-]+$/i.test(scheme))
25
+ return null;
26
+ return scheme.toLowerCase();
27
+ }
28
+ /** Resolve a `scheme://…` URL via the registry.
29
+ * Returns { _tag: 'error' } for unregistered schemes. */
30
+ async function resolveURL(registry, url, ctx) {
31
+ const scheme = parseScheme(url);
32
+ if (scheme === null) {
33
+ return { _tag: 'error', error: `Not a URL path: ${url}` };
34
+ }
35
+ const resolver = registry.resolvers.get(scheme);
36
+ if (!resolver) {
37
+ return { _tag: 'error', error: `No resolver registered for scheme "${scheme}://"` };
38
+ }
39
+ try {
40
+ const content = await resolver.resolve(url, ctx);
41
+ return { _tag: 'ok', content };
42
+ }
43
+ catch (e) {
44
+ return { _tag: 'error', error: e instanceof Error ? e.message : String(e) };
45
+ }
46
+ }
47
+ export { createURLRegistry, isURLPath, parseScheme, registerURLResolver, resolveURL };
@@ -0,0 +1,8 @@
1
+ import type { TruncateOptions, TruncateResult } from './types.js';
2
+ /** Default truncation thresholds — tuned for LLM context windows. */
3
+ export declare const DEFAULT_TRUNCATE_OPTIONS: TruncateOptions;
4
+ /**
5
+ * Truncate output to fit within line and character limits.
6
+ * Preserves head and tail, inserting a marker for omitted content.
7
+ */
8
+ export declare function truncateOutput(output: string, opts?: TruncateOptions): TruncateResult;
@@ -0,0 +1,50 @@
1
+ /** Default truncation thresholds — tuned for LLM context windows. */
2
+ export const DEFAULT_TRUNCATE_OPTIONS = {
3
+ maxLines: 2000,
4
+ maxChars: 100_000,
5
+ headLines: 50,
6
+ tailLines: 50,
7
+ };
8
+ /**
9
+ * Truncate output to fit within line and character limits.
10
+ * Preserves head and tail, inserting a marker for omitted content.
11
+ */
12
+ export function truncateOutput(output, opts = DEFAULT_TRUNCATE_OPTIONS) {
13
+ if (output === '') {
14
+ return { output: '', truncated: false, totalLines: 0, totalChars: 0 };
15
+ }
16
+ const lines = output.split('\n');
17
+ const totalLines = lines.length;
18
+ const totalChars = output.length;
19
+ // Check if truncation is needed
20
+ const needsLineTrunc = totalLines > opts.maxLines;
21
+ const needsCharTrunc = totalChars > opts.maxChars;
22
+ if (!needsLineTrunc && !needsCharTrunc) {
23
+ return { output, truncated: false, totalLines, totalChars };
24
+ }
25
+ // Line-based truncation takes priority
26
+ if (needsLineTrunc) {
27
+ const head = lines.slice(0, opts.headLines);
28
+ const tailStart = totalLines - opts.tailLines;
29
+ // If tail would overlap head, show only head + marker
30
+ if (tailStart <= opts.headLines) {
31
+ const omitted = totalLines - head.length;
32
+ const marker = `[... ${omitted} lines truncated ...]`;
33
+ return { output: [...head, marker].join('\n'), truncated: true, totalLines, totalChars };
34
+ }
35
+ const tail = lines.slice(tailStart);
36
+ const omitted = totalLines - head.length - tail.length;
37
+ const marker = `[... ${omitted} lines truncated ...]`;
38
+ const result = [...head, marker, ...tail].join('\n');
39
+ return { output: result, truncated: true, totalLines, totalChars };
40
+ }
41
+ // Char-based truncation (keep proportional head/tail of chars)
42
+ const keepChars = opts.maxChars;
43
+ const headChars = Math.floor(keepChars * 0.5);
44
+ const tailChars = keepChars - headChars;
45
+ const head = output.slice(0, headChars);
46
+ const tail = output.slice(totalChars - tailChars);
47
+ const omitted = totalChars - keepChars;
48
+ const marker = `\n[... ${omitted} chars truncated ...]\n`;
49
+ return { output: head + marker + tail, truncated: true, totalLines, totalChars };
50
+ }
@@ -0,0 +1,101 @@
1
+ import type { JSONSchema } from '../shared/types/base.js';
2
+ import type { ChatTool } from '../shared/types/llm.js';
3
+ import type { ToolContext, ToolDef, ToolExecutor, ToolMode, ToolPermission, ToolResult } from '../shared/types/tool.js';
4
+ /** Mutable tool registry. Stores both eager tool definitions and lazy factories. */
5
+ type ToolRegistry = {
6
+ tools: Map<string, ToolDef>;
7
+ factories: Map<string, ToolFactory>;
8
+ };
9
+ /** Context passed to tool factories for lazy tool construction. */
10
+ type ToolFactoryContext = {
11
+ config: Record<string, unknown>;
12
+ cwd: string;
13
+ };
14
+ /** Factory function for lazy tool loading. Returns null if tool is unavailable. */
15
+ type ToolFactory = (ctx: ToolFactoryContext) => ToolDef | null;
16
+ /** Result of a permission check. */
17
+ type PermissionResult = {
18
+ _tag: 'allow';
19
+ } | {
20
+ _tag: 'deny';
21
+ reason: string;
22
+ } | {
23
+ _tag: 'ask';
24
+ reason: string;
25
+ toolCallId: string;
26
+ };
27
+ /** Permission checker interface. The executor calls `check` before running a tool. */
28
+ type PermissionChecker = {
29
+ check: (tool: ToolDef, input: unknown, ctx: ToolContext) => Promise<PermissionResult>;
30
+ confirm: (toolCallId: string, approved: boolean) => void;
31
+ };
32
+ /** Result of JSON Schema validation. */
33
+ type ValidationResult = {
34
+ valid: true;
35
+ } | {
36
+ valid: false;
37
+ error: string;
38
+ };
39
+ /** Options for output truncation. */
40
+ type TruncateOptions = {
41
+ maxLines: number;
42
+ maxChars: number;
43
+ headLines: number;
44
+ tailLines: number;
45
+ };
46
+ /** Result of output truncation. */
47
+ type TruncateResult = {
48
+ output: string;
49
+ truncated: boolean;
50
+ totalLines: number;
51
+ totalChars: number;
52
+ };
53
+ /** Input for the read tool. */
54
+ type ReadInput = {
55
+ path: string;
56
+ offset?: number;
57
+ limit?: number;
58
+ };
59
+ /** Input for the write tool. */
60
+ type WriteInput = {
61
+ path: string;
62
+ content: string;
63
+ };
64
+ /** Input for the edit tool. Two modes (spec §16.4):
65
+ * - diff: search/replace via oldText/newText
66
+ * - hashline: content-hash-anchored patch via `patch` */
67
+ type EditInput = {
68
+ path: string;
69
+ oldText: string;
70
+ newText: string;
71
+ } | {
72
+ path: string;
73
+ patch: string;
74
+ };
75
+ /** Input for the glob tool. */
76
+ type GlobInput = {
77
+ pattern: string;
78
+ path?: string;
79
+ };
80
+ /** Input for the grep tool. */
81
+ type GrepInput = {
82
+ pattern: string;
83
+ path?: string;
84
+ caseSensitive?: boolean;
85
+ maxResults?: number;
86
+ };
87
+ /** Input for the bash tool (sync mode). */
88
+ type BashInput = {
89
+ command: string;
90
+ cwd?: string;
91
+ timeout?: number;
92
+ env?: Record<string, string>;
93
+ };
94
+ /** A single grep match. */
95
+ type GrepMatch = {
96
+ file: string;
97
+ line: number;
98
+ text: string;
99
+ match: string;
100
+ };
101
+ export type { BashInput, ChatTool, EditInput, GlobInput, GrepInput, GrepMatch, JSONSchema, PermissionChecker, PermissionResult, ReadInput, ToolContext, ToolDef, ToolExecutor, ToolFactory, ToolFactoryContext, ToolMode, ToolPermission, ToolRegistry, ToolResult, TruncateOptions, TruncateResult, ValidationResult, WriteInput, };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { JSONSchema } from '../shared/types/base.js';
2
+ import type { ValidationResult } from './types.js';
3
+ /**
4
+ * Validate a value against a JSON Schema (draft-07 subset).
5
+ * Supports: type, required, properties, items, enum, additionalProperties, anyOf, oneOf.
6
+ * No external dependency — lightweight custom implementation.
7
+ */
8
+ export declare function validateInput(schema: JSONSchema, value: unknown): ValidationResult;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Validate a value against a JSON Schema (draft-07 subset).
3
+ * Supports: type, required, properties, items, enum, additionalProperties, anyOf, oneOf.
4
+ * No external dependency — lightweight custom implementation.
5
+ */
6
+ export function validateInput(schema, value) {
7
+ const error = validateNode(schema, value, '');
8
+ if (error)
9
+ return { valid: false, error };
10
+ return { valid: true };
11
+ }
12
+ function validateNode(schema, value, path) {
13
+ // Empty schema accepts anything
14
+ if (Object.keys(schema).length === 0)
15
+ return null;
16
+ // anyOf: at least one must pass
17
+ if (schema.anyOf) {
18
+ const passed = schema.anyOf.some((s) => validateNode(s, value, path) === null);
19
+ if (!passed)
20
+ return `${path || 'value'}: does not match anyOf schemas`;
21
+ return null;
22
+ }
23
+ // oneOf: exactly one must pass
24
+ if (schema.oneOf) {
25
+ const count = schema.oneOf.filter((s) => validateNode(s, value, path) === null).length;
26
+ if (count !== 1)
27
+ return `${path || 'value'}: must match exactly one oneOf schema (matched ${count})`;
28
+ return null;
29
+ }
30
+ // enum
31
+ if (schema.enum !== undefined) {
32
+ if (!schema.enum.includes(value)) {
33
+ return `${path || 'value'}: must be one of ${JSON.stringify(schema.enum)}`;
34
+ }
35
+ }
36
+ // type check
37
+ if (schema.type) {
38
+ const typeError = checkType(schema.type, value, path);
39
+ if (typeError)
40
+ return typeError;
41
+ }
42
+ // object validation
43
+ if (schema.type === 'object' &&
44
+ typeof value === 'object' &&
45
+ value !== null &&
46
+ !Array.isArray(value)) {
47
+ const obj = value;
48
+ // required fields
49
+ if (schema.required) {
50
+ for (const field of schema.required) {
51
+ if (!(field in obj)) {
52
+ return `${path ? `${path}.` : ''}${field}: missing required field`;
53
+ }
54
+ }
55
+ }
56
+ // properties
57
+ if (schema.properties) {
58
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
59
+ if (key in obj) {
60
+ const err = validateNode(propSchema, obj[key], path ? `${path}.${key}` : key);
61
+ if (err)
62
+ return err;
63
+ }
64
+ }
65
+ }
66
+ // additionalProperties
67
+ if (schema.additionalProperties === false && schema.properties) {
68
+ const knownKeys = new Set(Object.keys(schema.properties));
69
+ for (const key of Object.keys(obj)) {
70
+ if (!knownKeys.has(key)) {
71
+ return `${path ? `${path}.` : ''}${key}: additional property not allowed`;
72
+ }
73
+ }
74
+ }
75
+ }
76
+ // array validation
77
+ if (schema.type === 'array' && Array.isArray(value)) {
78
+ if (schema.items) {
79
+ const itemSchema = Array.isArray(schema.items) ? schema.items : [schema.items];
80
+ for (let i = 0; i < value.length; i++) {
81
+ const s = itemSchema[i] ?? itemSchema[0];
82
+ if (s) {
83
+ const err = validateNode(s, value[i], `${path}[${i}]`);
84
+ if (err)
85
+ return err;
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ function checkType(type, value, path) {
93
+ const label = path || 'value';
94
+ switch (type) {
95
+ case 'string':
96
+ if (typeof value !== 'string')
97
+ return `${label}: expected string, got ${typeof value}`;
98
+ break;
99
+ case 'number':
100
+ if (typeof value !== 'number' || Number.isNaN(value)) {
101
+ return `${label}: expected number, got ${typeof value}`;
102
+ }
103
+ break;
104
+ case 'integer':
105
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
106
+ return `${label}: expected integer, got ${typeof value}`;
107
+ }
108
+ break;
109
+ case 'boolean':
110
+ if (typeof value !== 'boolean')
111
+ return `${label}: expected boolean, got ${typeof value}`;
112
+ break;
113
+ case 'object':
114
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
115
+ return `${label}: expected object`;
116
+ }
117
+ break;
118
+ case 'array':
119
+ if (!Array.isArray(value))
120
+ return `${label}: expected array, got ${typeof value}`;
121
+ break;
122
+ case 'null':
123
+ if (value !== null)
124
+ return `${label}: expected null, got ${typeof value}`;
125
+ break;
126
+ default:
127
+ break;
128
+ }
129
+ return null;
130
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 读 HTTPS_PROXY/HTTP_PROXY 环境变量,有则返回带 dispatcher 的 fetch,
3
+ * 无则返回全局 fetch。仅影响本模块,不调用 setGlobalDispatcher(避免污染全局)。
4
+ *
5
+ * AGENTS.md 规定代理端口 7890;Node 内置 fetch 不自动走系统代理,故需包装。
6
+ *
7
+ * 注意:必须用 npm 包 undici 自带的 `fetch`(与 ProxyAgent 同版本)发起请求,
8
+ * 而非 Node 全局 fetch——Node 捆绑的 undici 与 npm 安装的 undici 版本不同,
9
+ * 跨版本传 Dispatcher 会被拒绝(UND_ERR_INVALID_ARG)。详见设计文档 §6。
10
+ */
11
+ export declare function createFetch(): typeof fetch;
@@ -0,0 +1,21 @@
1
+ import { ProxyAgent, fetch as undiciFetch } from 'undici';
2
+ /**
3
+ * 读 HTTPS_PROXY/HTTP_PROXY 环境变量,有则返回带 dispatcher 的 fetch,
4
+ * 无则返回全局 fetch。仅影响本模块,不调用 setGlobalDispatcher(避免污染全局)。
5
+ *
6
+ * AGENTS.md 规定代理端口 7890;Node 内置 fetch 不自动走系统代理,故需包装。
7
+ *
8
+ * 注意:必须用 npm 包 undici 自带的 `fetch`(与 ProxyAgent 同版本)发起请求,
9
+ * 而非 Node 全局 fetch——Node 捆绑的 undici 与 npm 安装的 undici 版本不同,
10
+ * 跨版本传 Dispatcher 会被拒绝(UND_ERR_INVALID_ARG)。详见设计文档 §6。
11
+ */
12
+ export function createFetch() {
13
+ const proxy = process.env.HTTPS_PROXY ||
14
+ process.env.https_proxy ||
15
+ process.env.HTTP_PROXY ||
16
+ process.env.http_proxy;
17
+ if (!proxy)
18
+ return fetch;
19
+ const dispatcher = new ProxyAgent(proxy);
20
+ return ((url, init) => undiciFetch(url, { ...init, dispatcher }));
21
+ }
@@ -0,0 +1,19 @@
1
+ import type { WebSearchConfig } from '../../shared/types/config.js';
2
+ import type { Recency, WebSearchProvider, WebSearchProviderId, WebSearchResponse } from './types.js';
3
+ import { clampNumResults, DEFAULT_NUM_RESULTS } from './types.js';
4
+ /** 按 preference + key 可用性解析目标 provider。auto 优先级:tavily > brave > duckduckgo。 */
5
+ export declare function resolveProvider(preference: 'auto' | WebSearchProviderId, keys: {
6
+ tavily?: string;
7
+ brave?: string;
8
+ }): WebSearchProvider;
9
+ /** 将统一响应格式化为 LLM 友好的纯文本。 */
10
+ export declare function formatForLLM(response: WebSearchResponse): string;
11
+ /** 测试钩子:覆盖默认 fetch(undici ProxyAgent 包装版)。 */
12
+ export declare function setFetchOverride(f: typeof fetch | undefined): void;
13
+ /** 工具入口:解析 provider → search → 返回 WebSearchResponse。 */
14
+ export declare function runWebSearch(input: {
15
+ query: string;
16
+ numResults?: number;
17
+ recency?: Recency;
18
+ }, config: WebSearchConfig, abort: AbortSignal): Promise<WebSearchResponse>;
19
+ export { clampNumResults, DEFAULT_NUM_RESULTS };
@@ -0,0 +1,78 @@
1
+ import { createFetch } from './fetch.js';
2
+ import { braveProvider } from './providers/brave.js';
3
+ import { duckduckgoProvider } from './providers/duckduckgo.js';
4
+ import { tavilyProvider } from './providers/tavily.js';
5
+ import { clampNumResults, DEFAULT_NUM_RESULTS } from './types.js';
6
+ const PROVIDERS = {
7
+ duckduckgo: duckduckgoProvider,
8
+ tavily: tavilyProvider,
9
+ brave: braveProvider,
10
+ };
11
+ /** 环境变量名 → config key 字段映射。环境变量优先于 config。 */
12
+ function resolveKeys(config) {
13
+ return {
14
+ tavily: process.env.TAVILY_API_KEY ?? config.tavilyApiKey,
15
+ brave: process.env.BRAVE_API_KEY ?? config.braveApiKey,
16
+ };
17
+ }
18
+ /** 按 preference + key 可用性解析目标 provider。auto 优先级:tavily > brave > duckduckgo。 */
19
+ export function resolveProvider(preference, keys) {
20
+ if (preference === 'auto') {
21
+ if (tavilyProvider.isAvailable(keys.tavily))
22
+ return tavilyProvider;
23
+ if (braveProvider.isAvailable(keys.brave))
24
+ return braveProvider;
25
+ return duckduckgoProvider;
26
+ }
27
+ const provider = PROVIDERS[preference];
28
+ if (!provider)
29
+ throw new Error(`Unknown websearch provider: ${preference}`);
30
+ if (!provider.isAvailable(preference === 'tavily' ? keys.tavily : keys.brave)) {
31
+ throw new Error(`${preference} provider requires an API key (set ${preference === 'tavily' ? 'TAVILY_API_KEY' : 'BRAVE_API_KEY'} or config).`);
32
+ }
33
+ return provider;
34
+ }
35
+ const SNIPPET_MAX = 240;
36
+ function truncate(text, max) {
37
+ return text.length <= max ? text : `${text.slice(0, Math.max(0, max - 1))}…`;
38
+ }
39
+ /** 将统一响应格式化为 LLM 友好的纯文本。 */
40
+ export function formatForLLM(response) {
41
+ const parts = [];
42
+ if (response.answer?.trim()) {
43
+ parts.push(response.answer.trim());
44
+ }
45
+ if (response.sources.length > 0) {
46
+ parts.push(`## Sources (${response.sources.length})`);
47
+ for (const [i, src] of response.sources.entries()) {
48
+ parts.push(`[${i + 1}] ${src.title}`);
49
+ parts.push(` ${src.url}`);
50
+ if (src.snippet)
51
+ parts.push(` ${truncate(src.snippet, SNIPPET_MAX)}`);
52
+ }
53
+ }
54
+ return parts.join('\n');
55
+ }
56
+ /** 模块级 fetch 覆盖(测试注入)。undefined 时用 createFetch()。 */
57
+ let fetchOverride;
58
+ /** 测试钩子:覆盖默认 fetch(undici ProxyAgent 包装版)。 */
59
+ export function setFetchOverride(f) {
60
+ fetchOverride = f;
61
+ }
62
+ function currentFetch() {
63
+ return fetchOverride ?? createFetch();
64
+ }
65
+ /** 工具入口:解析 provider → search → 返回 WebSearchResponse。 */
66
+ export async function runWebSearch(input, config, abort) {
67
+ const keys = resolveKeys(config);
68
+ const provider = resolveProvider(config.provider, keys);
69
+ return provider.search({
70
+ query: input.query,
71
+ limit: clampNumResults(input.numResults),
72
+ recency: input.recency,
73
+ signal: abort,
74
+ apiKey: provider.id === 'tavily' ? keys.tavily : provider.id === 'brave' ? keys.brave : undefined,
75
+ fetchImpl: currentFetch(),
76
+ });
77
+ }
78
+ export { clampNumResults, DEFAULT_NUM_RESULTS };
@@ -0,0 +1,5 @@
1
+ import type { Recency, WebSearchProvider } from '../types.js';
2
+ /** 导出供测试:recency → Brave freshness 代码映射。 */
3
+ export declare function recencyToFreshness(recency: Recency | undefined): string | undefined;
4
+ /** Brave 后端(独立索引,需 key)。 */
5
+ export declare const braveProvider: WebSearchProvider;
@@ -0,0 +1,53 @@
1
+ import { DEFAULT_NUM_RESULTS } from '../types.js';
2
+ const BRAVE_SEARCH_URL = 'https://api.search.brave.com/res/v1/web/search';
3
+ /** 导出供测试:recency → Brave freshness 代码映射。 */
4
+ export function recencyToFreshness(recency) {
5
+ switch (recency) {
6
+ case 'day':
7
+ return 'pd';
8
+ case 'week':
9
+ return 'pw';
10
+ case 'month':
11
+ return 'pm';
12
+ case 'year':
13
+ return 'py';
14
+ default:
15
+ return undefined;
16
+ }
17
+ }
18
+ /** Brave 后端(独立索引,需 key)。 */
19
+ export const braveProvider = {
20
+ id: 'brave',
21
+ isAvailable: (apiKey) => Boolean(apiKey && apiKey.length > 0),
22
+ search: async (params) => {
23
+ if (!params.apiKey)
24
+ throw new Error('Brave requires an API key (BRAVE_API_KEY or config).');
25
+ const fetchImpl = params.fetchImpl ?? fetch;
26
+ const count = params.limit ?? DEFAULT_NUM_RESULTS;
27
+ const query = new URLSearchParams({ q: params.query, count: String(count) });
28
+ const freshness = recencyToFreshness(params.recency);
29
+ if (freshness)
30
+ query.set('freshness', freshness);
31
+ const response = await fetchImpl(`${BRAVE_SEARCH_URL}?${query.toString()}`, {
32
+ method: 'GET',
33
+ headers: {
34
+ 'X-Subscription-Token': params.apiKey,
35
+ Accept: 'application/json',
36
+ 'Accept-Encoding': 'gzip',
37
+ },
38
+ signal: params.signal,
39
+ });
40
+ if (!response.ok) {
41
+ const text = await response.text().catch(() => response.statusText);
42
+ throw new Error(`Brave API error (${response.status}): ${text}`);
43
+ }
44
+ const data = (await response.json());
45
+ const sources = [];
46
+ for (const r of data.web?.results ?? []) {
47
+ if (!r.url)
48
+ continue;
49
+ sources.push({ title: r.title ?? r.url, url: r.url, snippet: r.description ?? undefined });
50
+ }
51
+ return { provider: 'brave', sources };
52
+ },
53
+ };
@@ -0,0 +1,3 @@
1
+ import type { WebSearchProvider } from '../types.js';
2
+ /** DuckDuckGo Instant Answer 后端(零 key,恒可用)。recency 不支持,忽略。 */
3
+ export declare const duckduckgoProvider: WebSearchProvider;