@agi-cli/sdk 0.1.49 → 0.1.51

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 (50) hide show
  1. package/README.md +237 -538
  2. package/package.json +11 -7
  3. package/src/agent/types.ts +1 -1
  4. package/src/index.ts +99 -64
  5. package/src/errors.ts +0 -102
  6. package/src/providers/resolver.ts +0 -84
  7. package/src/streaming/artifacts.ts +0 -41
  8. package/src/tools/builtin/bash.ts +0 -73
  9. package/src/tools/builtin/bash.txt +0 -7
  10. package/src/tools/builtin/edit.ts +0 -145
  11. package/src/tools/builtin/edit.txt +0 -7
  12. package/src/tools/builtin/file-cache.ts +0 -39
  13. package/src/tools/builtin/finish.ts +0 -11
  14. package/src/tools/builtin/finish.txt +0 -5
  15. package/src/tools/builtin/fs/cd.ts +0 -19
  16. package/src/tools/builtin/fs/cd.txt +0 -5
  17. package/src/tools/builtin/fs/index.ts +0 -20
  18. package/src/tools/builtin/fs/ls.ts +0 -57
  19. package/src/tools/builtin/fs/ls.txt +0 -8
  20. package/src/tools/builtin/fs/pwd.ts +0 -17
  21. package/src/tools/builtin/fs/pwd.txt +0 -5
  22. package/src/tools/builtin/fs/read.ts +0 -49
  23. package/src/tools/builtin/fs/read.txt +0 -8
  24. package/src/tools/builtin/fs/tree.ts +0 -67
  25. package/src/tools/builtin/fs/tree.txt +0 -8
  26. package/src/tools/builtin/fs/util.ts +0 -95
  27. package/src/tools/builtin/fs/write.ts +0 -61
  28. package/src/tools/builtin/fs/write.txt +0 -8
  29. package/src/tools/builtin/git.commit.txt +0 -6
  30. package/src/tools/builtin/git.diff.txt +0 -5
  31. package/src/tools/builtin/git.status.txt +0 -5
  32. package/src/tools/builtin/git.ts +0 -112
  33. package/src/tools/builtin/glob.ts +0 -82
  34. package/src/tools/builtin/glob.txt +0 -8
  35. package/src/tools/builtin/grep.ts +0 -138
  36. package/src/tools/builtin/grep.txt +0 -9
  37. package/src/tools/builtin/ignore.ts +0 -45
  38. package/src/tools/builtin/patch.ts +0 -273
  39. package/src/tools/builtin/patch.txt +0 -7
  40. package/src/tools/builtin/plan.ts +0 -58
  41. package/src/tools/builtin/plan.txt +0 -6
  42. package/src/tools/builtin/progress.ts +0 -55
  43. package/src/tools/builtin/progress.txt +0 -7
  44. package/src/tools/builtin/ripgrep.ts +0 -71
  45. package/src/tools/builtin/ripgrep.txt +0 -7
  46. package/src/tools/builtin/websearch.ts +0 -219
  47. package/src/tools/builtin/websearch.txt +0 -12
  48. package/src/tools/loader.ts +0 -390
  49. package/src/types/index.ts +0 -11
  50. package/src/types/types.ts +0 -4
