@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,48 @@
1
+ import { readTool } from "./read.js";
2
+ import { writeTool, createWriteTool } from "./write.js";
3
+ import { editTool, createEditTool } from "./edit.js";
4
+ import { bashTool, createBashTool } from "./bash.js";
5
+ import { globTool } from "./glob.js";
6
+ import { grepTool } from "./grep.js";
7
+ import { listDirTool } from "./list-dir.js";
8
+ import { multiEditTool, createMultiEditTool } from "./multi-edit.js";
9
+ import { todoWriteTool, todoReadTool } from "./todo.js";
10
+ import { diagnosticsTool } from "./diagnostics.js";
11
+ import { createAskUserTool } from "./ask-user.js";
12
+ import { webFetchTool } from "./web-fetch.js";
13
+ import { createWebSearchTool } from "./web-search.js";
14
+ import { createTaskTool } from "./task.js";
15
+ import { createBatchTool } from "./batch.js";
16
+ import { applyPatchTool, createApplyPatchTool } from "./apply-patch.js";
17
+ import { createCodeSearchTool } from "./code-search.js";
18
+ import { httpRequestTool } from "./http-request.js";
19
+ import { apiSpecTool } from "./api-spec.js";
20
+
21
+ export { createBashTool } from "./bash.js";
22
+ export { createWriteTool } from "./write.js";
23
+ export { createEditTool } from "./edit.js";
24
+ export { createMultiEditTool } from "./multi-edit.js";
25
+ export { createApplyPatchTool } from "./apply-patch.js";
26
+
27
+ export const codingTools = {
28
+ Read: readTool,
29
+ Write: writeTool,
30
+ Edit: editTool,
31
+ Bash: bashTool,
32
+ Glob: globTool,
33
+ Grep: grepTool,
34
+ ListDir: listDirTool,
35
+ MultiEdit: multiEditTool,
36
+ TodoWrite: todoWriteTool,
37
+ TodoRead: todoReadTool,
38
+ Diagnostics: diagnosticsTool,
39
+ AskUser: createAskUserTool(),
40
+ WebFetch: webFetchTool,
41
+ WebSearch: createWebSearchTool(),
42
+ Task: createTaskTool(),
43
+ Batch: createBatchTool({}),
44
+ ApplyPatch: applyPatchTool,
45
+ CodeSearch: createCodeSearchTool(),
46
+ HttpRequest: httpRequestTool,
47
+ ApiSpec: apiSpecTool,
48
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Vercel AI SDK tool wrappers for job management.
3
+ * Uses callbacks to avoid coupling to a specific job engine implementation.
4
+ */
5
+ import { tool } from "ai";
6
+ import { z } from "zod";
7
+
8
+ export interface JobToolCallbacks {
9
+ submitJob: (opts: {
10
+ agentId: string;
11
+ command: string;
12
+ timeout?: number;
13
+ }) => unknown;
14
+ listJobs: (agentId?: string) => unknown[];
15
+ getJob: (jobId: string) => unknown | undefined;
16
+ killJob: (jobId: string) => boolean;
17
+ getAgentId: () => string | undefined;
18
+ }
19
+
20
+ export function createJobTools(callbacks: JobToolCallbacks) {
21
+ return {
22
+ submit_job: tool({
23
+ description:
24
+ "Submit a long-running process for backbone supervision. The backbone captures output, applies timeout, and wakes you up when the job finishes.",
25
+ inputSchema: z.object({
26
+ command: z
27
+ .string()
28
+ .describe("Shell command to execute (e.g. 'sleep 30')"),
29
+ timeout: z
30
+ .number()
31
+ .optional()
32
+ .describe("Timeout in seconds. Default: 1800 (30min)"),
33
+ }),
34
+ execute: async ({ command, timeout }) => {
35
+ const agentId = callbacks.getAgentId();
36
+ if (!agentId) return "Error: AGENT_ID not available";
37
+ try {
38
+ const summary = callbacks.submitJob({
39
+ agentId,
40
+ command,
41
+ timeout,
42
+ });
43
+ return JSON.stringify(summary);
44
+ } catch (err) {
45
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
46
+ }
47
+ },
48
+ }),
49
+
50
+ list_jobs: tool({
51
+ description:
52
+ "List all jobs for this agent. Returns an array of job summaries.",
53
+ inputSchema: z.object({}),
54
+ execute: async () => {
55
+ const agentId = callbacks.getAgentId();
56
+ return JSON.stringify(callbacks.listJobs(agentId ?? undefined));
57
+ },
58
+ }),
59
+
60
+ get_job: tool({
61
+ description:
62
+ "Get full details of a specific job including output tail, status, exit code, and duration.",
63
+ inputSchema: z.object({
64
+ jobId: z.string().describe("Job ID to query"),
65
+ }),
66
+ execute: async ({ jobId }) => {
67
+ const job = callbacks.getJob(jobId);
68
+ return job ? JSON.stringify(job) : "Job not found";
69
+ },
70
+ }),
71
+
72
+ kill_job: tool({
73
+ description: "Kill a running job with SIGKILL.",
74
+ inputSchema: z.object({
75
+ jobId: z.string().describe("Job ID to kill"),
76
+ }),
77
+ execute: async ({ jobId }) => {
78
+ return callbacks.killJob(jobId)
79
+ ? "Job killed successfully"
80
+ : "Job not found or already finished";
81
+ },
82
+ }),
83
+ };
84
+ }
@@ -0,0 +1,102 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { readdir, stat } from "node:fs/promises";
4
+ import { join, basename } from "node:path";
5
+
6
+ const DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build"];
7
+
8
+ async function buildTree(
9
+ dirPath: string,
10
+ depth: number,
11
+ maxDepth: number,
12
+ ignore: string[],
13
+ prefix: string
14
+ ): Promise<string[]> {
15
+ if (depth >= maxDepth) return [];
16
+
17
+ let entries;
18
+ try {
19
+ entries = await readdir(dirPath, { withFileTypes: true });
20
+ } catch {
21
+ return [];
22
+ }
23
+
24
+ // Filter ignored entries
25
+ entries = entries.filter((e) => !ignore.includes(e.name));
26
+
27
+ // Sort: directories first, then files, alphabetically within each group
28
+ entries.sort((a, b) => {
29
+ if (a.isDirectory() && !b.isDirectory()) return -1;
30
+ if (!a.isDirectory() && b.isDirectory()) return 1;
31
+ return a.name.localeCompare(b.name);
32
+ });
33
+
34
+ const lines: string[] = [];
35
+
36
+ for (let i = 0; i < entries.length; i++) {
37
+ const entry = entries[i];
38
+ const isLast = i === entries.length - 1;
39
+ const connector = isLast ? "└── " : "├── ";
40
+ const childPrefix = isLast ? " " : "│ ";
41
+
42
+ if (entry.isDirectory()) {
43
+ lines.push(`${prefix}${connector}${entry.name}/`);
44
+ const children = await buildTree(
45
+ join(dirPath, entry.name),
46
+ depth + 1,
47
+ maxDepth,
48
+ ignore,
49
+ prefix + childPrefix
50
+ );
51
+ lines.push(...children);
52
+ } else {
53
+ lines.push(`${prefix}${connector}${entry.name}`);
54
+ }
55
+ }
56
+
57
+ return lines;
58
+ }
59
+
60
+ export const listDirTool = tool({
61
+ description:
62
+ "Lists files and directories as an indented tree. Use this to understand project structure.",
63
+ inputSchema: z.object({
64
+ path: z.string().describe("Absolute path of the directory to list"),
65
+ depth: z
66
+ .number()
67
+ .optional()
68
+ .default(3)
69
+ .describe("Maximum depth to traverse (default: 3)"),
70
+ ignore: z
71
+ .array(z.string())
72
+ .optional()
73
+ .default(DEFAULT_IGNORE)
74
+ .describe(
75
+ "Directory/file names to ignore (default: node_modules, .git, dist, build)"
76
+ ),
77
+ }),
78
+ execute: async ({ path, depth, ignore }) => {
79
+ try {
80
+ const s = await stat(path);
81
+ if (!s.isDirectory()) {
82
+ return `Error: ${path} is not a directory`;
83
+ }
84
+
85
+ const rootName = basename(path) || path;
86
+ const lines = [`${rootName}/`];
87
+ const children = await buildTree(path, 0, depth, ignore, "");
88
+ lines.push(...children);
89
+
90
+ const result = lines.join("\n");
91
+
92
+ // Respect 50KB output limit
93
+ if (result.length > 50_000) {
94
+ return result.slice(0, 50_000) + "\n... (truncated at 50KB)";
95
+ }
96
+
97
+ return result;
98
+ } catch (err: any) {
99
+ return `Error: ${err.message}`;
100
+ }
101
+ },
102
+ });
@@ -0,0 +1,65 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+
5
+ export function createMultiEditTool(opts?: { autoApprove?: boolean }) {
6
+ const baseTool = tool({
7
+ description:
8
+ "Applies multiple string replacements to a single file atomically. If any edit fails (old_string not found), none are applied.",
9
+ inputSchema: z.object({
10
+ file_path: z.string().describe("Absolute path to the file to edit"),
11
+ edits: z
12
+ .array(
13
+ z.object({
14
+ old_string: z.string().describe("The exact text to find"),
15
+ new_string: z.string().describe("The replacement text"),
16
+ replace_all: z
17
+ .boolean()
18
+ .optional()
19
+ .default(false)
20
+ .describe("Replace all occurrences instead of just the first"),
21
+ })
22
+ )
23
+ .min(1)
24
+ .describe("Array of edits to apply sequentially"),
25
+ }),
26
+ execute: async ({ file_path, edits }) => {
27
+ try {
28
+ let content = await readFile(file_path, "utf-8");
29
+
30
+ // Validation pass: check all old_strings exist before applying any edit
31
+ for (let i = 0; i < edits.length; i++) {
32
+ const edit = edits[i];
33
+ if (!content.includes(edit.old_string)) {
34
+ return `Error: edit[${i}] old_string not found in ${file_path}. No edits applied (atomic rollback).`;
35
+ }
36
+ if (!edit.replace_all) {
37
+ const count = content.split(edit.old_string).length - 1;
38
+ if (count > 1) {
39
+ return `Error: edit[${i}] old_string appears ${count} times. Provide more context or set replace_all to true. No edits applied.`;
40
+ }
41
+ }
42
+ // Apply the edit to the working content so subsequent edits see the result
43
+ content = edit.replace_all
44
+ ? content.replaceAll(edit.old_string, edit.new_string)
45
+ : content.replace(edit.old_string, edit.new_string);
46
+ }
47
+
48
+ await writeFile(file_path, content, "utf-8");
49
+ return `${edits.length} edit(s) applied successfully to ${file_path}`;
50
+ } catch (err: any) {
51
+ return `Error editing file: ${err.message}`;
52
+ }
53
+ },
54
+ });
55
+
56
+ if (opts?.autoApprove === false) {
57
+ return Object.assign(baseTool, {
58
+ needsApproval: async () => true as const,
59
+ });
60
+ }
61
+
62
+ return baseTool;
63
+ }
64
+
65
+ export const multiEditTool = createMultiEditTool();
@@ -0,0 +1,40 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { readFile } from "node:fs/promises";
4
+
5
+ export const readTool = tool({
6
+ description:
7
+ "Reads a file from the local filesystem. Returns content with line numbers (cat -n format).",
8
+ inputSchema: z.object({
9
+ file_path: z.string().describe("Absolute path to the file to read"),
10
+ offset: z
11
+ .number()
12
+ .optional()
13
+ .describe("Line number to start reading from (1-based)"),
14
+ limit: z
15
+ .number()
16
+ .optional()
17
+ .describe("Maximum number of lines to read"),
18
+ }),
19
+ execute: async ({ file_path, offset, limit }) => {
20
+ try {
21
+ const content = await readFile(file_path, "utf-8");
22
+ let lines = content.split("\n");
23
+
24
+ const start = offset ? offset - 1 : 0;
25
+ const end = limit ? start + limit : lines.length;
26
+ lines = lines.slice(start, end);
27
+
28
+ const maxLineNum = start + lines.length;
29
+ const pad = String(maxLineNum).length;
30
+
31
+ const numbered = lines.map(
32
+ (line, i) =>
33
+ `${String(start + i + 1).padStart(pad)}\t${line}`
34
+ );
35
+ return numbered.join("\n");
36
+ } catch (err: any) {
37
+ return `Error reading file: ${err.message}`;
38
+ }
39
+ },
40
+ });
@@ -0,0 +1,71 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ const MAX_OUTPUT = 50_000;
5
+
6
+ /**
7
+ * Configuration for spawning a sub-agent via the Task tool.
8
+ * Inherits model/apiKey from the parent agent.
9
+ */
10
+ export interface TaskConfig {
11
+ model: string;
12
+ apiKey: string;
13
+ /** Maximum steps the sub-agent can take (default: 10) */
14
+ maxSubSteps?: number;
15
+ }
16
+
17
+ /**
18
+ * Factory that creates the Task tool for launching sub-agents.
19
+ *
20
+ * If no config is provided, returns a tool that explains it's not configured.
21
+ * When configured, uses runAiAgent() internally to spawn an isolated sub-agent
22
+ * that inherits the parent's model and apiKey but has no access to conversation history.
23
+ */
24
+ export function createTaskTool(config?: TaskConfig) {
25
+ return tool({
26
+ description:
27
+ "Launch a sub-agent to handle a task autonomously. The sub-agent receives the same coding tools but runs in an isolated session without access to the parent's conversation history. Use this for tasks that can be completed independently.",
28
+ inputSchema: z.object({
29
+ description: z
30
+ .string()
31
+ .describe("Short description of the task (3-5 words)"),
32
+ prompt: z
33
+ .string()
34
+ .describe("Detailed instructions for the sub-agent to execute"),
35
+ }),
36
+ execute: async ({ description, prompt }) => {
37
+ if (!config) {
38
+ return "Task tool not configured. The consuming application must provide model and apiKey configuration to enable sub-agent spawning.";
39
+ }
40
+
41
+ try {
42
+ // Dynamic import to avoid circular dependency at module load time
43
+ const { runAiAgent } = await import("../agent.js");
44
+
45
+ let resultText = "";
46
+
47
+ const generator = runAiAgent(prompt, {
48
+ model: config.model,
49
+ apiKey: config.apiKey,
50
+ maxSteps: config.maxSubSteps ?? 10,
51
+ });
52
+
53
+ for await (const event of generator) {
54
+ if (event.type === "result") {
55
+ resultText = event.content;
56
+ }
57
+ }
58
+
59
+ let output = `## Sub-agent result for: "${description}"\n\n${resultText}`;
60
+
61
+ if (output.length > MAX_OUTPUT) {
62
+ output = output.slice(0, MAX_OUTPUT) + "\n...[truncated at 50KB]";
63
+ }
64
+
65
+ return output;
66
+ } catch (err: any) {
67
+ return `Error running sub-agent "${description}": ${err.message}`;
68
+ }
69
+ },
70
+ });
71
+ }
@@ -0,0 +1,82 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import type { AiTodoItem } from "../types.js";
4
+
5
+ // Module-level state: session todos keyed by a simple singleton
6
+ // (one agent run = one process = one list)
7
+ let currentTodos: AiTodoItem[] = [];
8
+
9
+ /** Reset state — useful for testing or when starting a new session */
10
+ export function resetTodoState(): void {
11
+ currentTodos = [];
12
+ }
13
+
14
+ /** Read current todos — used internally by TodoRead and available for consumers */
15
+ export function getTodos(): AiTodoItem[] {
16
+ return [...currentTodos];
17
+ }
18
+
19
+ const todoItemSchema = z.object({
20
+ id: z.string().describe("Unique identifier for the todo item"),
21
+ content: z.string().describe("Description of the task"),
22
+ status: z
23
+ .enum(["pending", "in_progress", "completed"])
24
+ .describe("Current status of the task"),
25
+ priority: z
26
+ .enum(["high", "medium", "low"])
27
+ .describe("Priority level of the task"),
28
+ });
29
+
30
+ export const todoWriteTool = tool({
31
+ description:
32
+ "Creates or updates the task list for the current session. Replaces the entire list with the provided todos. Use this to track progress on multi-step tasks.",
33
+ inputSchema: z.object({
34
+ todos: z
35
+ .array(todoItemSchema)
36
+ .min(1)
37
+ .describe("The complete list of todos (replaces any existing list)"),
38
+ }),
39
+ execute: async ({ todos }) => {
40
+ currentTodos = todos as AiTodoItem[];
41
+
42
+ const pending = currentTodos.filter((t) => t.status === "pending").length;
43
+ const inProgress = currentTodos.filter(
44
+ (t) => t.status === "in_progress"
45
+ ).length;
46
+ const completed = currentTodos.filter(
47
+ (t) => t.status === "completed"
48
+ ).length;
49
+
50
+ return `Todo list updated: ${currentTodos.length} items (${completed} completed, ${inProgress} in progress, ${pending} pending)`;
51
+ },
52
+ });
53
+
54
+ export const todoReadTool = tool({
55
+ description:
56
+ "Reads the current task list for the session. Returns all todos with their status and priority.",
57
+ inputSchema: z.object({}),
58
+ execute: async () => {
59
+ if (currentTodos.length === 0) {
60
+ return "Nenhuma tarefa registrada.";
61
+ }
62
+
63
+ const statusIcon: Record<string, string> = {
64
+ pending: "○",
65
+ in_progress: "◐",
66
+ completed: "●",
67
+ };
68
+
69
+ const priorityTag: Record<string, string> = {
70
+ high: "[HIGH]",
71
+ medium: "[MED]",
72
+ low: "[LOW]",
73
+ };
74
+
75
+ const lines = currentTodos.map(
76
+ (t) =>
77
+ `${statusIcon[t.status]} ${priorityTag[t.priority]} ${t.id}: ${t.content} (${t.status})`
78
+ );
79
+
80
+ return lines.join("\n");
81
+ },
82
+ });
@@ -0,0 +1,155 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ const MAX_OUTPUT = 50_000;
5
+ const FETCH_TIMEOUT = 30_000;
6
+
7
+ /**
8
+ * Converts HTML to a simplified markdown representation.
9
+ * Handles common elements: headings, links, lists, paragraphs, code blocks, bold, italic.
10
+ */
11
+ function htmlToMarkdown(html: string): string {
12
+ let text = html;
13
+
14
+ // Remove script, style, nav, footer, header tags and their content
15
+ text = text.replace(/<(script|style|nav|footer|header|aside|svg|noscript)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
16
+
17
+ // Remove HTML comments
18
+ text = text.replace(/<!--[\s\S]*?-->/g, "");
19
+
20
+ // Headings
21
+ text = text.replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n");
22
+ text = text.replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n");
23
+ text = text.replace(/<h3\b[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n");
24
+ text = text.replace(/<h4\b[^>]*>([\s\S]*?)<\/h4>/gi, "\n#### $1\n");
25
+ text = text.replace(/<h5\b[^>]*>([\s\S]*?)<\/h5>/gi, "\n##### $1\n");
26
+ text = text.replace(/<h6\b[^>]*>([\s\S]*?)<\/h6>/gi, "\n###### $1\n");
27
+
28
+ // Code blocks (pre > code)
29
+ text = text.replace(/<pre\b[^>]*>\s*<code\b[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, "\n```\n$1\n```\n");
30
+ text = text.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre>/gi, "\n```\n$1\n```\n");
31
+
32
+ // Inline code
33
+ text = text.replace(/<code\b[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
34
+
35
+ // Bold
36
+ text = text.replace(/<(strong|b)\b[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
37
+
38
+ // Italic
39
+ text = text.replace(/<(em|i)\b[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
40
+
41
+ // Links
42
+ text = text.replace(/<a\b[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
43
+
44
+ // Images
45
+ text = text.replace(/<img\b[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, "![$1]($2)");
46
+ text = text.replace(/<img\b[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, "![$2]($1)");
47
+ text = text.replace(/<img\b[^>]*src="([^"]*)"[^>]*\/?>/gi, "![]($1)");
48
+
49
+ // List items
50
+ text = text.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");
51
+
52
+ // Unordered/ordered list wrappers
53
+ text = text.replace(/<\/?(ul|ol)\b[^>]*>/gi, "\n");
54
+
55
+ // Paragraphs and divs
56
+ text = text.replace(/<\/?p\b[^>]*>/gi, "\n");
57
+ text = text.replace(/<\/?div\b[^>]*>/gi, "\n");
58
+
59
+ // Line breaks
60
+ text = text.replace(/<br\s*\/?>/gi, "\n");
61
+
62
+ // Horizontal rules
63
+ text = text.replace(/<hr\b[^>]*\/?>/gi, "\n---\n");
64
+
65
+ // Table handling (basic)
66
+ text = text.replace(/<\/tr>/gi, "\n");
67
+ text = text.replace(/<\/?(table|thead|tbody|tfoot)\b[^>]*>/gi, "\n");
68
+ text = text.replace(/<th\b[^>]*>([\s\S]*?)<\/th>/gi, "| **$1** ");
69
+ text = text.replace(/<td\b[^>]*>([\s\S]*?)<\/td>/gi, "| $1 ");
70
+
71
+ // Strip remaining HTML tags
72
+ text = text.replace(/<[^>]+>/g, "");
73
+
74
+ // Decode common HTML entities
75
+ text = text.replace(/&amp;/g, "&");
76
+ text = text.replace(/&lt;/g, "<");
77
+ text = text.replace(/&gt;/g, ">");
78
+ text = text.replace(/&quot;/g, '"');
79
+ text = text.replace(/&#39;/g, "'");
80
+ text = text.replace(/&nbsp;/g, " ");
81
+ text = text.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)));
82
+
83
+ // Clean up whitespace: collapse multiple blank lines
84
+ text = text.replace(/\n{3,}/g, "\n\n");
85
+ text = text.trim();
86
+
87
+ return text;
88
+ }
89
+
90
+ export const webFetchTool = tool({
91
+ description:
92
+ "Fetches content from a URL and returns it as markdown/text. Converts HTML to markdown. Timeout: 30s. Max output: 50KB.",
93
+ inputSchema: z.object({
94
+ url: z.string().url().describe("The URL to fetch content from"),
95
+ prompt: z
96
+ .string()
97
+ .optional()
98
+ .describe("Extraction instruction — what to look for in the content"),
99
+ }),
100
+ execute: async ({ url, prompt }) => {
101
+ try {
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
104
+
105
+ const response = await fetch(url, {
106
+ signal: controller.signal,
107
+ headers: {
108
+ "User-Agent": "AiSDK/1.0 (WebFetch tool)",
109
+ Accept: "text/html, application/json, text/plain, */*",
110
+ },
111
+ });
112
+
113
+ clearTimeout(timer);
114
+
115
+ if (!response.ok) {
116
+ return `Error: HTTP ${response.status} ${response.statusText}`;
117
+ }
118
+
119
+ const contentType = response.headers.get("content-type") ?? "";
120
+ const rawBody = await response.text();
121
+
122
+ let content: string;
123
+
124
+ if (contentType.includes("text/html")) {
125
+ content = htmlToMarkdown(rawBody);
126
+ } else if (contentType.includes("application/json")) {
127
+ try {
128
+ const parsed = JSON.parse(rawBody);
129
+ content = JSON.stringify(parsed, null, 2);
130
+ } catch {
131
+ content = rawBody;
132
+ }
133
+ } else {
134
+ content = rawBody;
135
+ }
136
+
137
+ if (content.length > MAX_OUTPUT) {
138
+ content = content.slice(0, MAX_OUTPUT) + "\n...[truncated at 50KB]";
139
+ }
140
+
141
+ let result = `# Fetched: ${url}\n\n${content}`;
142
+
143
+ if (prompt) {
144
+ result = `# Fetched: ${url}\n# Extraction: ${prompt}\n\n${content}`;
145
+ }
146
+
147
+ return result;
148
+ } catch (err: any) {
149
+ if (err.name === "AbortError") {
150
+ return `Error: Request timed out after ${FETCH_TIMEOUT / 1000}s`;
151
+ }
152
+ return `Error fetching URL: ${err.message}`;
153
+ }
154
+ },
155
+ });