@agi-cli/sdk 0.1.110 → 0.1.111
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.
- package/package.json +1 -1
- package/src/core/src/tools/builtin/edit.ts +22 -5
- package/src/core/src/tools/builtin/finish.ts +3 -2
- package/src/core/src/tools/builtin/fs/ls.ts +23 -3
- package/src/core/src/tools/builtin/fs/tree.ts +15 -3
- package/src/core/src/tools/builtin/git.ts +26 -14
- package/src/core/src/tools/builtin/glob.ts +19 -7
- package/src/core/src/tools/builtin/grep.ts +30 -4
- package/src/core/src/tools/builtin/ripgrep.ts +26 -5
- package/src/core/src/tools/builtin/websearch.ts +54 -23
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
4
4
|
import { constants } from 'node:fs';
|
|
5
5
|
import DESCRIPTION from './edit.txt' with { type: 'text' };
|
|
6
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
6
7
|
|
|
7
8
|
const replaceOp = z.object({
|
|
8
9
|
type: z.literal('replace'),
|
|
@@ -54,7 +55,9 @@ export const editTool: Tool = tool({
|
|
|
54
55
|
path: string;
|
|
55
56
|
ops: z.infer<typeof opSchema>[];
|
|
56
57
|
create?: boolean;
|
|
57
|
-
})
|
|
58
|
+
}): Promise<
|
|
59
|
+
ToolResponse<{ path: string; opsApplied: number; bytes: number }>
|
|
60
|
+
> {
|
|
58
61
|
let exists = false;
|
|
59
62
|
try {
|
|
60
63
|
await access(path, constants.F_OK);
|
|
@@ -62,7 +65,13 @@ export const editTool: Tool = tool({
|
|
|
62
65
|
} catch {}
|
|
63
66
|
|
|
64
67
|
if (!exists) {
|
|
65
|
-
if (!create)
|
|
68
|
+
if (!create) {
|
|
69
|
+
return createToolError(`File not found: ${path}`, 'not_found', {
|
|
70
|
+
parameter: 'path',
|
|
71
|
+
value: path,
|
|
72
|
+
suggestion: 'Set create: true to create a new file',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
66
75
|
await writeFile(path, '');
|
|
67
76
|
}
|
|
68
77
|
let text = await readFile(path, 'utf-8');
|
|
@@ -120,8 +129,16 @@ export const editTool: Tool = tool({
|
|
|
120
129
|
applied += 1;
|
|
121
130
|
continue;
|
|
122
131
|
}
|
|
123
|
-
if (!op.pattern)
|
|
124
|
-
|
|
132
|
+
if (!op.pattern) {
|
|
133
|
+
return createToolError(
|
|
134
|
+
'insert requires pattern for before/after',
|
|
135
|
+
'validation',
|
|
136
|
+
{
|
|
137
|
+
parameter: 'pattern',
|
|
138
|
+
suggestion: 'Provide a pattern to anchor the insertion',
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
}
|
|
125
142
|
const idx = text.indexOf(op.pattern);
|
|
126
143
|
if (idx === -1) continue;
|
|
127
144
|
if (op.position === 'before')
|
|
@@ -147,6 +164,6 @@ export const editTool: Tool = tool({
|
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
await writeFile(path, text);
|
|
150
|
-
return { path, opsApplied: applied, bytes: text.length };
|
|
167
|
+
return { ok: true, path, opsApplied: applied, bytes: text.length };
|
|
151
168
|
},
|
|
152
169
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { tool } from 'ai';
|
|
3
3
|
import DESCRIPTION from './finish.txt' with { type: 'text' };
|
|
4
|
+
import type { ToolResponse } from '../error.ts';
|
|
4
5
|
|
|
5
6
|
export const finishTool = tool({
|
|
6
7
|
description: DESCRIPTION,
|
|
7
8
|
inputSchema: z.object({}),
|
|
8
|
-
async execute() {
|
|
9
|
-
return { done: true }
|
|
9
|
+
async execute(): Promise<ToolResponse<{ done: true }>> {
|
|
10
|
+
return { ok: true, done: true };
|
|
10
11
|
},
|
|
11
12
|
});
|
|
@@ -5,6 +5,7 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
|
|
6
6
|
import DESCRIPTION from './ls.txt' with { type: 'text' };
|
|
7
7
|
import { toIgnoredBasenames } from '../ignore.ts';
|
|
8
|
+
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
8
9
|
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
11
|
|
|
@@ -25,7 +26,18 @@ export function buildLsTool(projectRoot: string): { name: string; tool: Tool } {
|
|
|
25
26
|
.optional()
|
|
26
27
|
.describe('List of directory names/globs to ignore'),
|
|
27
28
|
}),
|
|
28
|
-
async execute({
|
|
29
|
+
async execute({
|
|
30
|
+
path,
|
|
31
|
+
ignore,
|
|
32
|
+
}: {
|
|
33
|
+
path: string;
|
|
34
|
+
ignore?: string[];
|
|
35
|
+
}): Promise<
|
|
36
|
+
ToolResponse<{
|
|
37
|
+
path: string;
|
|
38
|
+
entries: Array<{ name: string; type: string }>;
|
|
39
|
+
}>
|
|
40
|
+
> {
|
|
29
41
|
const req = expandTilde(path || '.');
|
|
30
42
|
const abs = isAbsoluteLike(req)
|
|
31
43
|
? req
|
|
@@ -48,11 +60,19 @@ export function buildLsTool(projectRoot: string): { name: string; tool: Tool } {
|
|
|
48
60
|
.filter(
|
|
49
61
|
(entry) => !(entry.type === 'dir' && ignored.has(entry.name)),
|
|
50
62
|
);
|
|
51
|
-
return { path: req, entries };
|
|
63
|
+
return { ok: true, path: req, entries };
|
|
52
64
|
} catch (error: unknown) {
|
|
53
65
|
const err = error as { stderr?: string; stdout?: string };
|
|
54
66
|
const message = (err.stderr || err.stdout || 'ls failed').trim();
|
|
55
|
-
|
|
67
|
+
return createToolError(
|
|
68
|
+
`ls failed for ${req}: ${message}`,
|
|
69
|
+
'execution',
|
|
70
|
+
{
|
|
71
|
+
parameter: 'path',
|
|
72
|
+
value: req,
|
|
73
|
+
suggestion: 'Check if the directory exists and is accessible',
|
|
74
|
+
},
|
|
75
|
+
);
|
|
56
76
|
}
|
|
57
77
|
},
|
|
58
78
|
});
|
|
@@ -5,6 +5,7 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
|
|
6
6
|
import DESCRIPTION from './tree.txt' with { type: 'text' };
|
|
7
7
|
import { toIgnoredBasenames } from '../ignore.ts';
|
|
8
|
+
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
8
9
|
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
11
|
|
|
@@ -38,7 +39,9 @@ export function buildTreeTool(projectRoot: string): {
|
|
|
38
39
|
path: string;
|
|
39
40
|
depth?: number;
|
|
40
41
|
ignore?: string[];
|
|
41
|
-
})
|
|
42
|
+
}): Promise<
|
|
43
|
+
ToolResponse<{ path: string; depth: number | null; tree: string }>
|
|
44
|
+
> {
|
|
42
45
|
const req = expandTilde(path || '.');
|
|
43
46
|
const start = isAbsoluteLike(req)
|
|
44
47
|
? req
|
|
@@ -59,11 +62,20 @@ export function buildTreeTool(projectRoot: string): {
|
|
|
59
62
|
maxBuffer: 10 * 1024 * 1024,
|
|
60
63
|
});
|
|
61
64
|
const output = stdout.trimEnd();
|
|
62
|
-
return { path: req, depth: depth ?? null, tree: output };
|
|
65
|
+
return { ok: true, path: req, depth: depth ?? null, tree: output };
|
|
63
66
|
} catch (error: unknown) {
|
|
64
67
|
const err = error as { stderr?: string; stdout?: string };
|
|
65
68
|
const message = (err.stderr || err.stdout || 'tree failed').trim();
|
|
66
|
-
|
|
69
|
+
return createToolError(
|
|
70
|
+
`tree failed for ${req}: ${message}`,
|
|
71
|
+
'execution',
|
|
72
|
+
{
|
|
73
|
+
parameter: 'path',
|
|
74
|
+
value: req,
|
|
75
|
+
suggestion:
|
|
76
|
+
'Check if the directory exists and tree command is installed',
|
|
77
|
+
},
|
|
78
|
+
);
|
|
67
79
|
}
|
|
68
80
|
},
|
|
69
81
|
});
|
|
@@ -5,6 +5,7 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import GIT_STATUS_DESCRIPTION from './git.status.txt' with { type: 'text' };
|
|
6
6
|
import GIT_DIFF_DESCRIPTION from './git.diff.txt' with { type: 'text' };
|
|
7
7
|
import GIT_COMMIT_DESCRIPTION from './git.commit.txt' with { type: 'text' };
|
|
8
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
9
|
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
11
|
|
|
@@ -37,14 +38,13 @@ export function buildGitTools(
|
|
|
37
38
|
const git_status = tool({
|
|
38
39
|
description: GIT_STATUS_DESCRIPTION,
|
|
39
40
|
inputSchema: z.object({}).optional(),
|
|
40
|
-
async execute()
|
|
41
|
+
async execute(): Promise<
|
|
42
|
+
ToolResponse<{ staged: number; unstaged: number; raw: string[] }>
|
|
43
|
+
> {
|
|
41
44
|
if (!(await inRepo())) {
|
|
42
|
-
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
unstaged: 0,
|
|
46
|
-
raw: [],
|
|
47
|
-
};
|
|
45
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
46
|
+
suggestion: 'Initialize a git repository with git init',
|
|
47
|
+
});
|
|
48
48
|
}
|
|
49
49
|
const gitRoot = await findGitRoot();
|
|
50
50
|
const { stdout } = await execAsync(
|
|
@@ -63,6 +63,7 @@ export function buildGitTools(
|
|
|
63
63
|
if (isUntracked || y !== ' ') unstaged += 1;
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
|
+
ok: true,
|
|
66
67
|
staged,
|
|
67
68
|
unstaged,
|
|
68
69
|
raw: lines.slice(0, 200),
|
|
@@ -73,9 +74,15 @@ export function buildGitTools(
|
|
|
73
74
|
const git_diff = tool({
|
|
74
75
|
description: GIT_DIFF_DESCRIPTION,
|
|
75
76
|
inputSchema: z.object({ all: z.boolean().optional().default(false) }),
|
|
76
|
-
async execute({
|
|
77
|
+
async execute({
|
|
78
|
+
all,
|
|
79
|
+
}: {
|
|
80
|
+
all?: boolean;
|
|
81
|
+
}): Promise<ToolResponse<{ all: boolean; patch: string }>> {
|
|
77
82
|
if (!(await inRepo())) {
|
|
78
|
-
return
|
|
83
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
84
|
+
suggestion: 'Initialize a git repository with git init',
|
|
85
|
+
});
|
|
79
86
|
}
|
|
80
87
|
const gitRoot = await findGitRoot();
|
|
81
88
|
// When all=true, show full working tree diff relative to HEAD
|
|
@@ -86,7 +93,7 @@ export function buildGitTools(
|
|
|
86
93
|
: `git -C "${gitRoot}" diff --staged`;
|
|
87
94
|
const { stdout } = await execAsync(cmd, { maxBuffer: 10 * 1024 * 1024 });
|
|
88
95
|
const limited = stdout.split('\n').slice(0, 5000).join('\n');
|
|
89
|
-
return { all: !!all, patch: limited };
|
|
96
|
+
return { ok: true, all: !!all, patch: limited };
|
|
90
97
|
},
|
|
91
98
|
});
|
|
92
99
|
|
|
@@ -105,9 +112,11 @@ export function buildGitTools(
|
|
|
105
112
|
message: string;
|
|
106
113
|
amend?: boolean;
|
|
107
114
|
signoff?: boolean;
|
|
108
|
-
}) {
|
|
115
|
+
}): Promise<ToolResponse<{ result: string }>> {
|
|
109
116
|
if (!(await inRepo())) {
|
|
110
|
-
return
|
|
117
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
118
|
+
suggestion: 'Initialize a git repository with git init',
|
|
119
|
+
});
|
|
111
120
|
}
|
|
112
121
|
const gitRoot = await findGitRoot();
|
|
113
122
|
const args = [
|
|
@@ -122,11 +131,14 @@ export function buildGitTools(
|
|
|
122
131
|
if (signoff) args.push('--signoff');
|
|
123
132
|
try {
|
|
124
133
|
const { stdout } = await execAsync(args.join(' '));
|
|
125
|
-
return { result: stdout.trim() };
|
|
134
|
+
return { ok: true, result: stdout.trim() };
|
|
126
135
|
} catch (error: unknown) {
|
|
127
136
|
const err = error as { stderr?: string; message?: string };
|
|
128
137
|
const txt = err.stderr || err.message || 'git commit failed';
|
|
129
|
-
|
|
138
|
+
return createToolError(txt, 'execution', {
|
|
139
|
+
suggestion:
|
|
140
|
+
'Check if there are staged changes and the commit message is valid',
|
|
141
|
+
});
|
|
130
142
|
}
|
|
131
143
|
},
|
|
132
144
|
});
|
|
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { stat } from 'node:fs/promises';
|
|
6
6
|
import DESCRIPTION from './glob.txt' with { type: 'text' };
|
|
7
7
|
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
8
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
9
|
|
|
9
10
|
function expandTilde(p: string) {
|
|
10
11
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
@@ -54,7 +55,14 @@ export function buildGlobTool(projectRoot: string): {
|
|
|
54
55
|
path?: string;
|
|
55
56
|
ignore?: string[];
|
|
56
57
|
limit?: number;
|
|
57
|
-
})
|
|
58
|
+
}): Promise<
|
|
59
|
+
ToolResponse<{
|
|
60
|
+
count: number;
|
|
61
|
+
total: number;
|
|
62
|
+
files: string[];
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
}>
|
|
65
|
+
> {
|
|
58
66
|
const p = expandTilde(String(path || '.')).trim();
|
|
59
67
|
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
60
68
|
const searchPath = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
@@ -96,6 +104,7 @@ export function buildGlobTool(projectRoot: string): {
|
|
|
96
104
|
const limitedFiles = filesWithStats.slice(0, limit).map((f) => f.file);
|
|
97
105
|
|
|
98
106
|
return {
|
|
107
|
+
ok: true,
|
|
99
108
|
count: limitedFiles.length,
|
|
100
109
|
total: files.length,
|
|
101
110
|
files: limitedFiles,
|
|
@@ -103,12 +112,15 @@ export function buildGlobTool(projectRoot: string): {
|
|
|
103
112
|
};
|
|
104
113
|
} catch (error: unknown) {
|
|
105
114
|
const err = error as { message?: string };
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
return createToolError(
|
|
116
|
+
`Glob search failed: ${err.message || String(error)}`,
|
|
117
|
+
'execution',
|
|
118
|
+
{
|
|
119
|
+
parameter: 'pattern',
|
|
120
|
+
value: pattern,
|
|
121
|
+
suggestion: 'Check if the pattern syntax is valid',
|
|
122
|
+
},
|
|
123
|
+
);
|
|
112
124
|
}
|
|
113
125
|
},
|
|
114
126
|
});
|
|
@@ -5,6 +5,7 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import DESCRIPTION from './grep.txt' with { type: 'text' };
|
|
7
7
|
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
8
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
9
|
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
11
|
|
|
@@ -39,9 +40,24 @@ export function buildGrepTool(projectRoot: string): {
|
|
|
39
40
|
.optional()
|
|
40
41
|
.describe('Glob patterns to exclude from search'),
|
|
41
42
|
}),
|
|
42
|
-
async execute(params
|
|
43
|
+
async execute(params: {
|
|
44
|
+
pattern: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
include?: string;
|
|
47
|
+
ignore?: string[];
|
|
48
|
+
}): Promise<
|
|
49
|
+
ToolResponse<{
|
|
50
|
+
count: number;
|
|
51
|
+
matches: Array<{ file: string; line: number; text: string }>;
|
|
52
|
+
}>
|
|
53
|
+
> {
|
|
43
54
|
const pattern = String(params.pattern || '');
|
|
44
|
-
if (!pattern)
|
|
55
|
+
if (!pattern) {
|
|
56
|
+
return createToolError('pattern is required', 'validation', {
|
|
57
|
+
parameter: 'pattern',
|
|
58
|
+
suggestion: 'Provide a regex pattern to search for',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
45
61
|
|
|
46
62
|
const p = expandTilde(String(params.path || '')).trim();
|
|
47
63
|
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
@@ -63,10 +79,19 @@ export function buildGrepTool(projectRoot: string): {
|
|
|
63
79
|
} catch (error: unknown) {
|
|
64
80
|
const err = error as { code?: number; stderr?: string };
|
|
65
81
|
if (err.code === 1) {
|
|
66
|
-
return { count: 0, matches: [] };
|
|
82
|
+
return { ok: true, count: 0, matches: [] };
|
|
67
83
|
}
|
|
68
84
|
const err2 = error as { stderr?: string; message?: string };
|
|
69
|
-
|
|
85
|
+
return createToolError(
|
|
86
|
+
`ripgrep failed: ${err2.stderr || err2.message}`,
|
|
87
|
+
'execution',
|
|
88
|
+
{
|
|
89
|
+
parameter: 'pattern',
|
|
90
|
+
value: pattern,
|
|
91
|
+
suggestion:
|
|
92
|
+
'Check if ripgrep (rg) is installed and the pattern is valid',
|
|
93
|
+
},
|
|
94
|
+
);
|
|
70
95
|
}
|
|
71
96
|
|
|
72
97
|
const lines = output.trim().split('\n');
|
|
@@ -94,6 +119,7 @@ export function buildGrepTool(projectRoot: string): {
|
|
|
94
119
|
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
95
120
|
|
|
96
121
|
return {
|
|
122
|
+
ok: true,
|
|
97
123
|
count: finalMatches.length,
|
|
98
124
|
matches: finalMatches,
|
|
99
125
|
};
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import DESCRIPTION from './ripgrep.txt' with { type: 'text' };
|
|
6
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
6
7
|
|
|
7
8
|
export function buildRipgrepTool(projectRoot: string): {
|
|
8
9
|
name: string;
|
|
@@ -36,7 +37,12 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
36
37
|
ignoreCase?: boolean;
|
|
37
38
|
glob?: string[];
|
|
38
39
|
maxResults?: number;
|
|
39
|
-
})
|
|
40
|
+
}): Promise<
|
|
41
|
+
ToolResponse<{
|
|
42
|
+
count: number;
|
|
43
|
+
matches: Array<{ file: string; line: number; text: string }>;
|
|
44
|
+
}>
|
|
45
|
+
> {
|
|
40
46
|
function expandTilde(p: string) {
|
|
41
47
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
42
48
|
if (!home) return p;
|
|
@@ -70,7 +76,16 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
70
76
|
|
|
71
77
|
proc.on('close', (code) => {
|
|
72
78
|
if (code !== 0 && code !== 1) {
|
|
73
|
-
resolve(
|
|
79
|
+
resolve(
|
|
80
|
+
createToolError(
|
|
81
|
+
stderr.trim() || 'ripgrep failed',
|
|
82
|
+
'execution',
|
|
83
|
+
{
|
|
84
|
+
suggestion:
|
|
85
|
+
'Check if ripgrep (rg) is installed and the query is valid',
|
|
86
|
+
},
|
|
87
|
+
),
|
|
88
|
+
);
|
|
74
89
|
return;
|
|
75
90
|
}
|
|
76
91
|
|
|
@@ -86,15 +101,21 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
86
101
|
const text = parts.slice(2).join(':');
|
|
87
102
|
return { file, line, text };
|
|
88
103
|
});
|
|
89
|
-
resolve({ count: matches.length, matches });
|
|
104
|
+
resolve({ ok: true, count: matches.length, matches });
|
|
90
105
|
});
|
|
91
106
|
|
|
92
107
|
proc.on('error', (err) => {
|
|
93
|
-
resolve(
|
|
108
|
+
resolve(
|
|
109
|
+
createToolError(String(err), 'execution', {
|
|
110
|
+
suggestion: 'Ensure ripgrep (rg) is installed',
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
94
113
|
});
|
|
95
114
|
});
|
|
96
115
|
} catch (err) {
|
|
97
|
-
return
|
|
116
|
+
return createToolError(String(err), 'execution', {
|
|
117
|
+
suggestion: 'Ensure ripgrep (rg) is installed',
|
|
118
|
+
});
|
|
98
119
|
}
|
|
99
120
|
},
|
|
100
121
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import DESCRIPTION from './websearch.txt' with { type: 'text' };
|
|
4
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
4
5
|
|
|
5
6
|
export function buildWebSearchTool(): {
|
|
6
7
|
name: string;
|
|
@@ -42,7 +43,22 @@ export function buildWebSearchTool(): {
|
|
|
42
43
|
url?: string;
|
|
43
44
|
query?: string;
|
|
44
45
|
maxLength?: number;
|
|
45
|
-
})
|
|
46
|
+
}): Promise<
|
|
47
|
+
ToolResponse<
|
|
48
|
+
| {
|
|
49
|
+
url: string;
|
|
50
|
+
content: string;
|
|
51
|
+
contentLength: number;
|
|
52
|
+
truncated: boolean;
|
|
53
|
+
contentType: string;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
query: string;
|
|
57
|
+
results: Array<{ title: string; url: string; snippet: string }>;
|
|
58
|
+
count: number;
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
> {
|
|
46
62
|
const maxLen = maxLength ?? 50000;
|
|
47
63
|
|
|
48
64
|
if (url) {
|
|
@@ -76,9 +92,11 @@ export function buildWebSearchTool(): {
|
|
|
76
92
|
) {
|
|
77
93
|
content = await response.text();
|
|
78
94
|
} else {
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
return createToolError(
|
|
96
|
+
`Unsupported content type: ${contentType}. Only text-based content can be fetched.`,
|
|
97
|
+
'unsupported',
|
|
98
|
+
{ contentType },
|
|
99
|
+
);
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
// Strip HTML tags for better readability (basic cleaning)
|
|
@@ -93,6 +111,7 @@ export function buildWebSearchTool(): {
|
|
|
93
111
|
const wasTruncated = cleanContent.length > maxLen;
|
|
94
112
|
|
|
95
113
|
return {
|
|
114
|
+
ok: true,
|
|
96
115
|
url,
|
|
97
116
|
content: truncated,
|
|
98
117
|
contentLength: cleanContent.length,
|
|
@@ -102,9 +121,11 @@ export function buildWebSearchTool(): {
|
|
|
102
121
|
} catch (error) {
|
|
103
122
|
const errorMessage =
|
|
104
123
|
error instanceof Error ? error.message : String(error);
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
return createToolError(
|
|
125
|
+
`Failed to fetch URL: ${errorMessage}`,
|
|
126
|
+
'execution',
|
|
127
|
+
{ url },
|
|
128
|
+
);
|
|
108
129
|
}
|
|
109
130
|
}
|
|
110
131
|
|
|
@@ -183,16 +204,19 @@ export function buildWebSearchTool(): {
|
|
|
183
204
|
}
|
|
184
205
|
|
|
185
206
|
if (results.length === 0) {
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
return createToolError(
|
|
208
|
+
'No search results found. The search service may have changed its format or blocked the request.',
|
|
209
|
+
'execution',
|
|
210
|
+
{
|
|
211
|
+
query,
|
|
212
|
+
suggestion:
|
|
213
|
+
'Try using the url parameter to fetch a specific webpage instead.',
|
|
214
|
+
},
|
|
215
|
+
);
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
return {
|
|
219
|
+
ok: true,
|
|
196
220
|
query,
|
|
197
221
|
results,
|
|
198
222
|
count: results.length,
|
|
@@ -200,18 +224,25 @@ export function buildWebSearchTool(): {
|
|
|
200
224
|
} catch (error) {
|
|
201
225
|
const errorMessage =
|
|
202
226
|
error instanceof Error ? error.message : String(error);
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
return createToolError(
|
|
228
|
+
`Search failed: ${errorMessage}`,
|
|
229
|
+
'execution',
|
|
230
|
+
{
|
|
231
|
+
query,
|
|
232
|
+
suggestion:
|
|
233
|
+
'Search services may be temporarily unavailable. Try using the url parameter to fetch a specific webpage instead.',
|
|
234
|
+
},
|
|
235
|
+
);
|
|
209
236
|
}
|
|
210
237
|
}
|
|
211
238
|
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
return createToolError(
|
|
240
|
+
'Must provide either url or query parameter',
|
|
241
|
+
'validation',
|
|
242
|
+
{
|
|
243
|
+
suggestion: 'Provide either a url to fetch or a query to search',
|
|
244
|
+
},
|
|
245
|
+
);
|
|
215
246
|
},
|
|
216
247
|
});
|
|
217
248
|
|