@@ -1,58 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './plan.txt' with { type: 'text' };
4
-
5
- const STATUS_ENUM = z.enum(['pending', 'in_progress', 'completed']);
6
-
7
- const ITEM_SCHEMA = z
8
- .union([
9
- z.string().min(1, 'Plan steps must be non-empty'),
10
- z.object({
11
- step: z.string().min(1, 'Plan steps must be non-empty'),
12
- status: STATUS_ENUM.optional(),
13
- }),
14
- ])
15
- .describe('Plan item');
16
-
17
- type PlanItemInput = z.infer<typeof ITEM_SCHEMA>;
18
-
19
- function normalizeItems(
20
- raw: PlanItemInput[],
21
- ): Array<{ step: string; status: z.infer<typeof STATUS_ENUM> }> {
22
- const normalized = raw.map((item) => {
23
- if (typeof item === 'string') {
24
- return { step: item.trim(), status: 'pending' as const };
25
- }
26
- const step = item.step.trim();
27
- const status = item.status ?? 'pending';
28
- return { step, status };
29
- });
30
-
31
- const filtered = normalized.filter((item) => item.step.length > 0);
32
- if (!filtered.length) {
33
- throw new Error('At least one plan step is required');
34
- }
35
-
36
- const inProgressCount = filtered.filter(
37
- (item) => item.status === 'in_progress',
38
- ).length;
39
- if (inProgressCount > 1) {
40
- throw new Error('Only one plan step may be marked as in_progress');
41
- }
42
-
43
- return filtered;
44
- }
45
-
46
- export const updatePlanTool: Tool = tool({
47
- description: DESCRIPTION,
48
- inputSchema: z.object({
49
- items: z.array(ITEM_SCHEMA).min(1).describe('Ordered list of plan steps'),
50
- note: z
51
- .string()
52
- .optional()
53
- .describe('Optional note or context for the plan update'),
54
- }),
55
- async execute({ items, note }: { items: PlanItemInput[]; note?: string }) {
56
- return { items: normalizeItems(items), note };
57
- },
58
- });
@@ -1,6 +0,0 @@
1
- - Publish or update the current execution plan
2
- - Displays ordered steps and optional note/context to the user
3
-
4
- Usage tips:
5
- - Keep steps concise (imperative verbs) and reflect progress updates
6
- - Update the plan whenever scope changes or new steps emerge
@@ -1,55 +0,0 @@
1
- import { tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './progress.txt' with { type: 'text' };
4
-
5
- // Progress update tool: allows the model to emit lightweight status signals
6
- // without revealing chain-of-thought. The runner/UI should surface these
7
- // messages immediately.
8
- const StageEnum = z.enum([
9
- 'planning',
10
- 'discovering',
11
- 'generating',
12
- 'preparing',
13
- 'writing',
14
- 'verifying',
15
- ]);
16
-
17
- export const progressUpdateTool = tool({
18
- description: DESCRIPTION,
19
- inputSchema: z.object({
20
- message: z
21
- .string()
22
- .min(1)
23
- .max(200)
24
- .describe('Short, user-facing status message (<= 200 chars).'),
25
- pct: z
26
- .number()
27
- .min(0)
28
- .max(100)
29
- .optional()
30
- .describe('Optional overall progress percent 0-100.'),
31
- stage: StageEnum.optional().default('planning'),
32
- }),
33
- async execute({
34
- message,
35
- pct,
36
- stage,
37
- }: {
38
- message: string;
39
- pct?: number;
40
- stage?: z.infer<typeof StageEnum>;
41
- }) {
42
- // Keep the tool lightweight; no side effects beyond the event itself.
43
- // Returning the normalized payload allows generic renderers to inspect it if needed.
44
- const normalizedPct =
45
- typeof pct === 'number'
46
- ? Math.min(100, Math.max(0, Math.round(pct)))
47
- : undefined;
48
- return {
49
- ok: true,
50
- message,
51
- pct: normalizedPct,
52
- stage: stage ?? 'planning',
53
- } as const;
54
- },
55
- });
@@ -1,7 +0,0 @@
1
- - Emit a short, user-facing progress/status update
2
- - Supports optional `pct` (0–100) and `stage` indicators
3
- - Lightweight; intended for immediate UI display
4
-
5
- Usage tips:
6
- - Keep messages short (<= 200 chars) and informative
7
- - Use multiple updates during long-running tasks
@@ -1,71 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import { $ } from 'bun';
4
- import { join } from 'node:path';
5
- import DESCRIPTION from './ripgrep.txt' with { type: 'text' };
6
-
7
- export function buildRipgrepTool(projectRoot: string): {
8
- name: string;
9
- tool: Tool;
10
- } {
11
- const rg = tool({
12
- description: DESCRIPTION,
13
- inputSchema: z.object({
14
- query: z.string().min(1).describe('Search pattern (regex by default)'),
15
- path: z
16
- .string()
17
- .optional()
18
- .default('.')
19
- .describe('Relative path to search in'),
20
- ignoreCase: z.boolean().optional().default(false),
21
- glob: z
22
- .array(z.string())
23
- .optional()
24
- .describe('One or more glob patterns to include'),
25
- maxResults: z.number().int().min(1).max(5000).optional().default(500),
26
- }),
27
- async execute({
28
- query,
29
- path = '.',
30
- ignoreCase,
31
- glob,
32
- maxResults = 500,
33
- }: {
34
- query: string;
35
- path?: string;
36
- ignoreCase?: boolean;
37
- glob?: string[];
38
- maxResults?: number;
39
- }) {
40
- function expandTilde(p: string) {
41
- const home = process.env.HOME || process.env.USERPROFILE || '';
42
- if (!home) return p;
43
- if (p === '~') return home;
44
- if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
45
- return p;
46
- }
47
- const p = expandTilde(String(path ?? '.')).trim();
48
- const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
49
- const target = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
50
- const args = ['--no-heading', '--line-number', '--color=never'];
51
- if (ignoreCase) args.push('-i');
52
- if (Array.isArray(glob)) for (const g of glob) args.push('-g', g);
53
- args.push('--max-count', String(maxResults));
54
- args.push(query, target);
55
- try {
56
- const output = await $`rg ${args}`.quiet().text();
57
- const lines = output.split('\n').filter(Boolean).slice(0, maxResults);
58
- const matches = lines.map((l) => {
59
- const m = l.match(/^(.*?):(\d+):(.*)$/);
60
- if (!m) return { file: '', line: 0, text: l };
61
- return { file: m[1], line: Number(m[2]), text: m[3] };
62
- });
63
- return { count: matches.length, matches };
64
- } catch (err) {
65
- const stderr = (err as { stderr?: string })?.stderr ?? String(err);
66
- return { count: 0, matches: [], error: stderr?.trim() };
67
- }
68
- },
69
- });
70
- return { name: 'ripgrep', tool: rg };
71
- }
@@ -1,7 +0,0 @@
1
- - Search files using ripgrep (rg) with regex patterns
2
- - Returns a flat list of matches with `file`, `line`, and `text`
3
- - Supports include globs and case-insensitive search
4
-
5
- Usage tips:
6
- - Use the Grep tool for a friendly summary grouped by file
7
- - Use the Glob tool first to limit the search set if needed
@@ -1,219 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './websearch.txt' with { type: 'text' };
4
-
5
- export function buildWebSearchTool(): {
6
- name: string;
7
- tool: Tool;
8
- } {
9
- const websearch = tool({
10
- description: DESCRIPTION,
11
- inputSchema: z
12
- .object({
13
- url: z
14
- .string()
15
- .optional()
16
- .describe(
17
- 'URL to fetch content from (mutually exclusive with query)',
18
- ),
19
- query: z
20
- .string()
21
- .optional()
22
- .describe(
23
- 'Search query to search the web (mutually exclusive with url)',
24
- ),
25
- maxLength: z
26
- .number()
27
- .optional()
28
- .default(50000)
29
- .describe(
30
- 'Maximum content length to return (default: 50000 characters)',
31
- ),
32
- })
33
- .strict()
34
- .refine((data) => (data.url ? !data.query : !!data.query), {
35
- message: 'Must provide either url or query, but not both',
36
- }),
37
- async execute({
38
- url,
39
- query,
40
- maxLength,
41
- }: {
42
- url?: string;
43
- query?: string;
44
- maxLength?: number;
45
- }) {
46
- const maxLen = maxLength ?? 50000;
47
-
48
- if (url) {
49
- // Fetch URL content
50
- try {
51
- const response = await fetch(url, {
52
- headers: {
53
- 'User-Agent':
54
- 'Mozilla/5.0 (compatible; AGI-Bot/1.0; +https://github.com/anthropics/agi)',
55
- Accept:
56
- 'text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7',
57
- },
58
- redirect: 'follow',
59
- signal: AbortSignal.timeout(30000), // 30 second timeout
60
- });
61
-
62
- if (!response.ok) {
63
- throw new Error(
64
- `HTTP error! status: ${response.status} ${response.statusText}`,
65
- );
66
- }
67
-
68
- const contentType = response.headers.get('content-type') || '';
69
- let content = '';
70
-
71
- if (
72
- contentType.includes('text/') ||
73
- contentType.includes('application/json') ||
74
- contentType.includes('application/xml') ||
75
- contentType.includes('application/xhtml')
76
- ) {
77
- content = await response.text();
78
- } else {
79
- return {
80
- error: `Unsupported content type: ${contentType}. Only text-based content can be fetched.`,
81
- };
82
- }
83
-
84
- // Strip HTML tags for better readability (basic cleaning)
85
- const cleanContent = content
86
- .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
87
- .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
88
- .replace(/<[^>]+>/g, ' ')
89
- .replace(/\s+/g, ' ')
90
- .trim();
91
-
92
- const truncated = cleanContent.slice(0, maxLen);
93
- const wasTruncated = cleanContent.length > maxLen;
94
-
95
- return {
96
- url,
97
- content: truncated,
98
- contentLength: cleanContent.length,
99
- truncated: wasTruncated,
100
- contentType,
101
- };
102
- } catch (error) {
103
- const errorMessage =
104
- error instanceof Error ? error.message : String(error);
105
- return {
106
- error: `Failed to fetch URL: ${errorMessage}`,
107
- };
108
- }
109
- }
110
-
111
- if (query) {
112
- // Web search functionality
113
- // Use DuckDuckGo's HTML search (doesn't require API key)
114
- try {
115
- const searchUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
116
- const response = await fetch(searchUrl, {
117
- headers: {
118
- 'User-Agent':
119
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
120
- Accept: 'text/html',
121
- },
122
- redirect: 'follow',
123
- signal: AbortSignal.timeout(30000),
124
- });
125
-
126
- if (!response.ok) {
127
- throw new Error(`Search failed: ${response.status}`);
128
- }
129
-
130
- const html = await response.text();
131
-
132
- // Parse DuckDuckGo results (basic parsing)
133
- const results: Array<{
134
- title: string;
135
- url: string;
136
- snippet: string;
137
- }> = [];
138
-
139
- // Match result blocks
140
- const resultPattern =
141
- /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
142
-
143
- let match: RegExpExecArray | null = null;
144
- match = resultPattern.exec(html);
145
- while (match !== null && results.length < 10) {
146
- const url = match[1]?.trim();
147
- const title = match[2]?.trim();
148
- let snippet = match[3]?.trim();
149
-
150
- if (url && title) {
151
- // Clean snippet
152
- snippet = snippet
153
- ?.replace(/<[^>]+>/g, '')
154
- .replace(/\s+/g, ' ')
155
- .trim();
156
-
157
- results.push({
158
- title,
159
- url,
160
- snippet: snippet || '',
161
- });
162
- }
163
- match = resultPattern.exec(html);
164
- }
165
-
166
- // Fallback: simpler pattern if the above doesn't work
167
- if (results.length === 0) {
168
- const simplePattern =
169
- /<a[^>]+rel="nofollow"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
170
- match = simplePattern.exec(html);
171
- while (match !== null && results.length < 10) {
172
- const url = match[1]?.trim();
173
- const title = match[2]?.trim();
174
- if (url && title && url.startsWith('http')) {
175
- results.push({
176
- title,
177
- url,
178
- snippet: '',
179
- });
180
- }
181
- match = simplePattern.exec(html);
182
- }
183
- }
184
-
185
- if (results.length === 0) {
186
- return {
187
- error:
188
- 'No search results found. The search service may have changed its format or blocked the request.',
189
- query,
190
- suggestion:
191
- 'Try using the url parameter to fetch a specific webpage instead.',
192
- };
193
- }
194
-
195
- return {
196
- query,
197
- results,
198
- count: results.length,
199
- };
200
- } catch (error) {
201
- const errorMessage =
202
- error instanceof Error ? error.message : String(error);
203
- return {
204
- error: `Search failed: ${errorMessage}`,
205
- query,
206
- suggestion:
207
- 'Search services may be temporarily unavailable. Try using the url parameter to fetch a specific webpage instead.',
208
- };
209
- }
210
- }
211
-
212
- return {
213
- error: 'Must provide either url or query parameter',
214
- };
215
- },
216
- });
217
-
218
- return { name: 'websearch', tool: websearch };
219
- }
@@ -1,12 +0,0 @@
1
- - Search the web or fetch content from URLs
2
- - Use `query` to search the web and get a list of results with titles, URLs, and snippets
3
- - Use `url` to fetch and read the content of a specific webpage
4
- - Returns cleaned, text-based content (HTML tags are stripped)
5
- - Cannot be used for both search and URL fetch in the same call
6
-
7
- Usage tips:
8
- - For research: use `query` to find relevant pages, then `url` to read specific ones
9
- - Search returns up to 10 results with titles, URLs, and snippets
10
- - URL fetching works for text-based content (HTML, JSON, XML, plain text)
11
- - Content is automatically truncated if it exceeds maxLength (default 50,000 chars)
12
- - Use this to gather current information, read documentation, or verify facts