@link-assistant/agent 0.0.9 → 0.0.12
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/EXAMPLES.md +36 -0
- package/MODELS.md +72 -24
- package/README.md +59 -2
- package/TOOLS.md +20 -0
- package/package.json +35 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +469 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +144 -119
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +39 -24
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +346 -199
- package/src/json-standard/index.ts +67 -51
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/tool/webfetch.ts
CHANGED
|
@@ -1,171 +1,194 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import { Tool } from
|
|
3
|
-
import TurndownService from
|
|
4
|
-
import DESCRIPTION from
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { Tool } from './tool';
|
|
3
|
+
import TurndownService from 'turndown';
|
|
4
|
+
import DESCRIPTION from './webfetch.txt';
|
|
5
5
|
|
|
6
|
-
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
|
7
|
-
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
|
8
|
-
const MAX_TIMEOUT = 120 * 1000 // 2 minutes
|
|
6
|
+
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
7
|
+
const DEFAULT_TIMEOUT = 30 * 1000; // 30 seconds
|
|
8
|
+
const MAX_TIMEOUT = 120 * 1000; // 2 minutes
|
|
9
9
|
|
|
10
|
-
export const WebFetchTool = Tool.define(
|
|
10
|
+
export const WebFetchTool = Tool.define('webfetch', {
|
|
11
11
|
description: DESCRIPTION,
|
|
12
12
|
parameters: z.object({
|
|
13
|
-
url: z.string().describe(
|
|
13
|
+
url: z.string().describe('The URL to fetch content from'),
|
|
14
14
|
format: z
|
|
15
|
-
.enum([
|
|
16
|
-
.describe(
|
|
17
|
-
|
|
15
|
+
.enum(['text', 'markdown', 'html'])
|
|
16
|
+
.describe(
|
|
17
|
+
'The format to return the content in (text, markdown, or html)'
|
|
18
|
+
),
|
|
19
|
+
timeout: z
|
|
20
|
+
.number()
|
|
21
|
+
.describe('Optional timeout in seconds (max 120)')
|
|
22
|
+
.optional(),
|
|
18
23
|
}),
|
|
19
24
|
async execute(params, ctx) {
|
|
20
25
|
// Validate URL
|
|
21
|
-
if (
|
|
22
|
-
|
|
26
|
+
if (
|
|
27
|
+
!params.url.startsWith('http://') &&
|
|
28
|
+
!params.url.startsWith('https://')
|
|
29
|
+
) {
|
|
30
|
+
throw new Error('URL must start with http:// or https://');
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
// No restrictions - unrestricted web fetch
|
|
26
|
-
const timeout = Math.min(
|
|
34
|
+
const timeout = Math.min(
|
|
35
|
+
(params.timeout ?? DEFAULT_TIMEOUT / 1000) * 1000,
|
|
36
|
+
MAX_TIMEOUT
|
|
37
|
+
);
|
|
27
38
|
|
|
28
|
-
const controller = new AbortController()
|
|
29
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
30
41
|
|
|
31
42
|
// Build Accept header based on requested format with q parameters for fallbacks
|
|
32
|
-
let acceptHeader =
|
|
43
|
+
let acceptHeader = '*/*';
|
|
33
44
|
switch (params.format) {
|
|
34
|
-
case
|
|
35
|
-
acceptHeader =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
case 'markdown':
|
|
46
|
+
acceptHeader =
|
|
47
|
+
'text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1';
|
|
48
|
+
break;
|
|
49
|
+
case 'text':
|
|
50
|
+
acceptHeader =
|
|
51
|
+
'text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1';
|
|
52
|
+
break;
|
|
53
|
+
case 'html':
|
|
54
|
+
acceptHeader =
|
|
55
|
+
'text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1';
|
|
56
|
+
break;
|
|
43
57
|
default:
|
|
44
58
|
acceptHeader =
|
|
45
|
-
|
|
59
|
+
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8';
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
const response = await fetch(params.url, {
|
|
49
63
|
signal: AbortSignal.any([controller.signal, ctx.abort]),
|
|
50
64
|
headers: {
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
'User-Agent':
|
|
66
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
53
67
|
Accept: acceptHeader,
|
|
54
|
-
|
|
68
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
55
69
|
},
|
|
56
|
-
})
|
|
70
|
+
});
|
|
57
71
|
|
|
58
|
-
clearTimeout(timeoutId)
|
|
72
|
+
clearTimeout(timeoutId);
|
|
59
73
|
|
|
60
74
|
if (!response.ok) {
|
|
61
|
-
throw new Error(`Request failed with status code: ${response.status}`)
|
|
75
|
+
throw new Error(`Request failed with status code: ${response.status}`);
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
// Check content length
|
|
65
|
-
const contentLength = response.headers.get(
|
|
79
|
+
const contentLength = response.headers.get('content-length');
|
|
66
80
|
if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE) {
|
|
67
|
-
throw new Error(
|
|
81
|
+
throw new Error('Response too large (exceeds 5MB limit)');
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
84
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
71
85
|
if (arrayBuffer.byteLength > MAX_RESPONSE_SIZE) {
|
|
72
|
-
throw new Error(
|
|
86
|
+
throw new Error('Response too large (exceeds 5MB limit)');
|
|
73
87
|
}
|
|
74
88
|
|
|
75
|
-
const content = new TextDecoder().decode(arrayBuffer)
|
|
76
|
-
const contentType = response.headers.get(
|
|
89
|
+
const content = new TextDecoder().decode(arrayBuffer);
|
|
90
|
+
const contentType = response.headers.get('content-type') || '';
|
|
77
91
|
|
|
78
|
-
const title = `${params.url} (${contentType})
|
|
92
|
+
const title = `${params.url} (${contentType})`;
|
|
79
93
|
|
|
80
94
|
// Handle content based on requested format and actual content type
|
|
81
95
|
switch (params.format) {
|
|
82
|
-
case
|
|
83
|
-
if (contentType.includes(
|
|
84
|
-
const markdown = convertHTMLToMarkdown(content)
|
|
96
|
+
case 'markdown':
|
|
97
|
+
if (contentType.includes('text/html')) {
|
|
98
|
+
const markdown = convertHTMLToMarkdown(content);
|
|
85
99
|
return {
|
|
86
100
|
output: markdown,
|
|
87
101
|
title,
|
|
88
102
|
metadata: {},
|
|
89
|
-
}
|
|
103
|
+
};
|
|
90
104
|
}
|
|
91
105
|
return {
|
|
92
106
|
output: content,
|
|
93
107
|
title,
|
|
94
108
|
metadata: {},
|
|
95
|
-
}
|
|
109
|
+
};
|
|
96
110
|
|
|
97
|
-
case
|
|
98
|
-
if (contentType.includes(
|
|
99
|
-
const text = await extractTextFromHTML(content)
|
|
111
|
+
case 'text':
|
|
112
|
+
if (contentType.includes('text/html')) {
|
|
113
|
+
const text = await extractTextFromHTML(content);
|
|
100
114
|
return {
|
|
101
115
|
output: text,
|
|
102
116
|
title,
|
|
103
117
|
metadata: {},
|
|
104
|
-
}
|
|
118
|
+
};
|
|
105
119
|
}
|
|
106
120
|
return {
|
|
107
121
|
output: content,
|
|
108
122
|
title,
|
|
109
123
|
metadata: {},
|
|
110
|
-
}
|
|
124
|
+
};
|
|
111
125
|
|
|
112
|
-
case
|
|
126
|
+
case 'html':
|
|
113
127
|
return {
|
|
114
128
|
output: content,
|
|
115
129
|
title,
|
|
116
130
|
metadata: {},
|
|
117
|
-
}
|
|
131
|
+
};
|
|
118
132
|
|
|
119
133
|
default:
|
|
120
134
|
return {
|
|
121
135
|
output: content,
|
|
122
136
|
title,
|
|
123
137
|
metadata: {},
|
|
124
|
-
}
|
|
138
|
+
};
|
|
125
139
|
}
|
|
126
140
|
},
|
|
127
|
-
})
|
|
141
|
+
});
|
|
128
142
|
|
|
129
143
|
async function extractTextFromHTML(html: string) {
|
|
130
|
-
let text =
|
|
131
|
-
let skipContent = false
|
|
144
|
+
let text = '';
|
|
145
|
+
let skipContent = false;
|
|
132
146
|
|
|
133
147
|
const rewriter = new HTMLRewriter()
|
|
134
|
-
.on(
|
|
148
|
+
.on('script, style, noscript, iframe, object, embed', {
|
|
135
149
|
element() {
|
|
136
|
-
skipContent = true
|
|
150
|
+
skipContent = true;
|
|
137
151
|
},
|
|
138
152
|
text() {
|
|
139
153
|
// Skip text content inside these elements
|
|
140
154
|
},
|
|
141
155
|
})
|
|
142
|
-
.on(
|
|
156
|
+
.on('*', {
|
|
143
157
|
element(element) {
|
|
144
158
|
// Reset skip flag when entering other elements
|
|
145
|
-
if (
|
|
146
|
-
|
|
159
|
+
if (
|
|
160
|
+
![
|
|
161
|
+
'script',
|
|
162
|
+
'style',
|
|
163
|
+
'noscript',
|
|
164
|
+
'iframe',
|
|
165
|
+
'object',
|
|
166
|
+
'embed',
|
|
167
|
+
].includes(element.tagName)
|
|
168
|
+
) {
|
|
169
|
+
skipContent = false;
|
|
147
170
|
}
|
|
148
171
|
},
|
|
149
172
|
text(input) {
|
|
150
173
|
if (!skipContent) {
|
|
151
|
-
text += input.text
|
|
174
|
+
text += input.text;
|
|
152
175
|
}
|
|
153
176
|
},
|
|
154
177
|
})
|
|
155
|
-
.transform(new Response(html))
|
|
178
|
+
.transform(new Response(html));
|
|
156
179
|
|
|
157
|
-
await rewriter.text()
|
|
158
|
-
return text.trim()
|
|
180
|
+
await rewriter.text();
|
|
181
|
+
return text.trim();
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
function convertHTMLToMarkdown(html: string): string {
|
|
162
185
|
const turndownService = new TurndownService({
|
|
163
|
-
headingStyle:
|
|
164
|
-
hr:
|
|
165
|
-
bulletListMarker:
|
|
166
|
-
codeBlockStyle:
|
|
167
|
-
emDelimiter:
|
|
168
|
-
})
|
|
169
|
-
turndownService.remove([
|
|
170
|
-
return turndownService.turndown(html)
|
|
186
|
+
headingStyle: 'atx',
|
|
187
|
+
hr: '---',
|
|
188
|
+
bulletListMarker: '-',
|
|
189
|
+
codeBlockStyle: 'fenced',
|
|
190
|
+
emDelimiter: '*',
|
|
191
|
+
});
|
|
192
|
+
turndownService.remove(['script', 'style', 'meta', 'link']);
|
|
193
|
+
return turndownService.turndown(html);
|
|
171
194
|
}
|
package/src/tool/websearch.ts
CHANGED
|
@@ -1,133 +1,147 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import { Tool } from
|
|
3
|
-
import DESCRIPTION from
|
|
4
|
-
import { Config } from
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { Tool } from './tool';
|
|
3
|
+
import DESCRIPTION from './websearch.txt';
|
|
4
|
+
import { Config } from '../config/config';
|
|
5
5
|
|
|
6
6
|
const API_CONFIG = {
|
|
7
|
-
BASE_URL:
|
|
7
|
+
BASE_URL: 'https://mcp.exa.ai',
|
|
8
8
|
ENDPOINTS: {
|
|
9
|
-
SEARCH:
|
|
9
|
+
SEARCH: '/mcp',
|
|
10
10
|
},
|
|
11
11
|
DEFAULT_NUM_RESULTS: 8,
|
|
12
|
-
} as const
|
|
12
|
+
} as const;
|
|
13
13
|
|
|
14
14
|
interface McpSearchRequest {
|
|
15
|
-
jsonrpc: string
|
|
16
|
-
id: number
|
|
17
|
-
method: string
|
|
15
|
+
jsonrpc: string;
|
|
16
|
+
id: number;
|
|
17
|
+
method: string;
|
|
18
18
|
params: {
|
|
19
|
-
name: string
|
|
19
|
+
name: string;
|
|
20
20
|
arguments: {
|
|
21
|
-
query: string
|
|
22
|
-
numResults?: number
|
|
23
|
-
livecrawl?:
|
|
24
|
-
type?:
|
|
25
|
-
contextMaxCharacters?: number
|
|
26
|
-
}
|
|
27
|
-
}
|
|
21
|
+
query: string;
|
|
22
|
+
numResults?: number;
|
|
23
|
+
livecrawl?: 'fallback' | 'preferred';
|
|
24
|
+
type?: 'auto' | 'fast' | 'deep';
|
|
25
|
+
contextMaxCharacters?: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
interface McpSearchResponse {
|
|
31
|
-
jsonrpc: string
|
|
31
|
+
jsonrpc: string;
|
|
32
32
|
result: {
|
|
33
33
|
content: Array<{
|
|
34
|
-
type: string
|
|
35
|
-
text: string
|
|
36
|
-
}
|
|
37
|
-
}
|
|
34
|
+
type: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export const WebSearchTool = Tool.define(
|
|
40
|
+
export const WebSearchTool = Tool.define('websearch', {
|
|
41
41
|
description: DESCRIPTION,
|
|
42
42
|
parameters: z.object({
|
|
43
|
-
query: z.string().describe(
|
|
44
|
-
numResults: z
|
|
43
|
+
query: z.string().describe('Websearch query'),
|
|
44
|
+
numResults: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Number of search results to return (default: 8)'),
|
|
45
48
|
livecrawl: z
|
|
46
|
-
.enum([
|
|
49
|
+
.enum(['fallback', 'preferred'])
|
|
47
50
|
.optional()
|
|
48
51
|
.describe(
|
|
49
|
-
"Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')"
|
|
52
|
+
"Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')"
|
|
50
53
|
),
|
|
51
54
|
type: z
|
|
52
|
-
.enum([
|
|
55
|
+
.enum(['auto', 'fast', 'deep'])
|
|
53
56
|
.optional()
|
|
54
|
-
.describe(
|
|
57
|
+
.describe(
|
|
58
|
+
"Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"
|
|
59
|
+
),
|
|
55
60
|
contextMaxCharacters: z
|
|
56
61
|
.number()
|
|
57
62
|
.optional()
|
|
58
|
-
.describe(
|
|
63
|
+
.describe(
|
|
64
|
+
'Maximum characters for context string optimized for LLMs (default: 10000)'
|
|
65
|
+
),
|
|
59
66
|
}),
|
|
60
67
|
async execute(params, ctx) {
|
|
61
68
|
// No restrictions - unrestricted web search
|
|
62
69
|
const searchRequest: McpSearchRequest = {
|
|
63
|
-
jsonrpc:
|
|
70
|
+
jsonrpc: '2.0',
|
|
64
71
|
id: 1,
|
|
65
|
-
method:
|
|
72
|
+
method: 'tools/call',
|
|
66
73
|
params: {
|
|
67
|
-
name:
|
|
74
|
+
name: 'web_search_exa',
|
|
68
75
|
arguments: {
|
|
69
76
|
query: params.query,
|
|
70
|
-
type: params.type ||
|
|
77
|
+
type: params.type || 'auto',
|
|
71
78
|
numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
|
|
72
|
-
livecrawl: params.livecrawl ||
|
|
79
|
+
livecrawl: params.livecrawl || 'fallback',
|
|
73
80
|
contextMaxCharacters: params.contextMaxCharacters,
|
|
74
81
|
},
|
|
75
82
|
},
|
|
76
|
-
}
|
|
83
|
+
};
|
|
77
84
|
|
|
78
|
-
const controller = new AbortController()
|
|
79
|
-
const timeoutId = setTimeout(() => controller.abort(), 25000)
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeoutId = setTimeout(() => controller.abort(), 25000);
|
|
80
87
|
|
|
81
88
|
try {
|
|
82
89
|
const headers: Record<string, string> = {
|
|
83
|
-
accept:
|
|
84
|
-
|
|
85
|
-
}
|
|
90
|
+
accept: 'application/json, text/event-stream',
|
|
91
|
+
'content-type': 'application/json',
|
|
92
|
+
};
|
|
86
93
|
|
|
87
|
-
const response = await fetch(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const response = await fetch(
|
|
95
|
+
`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SEARCH}`,
|
|
96
|
+
{
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers,
|
|
99
|
+
body: JSON.stringify(searchRequest),
|
|
100
|
+
signal: AbortSignal.any([controller.signal, ctx.abort]),
|
|
101
|
+
}
|
|
102
|
+
);
|
|
93
103
|
|
|
94
|
-
clearTimeout(timeoutId)
|
|
104
|
+
clearTimeout(timeoutId);
|
|
95
105
|
|
|
96
106
|
if (!response.ok) {
|
|
97
|
-
const errorText = await response.text()
|
|
98
|
-
throw new Error(`Search error (${response.status}): ${errorText}`)
|
|
107
|
+
const errorText = await response.text();
|
|
108
|
+
throw new Error(`Search error (${response.status}): ${errorText}`);
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
const responseText = await response.text()
|
|
111
|
+
const responseText = await response.text();
|
|
102
112
|
|
|
103
113
|
// Parse SSE response
|
|
104
|
-
const lines = responseText.split(
|
|
114
|
+
const lines = responseText.split('\n');
|
|
105
115
|
for (const line of lines) {
|
|
106
|
-
if (line.startsWith(
|
|
107
|
-
const data: McpSearchResponse = JSON.parse(line.substring(6))
|
|
108
|
-
if (
|
|
116
|
+
if (line.startsWith('data: ')) {
|
|
117
|
+
const data: McpSearchResponse = JSON.parse(line.substring(6));
|
|
118
|
+
if (
|
|
119
|
+
data.result &&
|
|
120
|
+
data.result.content &&
|
|
121
|
+
data.result.content.length > 0
|
|
122
|
+
) {
|
|
109
123
|
return {
|
|
110
124
|
output: data.result.content[0].text,
|
|
111
125
|
title: `Web search: ${params.query}`,
|
|
112
126
|
metadata: {},
|
|
113
|
-
}
|
|
127
|
+
};
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
return {
|
|
119
|
-
output:
|
|
133
|
+
output: 'No search results found. Please try a different query.',
|
|
120
134
|
title: `Web search: ${params.query}`,
|
|
121
135
|
metadata: {},
|
|
122
|
-
}
|
|
136
|
+
};
|
|
123
137
|
} catch (error) {
|
|
124
|
-
clearTimeout(timeoutId)
|
|
138
|
+
clearTimeout(timeoutId);
|
|
125
139
|
|
|
126
|
-
if (error instanceof Error && error.name ===
|
|
127
|
-
throw new Error(
|
|
140
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
141
|
+
throw new Error('Search request timed out');
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
throw error
|
|
144
|
+
throw error;
|
|
131
145
|
}
|
|
132
146
|
},
|
|
133
|
-
})
|
|
147
|
+
});
|
package/src/tool/write.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { Tool } from
|
|
4
|
-
import DESCRIPTION from
|
|
5
|
-
import { Instance } from
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { Tool } from './tool';
|
|
4
|
+
import DESCRIPTION from './write.txt';
|
|
5
|
+
import { Instance } from '../project/instance';
|
|
6
6
|
|
|
7
|
-
export const WriteTool = Tool.define(
|
|
7
|
+
export const WriteTool = Tool.define('write', {
|
|
8
8
|
description: DESCRIPTION,
|
|
9
9
|
parameters: z.object({
|
|
10
|
-
content: z.string().describe(
|
|
11
|
-
filePath: z
|
|
10
|
+
content: z.string().describe('The content to write to the file'),
|
|
11
|
+
filePath: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe(
|
|
14
|
+
'The absolute path to the file to write (must be absolute, not relative)'
|
|
15
|
+
),
|
|
12
16
|
}),
|
|
13
17
|
async execute(params, ctx) {
|
|
14
18
|
// No restrictions - unrestricted file system access
|
|
15
|
-
const filepath = path.isAbsolute(params.filePath)
|
|
19
|
+
const filepath = path.isAbsolute(params.filePath)
|
|
20
|
+
? params.filePath
|
|
21
|
+
: path.join(Instance.directory, params.filePath);
|
|
16
22
|
|
|
17
|
-
const file = Bun.file(filepath)
|
|
18
|
-
const exists = await file.exists()
|
|
23
|
+
const file = Bun.file(filepath);
|
|
24
|
+
const exists = await file.exists();
|
|
19
25
|
|
|
20
26
|
// Write the file without permission checks
|
|
21
|
-
await Bun.write(filepath, params.content)
|
|
27
|
+
await Bun.write(filepath, params.content);
|
|
22
28
|
|
|
23
29
|
return {
|
|
24
30
|
title: path.relative(Instance.worktree, filepath),
|
|
@@ -27,7 +33,7 @@ export const WriteTool = Tool.define("write", {
|
|
|
27
33
|
filepath,
|
|
28
34
|
exists: exists,
|
|
29
35
|
},
|
|
30
|
-
output:
|
|
31
|
-
}
|
|
36
|
+
output: '',
|
|
37
|
+
};
|
|
32
38
|
},
|
|
33
|
-
})
|
|
39
|
+
});
|
package/src/util/binary.ts
CHANGED
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
export namespace Binary {
|
|
2
|
-
export function search<T>(
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export function search<T>(
|
|
3
|
+
array: T[],
|
|
4
|
+
id: string,
|
|
5
|
+
compare: (item: T) => string
|
|
6
|
+
): { found: boolean; index: number } {
|
|
7
|
+
let left = 0;
|
|
8
|
+
let right = array.length - 1;
|
|
5
9
|
|
|
6
10
|
while (left <= right) {
|
|
7
|
-
const mid = Math.floor((left + right) / 2)
|
|
8
|
-
const midId = compare(array[mid])
|
|
11
|
+
const mid = Math.floor((left + right) / 2);
|
|
12
|
+
const midId = compare(array[mid]);
|
|
9
13
|
|
|
10
14
|
if (midId === id) {
|
|
11
|
-
return { found: true, index: mid }
|
|
15
|
+
return { found: true, index: mid };
|
|
12
16
|
} else if (midId < id) {
|
|
13
|
-
left = mid + 1
|
|
17
|
+
left = mid + 1;
|
|
14
18
|
} else {
|
|
15
|
-
right = mid - 1
|
|
19
|
+
right = mid - 1;
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
return { found: false, index: left }
|
|
23
|
+
return { found: false, index: left };
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
export function insert<T>(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
export function insert<T>(
|
|
27
|
+
array: T[],
|
|
28
|
+
item: T,
|
|
29
|
+
compare: (item: T) => string
|
|
30
|
+
): T[] {
|
|
31
|
+
const id = compare(item);
|
|
32
|
+
let left = 0;
|
|
33
|
+
let right = array.length;
|
|
26
34
|
|
|
27
35
|
while (left < right) {
|
|
28
|
-
const mid = Math.floor((left + right) / 2)
|
|
29
|
-
const midId = compare(array[mid])
|
|
36
|
+
const mid = Math.floor((left + right) / 2);
|
|
37
|
+
const midId = compare(array[mid]);
|
|
30
38
|
|
|
31
39
|
if (midId < id) {
|
|
32
|
-
left = mid + 1
|
|
40
|
+
left = mid + 1;
|
|
33
41
|
} else {
|
|
34
|
-
right = mid
|
|
42
|
+
right = mid;
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
array.splice(left, 0, item)
|
|
39
|
-
return array
|
|
46
|
+
array.splice(left, 0, item);
|
|
47
|
+
return array;
|
|
40
48
|
}
|
|
41
49
|
}
|
package/src/util/context.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
2
|
|
|
3
3
|
export namespace Context {
|
|
4
4
|
export class NotFound extends Error {
|
|
5
5
|
constructor(public override readonly name: string) {
|
|
6
|
-
super(`No context found for ${name}`)
|
|
6
|
+
super(`No context found for ${name}`);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function create<T>(name: string) {
|
|
11
|
-
const storage = new AsyncLocalStorage<T>()
|
|
11
|
+
const storage = new AsyncLocalStorage<T>();
|
|
12
12
|
return {
|
|
13
13
|
use() {
|
|
14
|
-
const result = storage.getStore()
|
|
14
|
+
const result = storage.getStore();
|
|
15
15
|
if (!result) {
|
|
16
|
-
throw new NotFound(name)
|
|
16
|
+
throw new NotFound(name);
|
|
17
17
|
}
|
|
18
|
-
return result
|
|
18
|
+
return result;
|
|
19
19
|
},
|
|
20
20
|
provide<R>(value: T, fn: () => R) {
|
|
21
|
-
return storage.run(value, fn)
|
|
21
|
+
return storage.run(value, fn);
|
|
22
22
|
},
|
|
23
|
-
}
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
25
|
}
|
package/src/util/defer.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export function defer<T extends () => void | Promise<void>>(
|
|
2
|
-
fn: T
|
|
3
|
-
): T extends () => Promise<void>
|
|
2
|
+
fn: T
|
|
3
|
+
): T extends () => Promise<void>
|
|
4
|
+
? { [Symbol.asyncDispose]: () => Promise<void> }
|
|
5
|
+
: { [Symbol.dispose]: () => void } {
|
|
4
6
|
return {
|
|
5
7
|
[Symbol.dispose]() {
|
|
6
|
-
fn()
|
|
8
|
+
fn();
|
|
7
9
|
},
|
|
8
10
|
[Symbol.asyncDispose]() {
|
|
9
|
-
return Promise.resolve(fn())
|
|
11
|
+
return Promise.resolve(fn());
|
|
10
12
|
},
|
|
11
|
-
} as any
|
|
13
|
+
} as any;
|
|
12
14
|
}
|