@gugacoder/agentic-sdk 0.2.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 (129) hide show
  1. package/dist/agent.d.ts +2 -0
  2. package/dist/agent.js +463 -0
  3. package/dist/context/compaction.d.ts +27 -0
  4. package/dist/context/compaction.js +219 -0
  5. package/dist/context/models.d.ts +6 -0
  6. package/dist/context/models.js +41 -0
  7. package/dist/context/tokenizer.d.ts +5 -0
  8. package/dist/context/tokenizer.js +11 -0
  9. package/dist/context/usage.d.ts +11 -0
  10. package/dist/context/usage.js +49 -0
  11. package/dist/display-schemas.d.ts +1865 -0
  12. package/dist/display-schemas.js +219 -0
  13. package/dist/index.d.ts +38 -0
  14. package/dist/index.js +28 -0
  15. package/dist/middleware/logging.d.ts +2 -0
  16. package/dist/middleware/logging.js +32 -0
  17. package/dist/prompts/assembly.d.ts +13 -0
  18. package/dist/prompts/assembly.js +229 -0
  19. package/dist/providers.d.ts +19 -0
  20. package/dist/providers.js +44 -0
  21. package/dist/proxy.d.ts +2 -0
  22. package/dist/proxy.js +103 -0
  23. package/dist/schemas.d.ts +228 -0
  24. package/dist/schemas.js +51 -0
  25. package/dist/session.d.ts +7 -0
  26. package/dist/session.js +102 -0
  27. package/dist/structured.d.ts +18 -0
  28. package/dist/structured.js +38 -0
  29. package/dist/tool-repair.d.ts +21 -0
  30. package/dist/tool-repair.js +72 -0
  31. package/dist/tools/api-spec.d.ts +4 -0
  32. package/dist/tools/api-spec.js +123 -0
  33. package/dist/tools/apply-patch.d.ts +484 -0
  34. package/dist/tools/apply-patch.js +157 -0
  35. package/dist/tools/ask-user.d.ts +14 -0
  36. package/dist/tools/ask-user.js +27 -0
  37. package/dist/tools/bash.d.ts +550 -0
  38. package/dist/tools/bash.js +43 -0
  39. package/dist/tools/batch.d.ts +13 -0
  40. package/dist/tools/batch.js +84 -0
  41. package/dist/tools/brave-search.d.ts +6 -0
  42. package/dist/tools/brave-search.js +19 -0
  43. package/dist/tools/code-search.d.ts +20 -0
  44. package/dist/tools/code-search.js +42 -0
  45. package/dist/tools/diagnostics.d.ts +4 -0
  46. package/dist/tools/diagnostics.js +69 -0
  47. package/dist/tools/display.d.ts +483 -0
  48. package/dist/tools/display.js +77 -0
  49. package/dist/tools/edit.d.ts +682 -0
  50. package/dist/tools/edit.js +47 -0
  51. package/dist/tools/glob.d.ts +4 -0
  52. package/dist/tools/glob.js +42 -0
  53. package/dist/tools/grep.d.ts +6 -0
  54. package/dist/tools/grep.js +69 -0
  55. package/dist/tools/http-request.d.ts +7 -0
  56. package/dist/tools/http-request.js +98 -0
  57. package/dist/tools/index.d.ts +1611 -0
  58. package/dist/tools/index.js +46 -0
  59. package/dist/tools/job-tools.d.ts +24 -0
  60. package/dist/tools/job-tools.js +67 -0
  61. package/dist/tools/list-dir.d.ts +5 -0
  62. package/dist/tools/list-dir.js +79 -0
  63. package/dist/tools/multi-edit.d.ts +814 -0
  64. package/dist/tools/multi-edit.js +57 -0
  65. package/dist/tools/read.d.ts +5 -0
  66. package/dist/tools/read.js +33 -0
  67. package/dist/tools/task.d.ts +21 -0
  68. package/dist/tools/task.js +51 -0
  69. package/dist/tools/todo.d.ts +14 -0
  70. package/dist/tools/todo.js +60 -0
  71. package/dist/tools/web-fetch.d.ts +4 -0
  72. package/dist/tools/web-fetch.js +126 -0
  73. package/dist/tools/web-search.d.ts +22 -0
  74. package/dist/tools/web-search.js +48 -0
  75. package/dist/tools/write.d.ts +550 -0
  76. package/dist/tools/write.js +30 -0
  77. package/dist/types.d.ts +201 -0
  78. package/dist/types.js +1 -0
  79. package/package.json +43 -0
  80. package/src/agent.ts +520 -0
  81. package/src/context/compaction.ts +265 -0
  82. package/src/context/models.ts +42 -0
  83. package/src/context/tokenizer.ts +12 -0
  84. package/src/context/usage.ts +65 -0
  85. package/src/display-schemas.ts +276 -0
  86. package/src/index.ts +43 -0
  87. package/src/middleware/logging.ts +37 -0
  88. package/src/prompts/assembly.ts +263 -0
  89. package/src/prompts/identity.md +10 -0
  90. package/src/prompts/patterns.md +7 -0
  91. package/src/prompts/safety.md +7 -0
  92. package/src/prompts/tool-guide.md +9 -0
  93. package/src/prompts/tools/bash.md +7 -0
  94. package/src/prompts/tools/edit.md +7 -0
  95. package/src/prompts/tools/glob.md +7 -0
  96. package/src/prompts/tools/grep.md +7 -0
  97. package/src/prompts/tools/read.md +7 -0
  98. package/src/prompts/tools/write.md +7 -0
  99. package/src/providers.ts +58 -0
  100. package/src/proxy.ts +101 -0
  101. package/src/schemas.ts +58 -0
  102. package/src/session.ts +110 -0
  103. package/src/structured.ts +65 -0
  104. package/src/tool-repair.ts +92 -0
  105. package/src/tools/api-spec.ts +158 -0
  106. package/src/tools/apply-patch.ts +188 -0
  107. package/src/tools/ask-user.ts +40 -0
  108. package/src/tools/bash.ts +51 -0
  109. package/src/tools/batch.ts +103 -0
  110. package/src/tools/brave-search.ts +24 -0
  111. package/src/tools/code-search.ts +69 -0
  112. package/src/tools/diagnostics.ts +93 -0
  113. package/src/tools/display.ts +105 -0
  114. package/src/tools/edit.ts +55 -0
  115. package/src/tools/glob.ts +46 -0
  116. package/src/tools/grep.ts +68 -0
  117. package/src/tools/http-request.ts +103 -0
  118. package/src/tools/index.ts +48 -0
  119. package/src/tools/job-tools.ts +84 -0
  120. package/src/tools/list-dir.ts +102 -0
  121. package/src/tools/multi-edit.ts +65 -0
  122. package/src/tools/read.ts +40 -0
  123. package/src/tools/task.ts +71 -0
  124. package/src/tools/todo.ts +82 -0
  125. package/src/tools/web-fetch.ts +155 -0
  126. package/src/tools/web-search.ts +75 -0
  127. package/src/tools/write.ts +34 -0
  128. package/src/types.ts +145 -0
  129. package/tsconfig.json +17 -0
@@ -0,0 +1,103 @@
1
+ import { tool, type Tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ const MAX_OUTPUT = 50_000;
5
+
6
+ /**
7
+ * Factory that creates the Batch tool for parallel tool execution.
8
+ *
9
+ * Receives the resolved tool registry so it can look up tools by name.
10
+ * The Batch tool itself is excluded from the registry to prevent recursion.
11
+ */
12
+ export function createBatchTool(toolRegistry: Record<string, Tool>) {
13
+ // Build the list of available tool names (excluding Batch itself)
14
+ const availableTools = Object.keys(toolRegistry).filter(
15
+ (name) => name !== "Batch"
16
+ );
17
+
18
+ return tool({
19
+ description:
20
+ "Execute 2-10 tool calls simultaneously in parallel using Promise.allSettled(). " +
21
+ "Partial failures do not cancel other calls. " +
22
+ "Batch cannot call itself (no recursion). " +
23
+ `Available tools: ${availableTools.join(", ")}`,
24
+ inputSchema: z.object({
25
+ tool_calls: z
26
+ .array(
27
+ z.object({
28
+ tool: z
29
+ .string()
30
+ .describe("Name of the tool to call (must be a registered tool)"),
31
+ parameters: z
32
+ .record(z.any())
33
+ .describe("Parameters to pass to the tool"),
34
+ })
35
+ )
36
+ .min(2)
37
+ .max(10)
38
+ .describe("Array of tool calls to execute in parallel (2-10 items)"),
39
+ }),
40
+ execute: async ({ tool_calls }) => {
41
+ // Validate: no Batch inside Batch
42
+ const batchCalls = tool_calls.filter((tc) => tc.tool === "Batch");
43
+ if (batchCalls.length > 0) {
44
+ return "Error: Batch cannot call itself. Remove Batch from tool_calls.";
45
+ }
46
+
47
+ // Validate: all tools exist
48
+ const unknownTools = tool_calls.filter(
49
+ (tc) => !availableTools.includes(tc.tool)
50
+ );
51
+ if (unknownTools.length > 0) {
52
+ const names = unknownTools.map((tc) => tc.tool).join(", ");
53
+ return `Error: Unknown tool(s): ${names}. Available: ${availableTools.join(", ")}`;
54
+ }
55
+
56
+ // Execute all calls in parallel
57
+ const promises = tool_calls.map(async (tc, index) => {
58
+ const targetTool = toolRegistry[tc.tool] as Tool & {
59
+ execute?: (params: Record<string, unknown>) => Promise<string>;
60
+ };
61
+
62
+ if (!targetTool.execute) {
63
+ return { index, tool: tc.tool, result: `Error: Tool "${tc.tool}" has no execute function.` };
64
+ }
65
+
66
+ try {
67
+ const result = await targetTool.execute(tc.parameters, {
68
+ toolCallId: `batch-${index}-${tc.tool}`,
69
+ messages: [],
70
+ abortSignal: undefined as unknown as AbortSignal,
71
+ });
72
+ return { index, tool: tc.tool, result: String(result) };
73
+ } catch (err: any) {
74
+ return {
75
+ index,
76
+ tool: tc.tool,
77
+ result: `Error: ${err.message ?? String(err)}`,
78
+ };
79
+ }
80
+ });
81
+
82
+ const settled = await Promise.allSettled(promises);
83
+
84
+ const parts: string[] = [];
85
+ for (const outcome of settled) {
86
+ if (outcome.status === "fulfilled") {
87
+ const { index, tool: toolName, result } = outcome.value;
88
+ parts.push(`### [${index}] ${toolName}\n\n${result}`);
89
+ } else {
90
+ parts.push(`### [?] Error\n\n${outcome.reason?.message ?? String(outcome.reason)}`);
91
+ }
92
+ }
93
+
94
+ let output = parts.join("\n\n---\n\n");
95
+
96
+ if (output.length > MAX_OUTPUT) {
97
+ output = output.slice(0, MAX_OUTPUT) + "\n...[truncated at 50KB]";
98
+ }
99
+
100
+ return output;
101
+ },
102
+ });
103
+ }
@@ -0,0 +1,24 @@
1
+ import type { WebSearchResult } from "./web-search.js";
2
+
3
+ /**
4
+ * Brave Web Search API client.
5
+ * Docs: https://api.search.brave.com/app#/documentation/web-search
6
+ */
7
+ export async function braveSearch(
8
+ query: string,
9
+ numResults: number,
10
+ apiKey: string
11
+ ): Promise<WebSearchResult[]> {
12
+ const count = numResults ?? 5;
13
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`;
14
+ const res = await fetch(url, {
15
+ headers: { "X-Subscription-Token": apiKey },
16
+ });
17
+ if (!res.ok) return [];
18
+ const data: any = await res.json();
19
+ return (data.web?.results ?? []).slice(0, numResults).map((r: any) => ({
20
+ title: r.title,
21
+ url: r.url,
22
+ snippet: r.description,
23
+ }));
24
+ }
@@ -0,0 +1,69 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ const MAX_OUTPUT = 50_000;
5
+
6
+ /**
7
+ * A single code search result returned by a CodeSearch provider.
8
+ */
9
+ export interface CodeSearchResult {
10
+ title: string;
11
+ url: string;
12
+ content: string;
13
+ }
14
+
15
+ /**
16
+ * Callback type for pluggable code search providers.
17
+ * Implementors receive the query and return code examples and documentation snippets.
18
+ */
19
+ export type CodeSearchProvider = (
20
+ query: string
21
+ ) => Promise<CodeSearchResult[]>;
22
+
23
+ /**
24
+ * Factory that creates the CodeSearch tool with an injected search provider.
25
+ * If no provider is given, returns a tool that explains no provider is configured.
26
+ */
27
+ export function createCodeSearchTool(searchProvider?: CodeSearchProvider) {
28
+ return tool({
29
+ description:
30
+ "Search for code examples, API documentation, and library patterns online. Use this to find usage examples, best practices, and documentation for APIs and libraries.",
31
+ inputSchema: z.object({
32
+ query: z
33
+ .string()
34
+ .describe(
35
+ "Search query about APIs, libraries, or coding patterns (e.g. 'zod v4 migration', 'express middleware error handling')"
36
+ ),
37
+ }),
38
+ execute: async ({ query }) => {
39
+ if (!searchProvider) {
40
+ return "CodeSearch provider not configured. The consuming application must provide an onCodeSearch callback in AiAgentOptions to enable code search.";
41
+ }
42
+
43
+ try {
44
+ const results = await searchProvider(query);
45
+
46
+ if (results.length === 0) {
47
+ return `No code examples found for: "${query}"`;
48
+ }
49
+
50
+ let output = `# Code search results for: "${query}"\n\n`;
51
+
52
+ for (let i = 0; i < results.length; i++) {
53
+ const r = results[i];
54
+ output += `## ${i + 1}. ${r.title}\n`;
55
+ output += `**Source:** ${r.url}\n\n`;
56
+ output += `${r.content}\n\n`;
57
+ }
58
+
59
+ if (output.length > MAX_OUTPUT) {
60
+ output = output.slice(0, MAX_OUTPUT) + "\n...[truncated at 50KB]";
61
+ }
62
+
63
+ return output.trim();
64
+ } catch (err: any) {
65
+ return `Error searching code: ${err.message}`;
66
+ }
67
+ },
68
+ });
69
+ }
@@ -0,0 +1,93 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { exec } from "node:child_process";
4
+
5
+ const MAX_OUTPUT = 50_000;
6
+ const DEFAULT_COMMAND = "npx tsc --noEmit";
7
+ const DEFAULT_TIMEOUT = 120_000;
8
+
9
+ export const diagnosticsTool = tool({
10
+ description:
11
+ "Runs a type-checking or linting command and returns structured diagnostics. " +
12
+ "Defaults to `npx tsc --noEmit`. Optionally filters errors to a specific file.",
13
+ inputSchema: z.object({
14
+ command: z
15
+ .string()
16
+ .optional()
17
+ .describe(
18
+ 'The diagnostic command to run (default: "npx tsc --noEmit"). Examples: "npx eslint src/", "npx tsc --noEmit"'
19
+ ),
20
+ file_path: z
21
+ .string()
22
+ .optional()
23
+ .describe(
24
+ "Filter errors to this file path only. When set, only lines mentioning this path are returned."
25
+ ),
26
+ }),
27
+ execute: async ({ command, file_path }) => {
28
+ const cmd = command ?? DEFAULT_COMMAND;
29
+
30
+ const raw = await new Promise<{ stdout: string; stderr: string; exitCode: number }>(
31
+ (resolve) => {
32
+ exec(
33
+ cmd,
34
+ { timeout: DEFAULT_TIMEOUT, maxBuffer: 10 * 1024 * 1024 },
35
+ (err, stdout, stderr) => {
36
+ resolve({
37
+ stdout: stdout ?? "",
38
+ stderr: stderr ?? "",
39
+ exitCode: err ? (err as NodeJS.ErrnoException & { code?: number }).code ?? 1 : 0,
40
+ });
41
+ }
42
+ );
43
+ }
44
+ );
45
+
46
+ // Combine stdout + stderr (tsc writes to stdout, eslint to stdout, some tools to stderr)
47
+ let combined = raw.stdout;
48
+ if (raw.stderr) {
49
+ combined += (combined ? "\n" : "") + raw.stderr;
50
+ }
51
+
52
+ // If the command succeeded with no output, report clean
53
+ if (raw.exitCode === 0 && !combined.trim()) {
54
+ return "Nenhum erro encontrado.";
55
+ }
56
+
57
+ // If there's output but exit code 0, it may be warnings — still return them
58
+ let lines = combined.split("\n");
59
+
60
+ // Filter to specific file if requested
61
+ if (file_path) {
62
+ // Normalize path separators for matching
63
+ const normalized = file_path.replace(/\\/g, "/");
64
+ lines = lines.filter((line) => {
65
+ const normalizedLine = line.replace(/\\/g, "/");
66
+ return normalizedLine.includes(normalized);
67
+ });
68
+
69
+ if (lines.length === 0) {
70
+ return `Nenhum erro encontrado para ${file_path}.`;
71
+ }
72
+ }
73
+
74
+ let output = lines.join("\n").trim();
75
+
76
+ // If exit code 0 and we have output, it passed (possibly with warnings)
77
+ if (raw.exitCode === 0 && !output) {
78
+ return "Nenhum erro encontrado.";
79
+ }
80
+
81
+ if (!output) {
82
+ return raw.exitCode === 0
83
+ ? "Nenhum erro encontrado."
84
+ : `Comando falhou com código ${raw.exitCode} mas sem saída de erros.`;
85
+ }
86
+
87
+ if (output.length > MAX_OUTPUT) {
88
+ output = output.slice(0, MAX_OUTPUT) + "\n...[truncated]";
89
+ }
90
+
91
+ return output;
92
+ },
93
+ });
@@ -0,0 +1,105 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import {
4
+ DisplayMetricSchema,
5
+ DisplayChartSchema,
6
+ DisplayTableSchema,
7
+ DisplayProgressSchema,
8
+ DisplayProductSchema,
9
+ DisplayComparisonSchema,
10
+ DisplayPriceSchema,
11
+ DisplayImageSchema,
12
+ DisplayGallerySchema,
13
+ DisplayCarouselSchema,
14
+ DisplaySourcesSchema,
15
+ DisplayLinkSchema,
16
+ DisplayMapSchema,
17
+ DisplayFileSchema,
18
+ DisplayCodeSchema,
19
+ DisplaySpreadsheetSchema,
20
+ DisplayStepsSchema,
21
+ DisplayAlertSchema,
22
+ DisplayChoicesSchema,
23
+ } from "../display-schemas.js";
24
+
25
+ // display_highlight: metric, price, alert, choices
26
+ const highlightSchema = z.discriminatedUnion("action", [
27
+ z.object({ action: z.literal("metric"), ...DisplayMetricSchema.shape }),
28
+ z.object({ action: z.literal("price"), ...DisplayPriceSchema.shape }),
29
+ z.object({ action: z.literal("alert"), ...DisplayAlertSchema.shape }),
30
+ z.object({ action: z.literal("choices"), ...DisplayChoicesSchema.shape }),
31
+ ]);
32
+
33
+ // display_collection: table, spreadsheet, comparison, carousel, gallery, sources
34
+ const collectionSchema = z.discriminatedUnion("action", [
35
+ z.object({ action: z.literal("table"), ...DisplayTableSchema.shape }),
36
+ z.object({ action: z.literal("spreadsheet"), ...DisplaySpreadsheetSchema.shape }),
37
+ z.object({ action: z.literal("comparison"), ...DisplayComparisonSchema.shape }),
38
+ z.object({ action: z.literal("carousel"), ...DisplayCarouselSchema.shape }),
39
+ z.object({ action: z.literal("gallery"), ...DisplayGallerySchema.shape }),
40
+ z.object({ action: z.literal("sources"), ...DisplaySourcesSchema.shape }),
41
+ ]);
42
+
43
+ // display_card: product, link, file, image
44
+ const cardSchema = z.discriminatedUnion("action", [
45
+ z.object({ action: z.literal("product"), ...DisplayProductSchema.shape }),
46
+ z.object({ action: z.literal("link"), ...DisplayLinkSchema.shape }),
47
+ z.object({ action: z.literal("file"), ...DisplayFileSchema.shape }),
48
+ z.object({ action: z.literal("image"), ...DisplayImageSchema.shape }),
49
+ ]);
50
+
51
+ // display_visual: chart, map, code, progress, steps
52
+ const visualSchema = z.discriminatedUnion("action", [
53
+ z.object({ action: z.literal("chart"), ...DisplayChartSchema.shape }),
54
+ z.object({ action: z.literal("map"), ...DisplayMapSchema.shape }),
55
+ z.object({ action: z.literal("code"), ...DisplayCodeSchema.shape }),
56
+ z.object({ action: z.literal("progress"), ...DisplayProgressSchema.shape }),
57
+ z.object({ action: z.literal("steps"), ...DisplayStepsSchema.shape }),
58
+ ]);
59
+
60
+ export function createDisplayTools() {
61
+ return {
62
+ display_highlight: tool({
63
+ description: [
64
+ "Destaca informacao importante na resposta.",
65
+ "Actions: metric (KPI com valor e tendencia), price (preco em destaque),",
66
+ "alert (banner info/warning/error/success), choices (opcoes clicaveis para o usuario).",
67
+ ].join(" "),
68
+ inputSchema: highlightSchema,
69
+ execute: async (args) => ({ ...args, _display: true }),
70
+ }),
71
+
72
+ display_collection: tool({
73
+ description: [
74
+ "Apresenta colecao de itens organizados.",
75
+ "Actions: table (tabela rica com colunas tipadas), spreadsheet (planilha exportavel),",
76
+ "comparison (itens lado a lado), carousel (cards horizontais navegaveis),",
77
+ "gallery (grid de imagens), sources (lista de fontes consultadas).",
78
+ ].join(" "),
79
+ inputSchema: collectionSchema,
80
+ execute: async (args) => ({ ...args, _display: true }),
81
+ }),
82
+
83
+ display_card: tool({
84
+ description: [
85
+ "Apresenta item individual com detalhes visuais.",
86
+ "Actions: product (card com imagem, preco, rating, badges),",
87
+ "link (preview de URL com OG image), file (card de arquivo para download),",
88
+ "image (imagem unica com caption e zoom).",
89
+ ].join(" "),
90
+ inputSchema: cardSchema,
91
+ execute: async (args) => ({ ...args, _display: true }),
92
+ }),
93
+
94
+ display_visual: tool({
95
+ description: [
96
+ "Visualizacao especializada de dados ou fluxos.",
97
+ "Actions: chart (grafico bar/line/pie/area/donut), map (mapa com pins),",
98
+ "code (bloco com syntax highlighting), progress (barra de progresso com etapas),",
99
+ "steps (timeline/checklist de etapas).",
100
+ ].join(" "),
101
+ inputSchema: visualSchema,
102
+ execute: async (args) => ({ ...args, _display: true }),
103
+ }),
104
+ };
105
+ }
@@ -0,0 +1,55 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+
5
+ export function createEditTool(opts?: { autoApprove?: boolean }) {
6
+ const baseTool = tool({
7
+ description:
8
+ "Performs exact string replacement in a file. The old_string must be unique in the file unless replace_all is true.",
9
+ inputSchema: z.object({
10
+ file_path: z.string().describe("Absolute path to the file to edit"),
11
+ old_string: z.string().describe("The exact text to find and replace"),
12
+ new_string: z.string().describe("The replacement text"),
13
+ replace_all: z
14
+ .boolean()
15
+ .optional()
16
+ .default(false)
17
+ .describe("Replace all occurrences instead of just the first"),
18
+ }),
19
+ execute: async ({ file_path, old_string, new_string, replace_all }) => {
20
+ try {
21
+ const content = await readFile(file_path, "utf-8");
22
+
23
+ if (!content.includes(old_string)) {
24
+ return `Error: old_string not found in ${file_path}`;
25
+ }
26
+
27
+ if (!replace_all) {
28
+ const count = content.split(old_string).length - 1;
29
+ if (count > 1) {
30
+ return `Error: old_string appears ${count} times in the file. Provide more context to make it unique, or set replace_all to true.`;
31
+ }
32
+ }
33
+
34
+ const updated = replace_all
35
+ ? content.replaceAll(old_string, new_string)
36
+ : content.replace(old_string, new_string);
37
+
38
+ await writeFile(file_path, updated, "utf-8");
39
+ return `File edited successfully: ${file_path}`;
40
+ } catch (err: any) {
41
+ return `Error editing file: ${err.message}`;
42
+ }
43
+ },
44
+ });
45
+
46
+ if (opts?.autoApprove === false) {
47
+ return Object.assign(baseTool, {
48
+ needsApproval: async () => true as const,
49
+ });
50
+ }
51
+
52
+ return baseTool;
53
+ }
54
+
55
+ export const editTool = createEditTool();
@@ -0,0 +1,46 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import fg from "fast-glob";
4
+ import { stat } from "node:fs/promises";
5
+
6
+ export const globTool = tool({
7
+ description:
8
+ "Fast file pattern matching. Supports glob patterns like '**/*.ts'. Returns matching file paths sorted by modification time.",
9
+ inputSchema: z.object({
10
+ pattern: z.string().describe("Glob pattern to match files against"),
11
+ path: z
12
+ .string()
13
+ .optional()
14
+ .describe("Directory to search in. Defaults to cwd."),
15
+ }),
16
+ execute: async ({ pattern, path }) => {
17
+ try {
18
+ const cwd = path ?? process.cwd();
19
+ const files = await fg(pattern, {
20
+ cwd,
21
+ absolute: true,
22
+ dot: false,
23
+ onlyFiles: true,
24
+ });
25
+
26
+ // Sort by mtime (most recent first)
27
+ const withStats = await Promise.all(
28
+ files.map(async (f) => {
29
+ try {
30
+ const s = await stat(f);
31
+ return { file: f, mtime: s.mtimeMs };
32
+ } catch {
33
+ return { file: f, mtime: 0 };
34
+ }
35
+ })
36
+ );
37
+
38
+ withStats.sort((a, b) => b.mtime - a.mtime);
39
+
40
+ if (withStats.length === 0) return "No files matched the pattern.";
41
+ return withStats.map((w) => w.file).join("\n");
42
+ } catch (err: any) {
43
+ return `Error: ${err.message}`;
44
+ }
45
+ },
46
+ });
@@ -0,0 +1,68 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { execFile } from "node:child_process";
4
+
5
+ const MAX_OUTPUT = 30_000;
6
+
7
+ export const grepTool = tool({
8
+ description:
9
+ "Searches file contents using ripgrep (rg) with regex support. Falls back to grep if rg is unavailable.",
10
+ inputSchema: z.object({
11
+ pattern: z.string().describe("Regex pattern to search for"),
12
+ path: z
13
+ .string()
14
+ .optional()
15
+ .describe("File or directory to search in. Defaults to cwd."),
16
+ glob: z
17
+ .string()
18
+ .optional()
19
+ .describe("Glob pattern to filter files (e.g. '*.ts')"),
20
+ output_mode: z
21
+ .enum(["content", "files_with_matches", "count"])
22
+ .optional()
23
+ .default("files_with_matches")
24
+ .describe("Output mode"),
25
+ }),
26
+ execute: async ({ pattern, path, glob: globFilter, output_mode }) => {
27
+ const searchPath = path ?? ".";
28
+
29
+ // Build rg args array (no shell quoting needed with execFile)
30
+ const args: string[] = [];
31
+
32
+ if (output_mode === "files_with_matches") args.push("-l");
33
+ else if (output_mode === "count") args.push("-c");
34
+ else args.push("-n"); // content mode: show line numbers
35
+
36
+ if (globFilter) args.push("--glob", globFilter);
37
+
38
+ args.push(pattern, searchPath);
39
+
40
+ return new Promise<string>((resolve) => {
41
+ execFile("rg", args, { timeout: 30_000, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
42
+ if (stdout) {
43
+ const output = stdout.length > MAX_OUTPUT
44
+ ? stdout.slice(0, MAX_OUTPUT) + "\n...[truncated]"
45
+ : stdout;
46
+ resolve(output.trim());
47
+ } else if (err) {
48
+ // rg returns exit code 1 for no matches
49
+ if ((err as any).code === 1) {
50
+ resolve("No matches found.");
51
+ } else {
52
+ // Try grep fallback
53
+ const grepArgs = ["-rn"];
54
+ if (globFilter) grepArgs.push(`--include=${globFilter}`);
55
+ grepArgs.push(pattern, searchPath);
56
+
57
+ execFile("grep", grepArgs, { timeout: 30_000 }, (gErr, gOut) => {
58
+ if (gOut) resolve(gOut.trim());
59
+ else resolve("No matches found.");
60
+ });
61
+ }
62
+ } else {
63
+ resolve("No matches found.");
64
+ }
65
+ });
66
+ });
67
+ },
68
+ });
@@ -0,0 +1,103 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ const MAX_RESPONSE_SIZE = 50 * 1024; // 50KB
5
+ const MAX_TIMEOUT = 60_000;
6
+
7
+ export const httpRequestTool = tool({
8
+ description:
9
+ "Sends an HTTP request to a URL. Supports all methods, custom headers, and JSON/text bodies. Use for REST API interactions.",
10
+ inputSchema: z.object({
11
+ method: z
12
+ .enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"])
13
+ .describe("HTTP method"),
14
+ url: z.string().url().describe("Target URL"),
15
+ headers: z
16
+ .record(z.string())
17
+ .optional()
18
+ .describe("Custom headers (e.g. Authorization, Content-Type)"),
19
+ body: z
20
+ .string()
21
+ .optional()
22
+ .describe("Request body (JSON string or plain text)"),
23
+ timeout: z
24
+ .number()
25
+ .optional()
26
+ .default(30_000)
27
+ .describe("Timeout in ms (max 60000)"),
28
+ }),
29
+ execute: async ({ method, url, headers, body, timeout }) => {
30
+ const effectiveTimeout = Math.min(timeout ?? 30_000, MAX_TIMEOUT);
31
+ const controller = new AbortController();
32
+ const timer = setTimeout(() => controller.abort(), effectiveTimeout);
33
+
34
+ try {
35
+ const fetchHeaders: Record<string, string> = { ...headers };
36
+
37
+ // Auto-set Content-Type for JSON bodies if not specified
38
+ if (body && !fetchHeaders["Content-Type"] && !fetchHeaders["content-type"]) {
39
+ try {
40
+ JSON.parse(body);
41
+ fetchHeaders["Content-Type"] = "application/json";
42
+ } catch {
43
+ // Not JSON — leave Content-Type unset
44
+ }
45
+ }
46
+
47
+ const response = await fetch(url, {
48
+ method,
49
+ headers: fetchHeaders,
50
+ body: method !== "GET" && method !== "HEAD" ? body : undefined,
51
+ signal: controller.signal,
52
+ });
53
+
54
+ // Collect response headers (subset)
55
+ const responseHeaders: Record<string, string> = {};
56
+ for (const key of ["content-type", "location", "x-request-id", "retry-after"]) {
57
+ const val = response.headers.get(key);
58
+ if (val) responseHeaders[key] = val;
59
+ }
60
+
61
+ // Read response body
62
+ const contentType = response.headers.get("content-type") ?? "";
63
+ let responseBody: unknown;
64
+
65
+ if (method === "HEAD") {
66
+ responseBody = null;
67
+ } else {
68
+ const text = await response.text();
69
+ const truncated = text.length > MAX_RESPONSE_SIZE;
70
+ const bodyText = truncated ? text.slice(0, MAX_RESPONSE_SIZE) : text;
71
+
72
+ // Try JSON parse if content-type indicates JSON
73
+ if (contentType.includes("json")) {
74
+ try {
75
+ responseBody = JSON.parse(bodyText);
76
+ } catch {
77
+ responseBody = bodyText;
78
+ }
79
+ } else {
80
+ responseBody = bodyText;
81
+ }
82
+
83
+ if (truncated) {
84
+ responseHeaders["x-truncated"] = `true (${text.length} bytes, showing first ${MAX_RESPONSE_SIZE})`;
85
+ }
86
+ }
87
+
88
+ return JSON.stringify({
89
+ status: response.status,
90
+ statusText: response.statusText,
91
+ headers: responseHeaders,
92
+ body: responseBody,
93
+ });
94
+ } catch (err: any) {
95
+ if (err.name === "AbortError") {
96
+ return `Error: Request timed out after ${effectiveTimeout}ms`;
97
+ }
98
+ return `Error: ${err.message}`;
99
+ } finally {
100
+ clearTimeout(timer);
101
+ }
102
+ },
103
+ });