@jupyterlite/ai 0.11.1 → 0.13.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.
- package/lib/agent.d.ts +61 -7
- package/lib/agent.js +286 -103
- package/lib/chat-commands/clear.d.ts +8 -0
- package/lib/chat-commands/clear.js +30 -0
- package/lib/chat-commands/index.d.ts +2 -0
- package/lib/chat-commands/index.js +2 -0
- package/lib/chat-commands/skills.d.ts +19 -0
- package/lib/chat-commands/skills.js +57 -0
- package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
- package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
- package/lib/chat-model.d.ts +16 -0
- package/lib/chat-model.js +191 -11
- package/lib/completion/completion-provider.d.ts +1 -1
- package/lib/completion/completion-provider.js +14 -2
- package/lib/components/model-select.js +4 -4
- package/lib/components/tool-select.d.ts +11 -2
- package/lib/components/tool-select.js +77 -18
- package/lib/index.d.ts +3 -3
- package/lib/index.js +311 -72
- package/lib/models/settings-model.d.ts +3 -0
- package/lib/models/settings-model.js +63 -14
- package/lib/providers/built-in-providers.js +12 -7
- package/lib/providers/provider-tools.d.ts +36 -0
- package/lib/providers/provider-tools.js +93 -0
- package/lib/rendered-message-outputarea.d.ts +24 -0
- package/lib/rendered-message-outputarea.js +48 -0
- package/lib/skills/index.d.ts +4 -0
- package/lib/skills/index.js +7 -0
- package/lib/skills/parse-skill.d.ts +25 -0
- package/lib/skills/parse-skill.js +69 -0
- package/lib/skills/skill-loader.d.ts +25 -0
- package/lib/skills/skill-loader.js +133 -0
- package/lib/skills/skill-registry.d.ts +31 -0
- package/lib/skills/skill-registry.js +100 -0
- package/lib/skills/types.d.ts +29 -0
- package/lib/skills/types.js +5 -0
- package/lib/tokens.d.ts +77 -7
- package/lib/tokens.js +6 -1
- package/lib/tools/commands.js +4 -2
- package/lib/tools/skills.d.ts +9 -0
- package/lib/tools/skills.js +73 -0
- package/lib/tools/web.d.ts +8 -0
- package/lib/tools/web.js +196 -0
- package/lib/widgets/ai-settings.d.ts +1 -1
- package/lib/widgets/ai-settings.js +157 -38
- package/lib/widgets/main-area-chat.d.ts +6 -0
- package/lib/widgets/main-area-chat.js +28 -0
- package/lib/widgets/provider-config-dialog.js +207 -4
- package/package.json +18 -11
- package/schema/settings-model.json +97 -2
- package/src/agent.ts +397 -123
- package/src/chat-commands/clear.ts +46 -0
- package/src/chat-commands/index.ts +2 -0
- package/src/chat-commands/skills.ts +87 -0
- package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
- package/src/chat-model.ts +270 -23
- package/src/completion/completion-provider.ts +26 -12
- package/src/components/model-select.tsx +4 -5
- package/src/components/tool-select.tsx +110 -7
- package/src/index.ts +395 -87
- package/src/models/settings-model.ts +70 -15
- package/src/providers/built-in-providers.ts +12 -7
- package/src/providers/provider-tools.ts +179 -0
- package/src/rendered-message-outputarea.ts +62 -0
- package/src/skills/index.ts +14 -0
- package/src/skills/parse-skill.ts +91 -0
- package/src/skills/skill-loader.ts +175 -0
- package/src/skills/skill-registry.ts +137 -0
- package/src/skills/types.ts +37 -0
- package/src/tokens.ts +109 -9
- package/src/tools/commands.ts +4 -2
- package/src/tools/skills.ts +84 -0
- package/src/tools/web.ts +238 -0
- package/src/widgets/ai-settings.tsx +357 -77
- package/src/widgets/main-area-chat.ts +34 -1
- package/src/widgets/provider-config-dialog.tsx +496 -3
package/src/tools/web.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ITool } from '../tokens';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MAX_CONTENT_CHARS = 20000;
|
|
6
|
+
const MAX_ALLOWED_CONTENT_CHARS = 100000;
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
8
|
+
const MAX_TIMEOUT_MS = 120000;
|
|
9
|
+
|
|
10
|
+
interface IReadBodyResult {
|
|
11
|
+
content: string;
|
|
12
|
+
isTruncated: boolean;
|
|
13
|
+
totalChars: number;
|
|
14
|
+
totalCharsExact: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read response body text with a character cap.
|
|
19
|
+
*
|
|
20
|
+
* Stops early once the cap is reached to avoid buffering arbitrarily large
|
|
21
|
+
* payloads in memory.
|
|
22
|
+
*/
|
|
23
|
+
async function readResponseText(
|
|
24
|
+
response: Response,
|
|
25
|
+
maxContentChars: number
|
|
26
|
+
): Promise<IReadBodyResult> {
|
|
27
|
+
if (!response.body) {
|
|
28
|
+
const body = await response.text();
|
|
29
|
+
return {
|
|
30
|
+
content: body.slice(0, maxContentChars),
|
|
31
|
+
isTruncated: body.length > maxContentChars,
|
|
32
|
+
totalChars: body.length,
|
|
33
|
+
totalCharsExact: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const reader = response.body.getReader();
|
|
38
|
+
const decoder = new TextDecoder();
|
|
39
|
+
let content = '';
|
|
40
|
+
let totalChars = 0;
|
|
41
|
+
let isTruncated = false;
|
|
42
|
+
let done = false;
|
|
43
|
+
|
|
44
|
+
while (!done) {
|
|
45
|
+
const readResult = await reader.read();
|
|
46
|
+
done = readResult.done;
|
|
47
|
+
if (done) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const chunk = decoder.decode(readResult.value, { stream: true });
|
|
52
|
+
if (!chunk) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
totalChars += chunk.length;
|
|
57
|
+
|
|
58
|
+
if (!isTruncated) {
|
|
59
|
+
const remaining = maxContentChars - content.length;
|
|
60
|
+
if (chunk.length <= remaining) {
|
|
61
|
+
content += chunk;
|
|
62
|
+
} else {
|
|
63
|
+
content += chunk.slice(0, remaining);
|
|
64
|
+
isTruncated = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isTruncated) {
|
|
69
|
+
await reader.cancel();
|
|
70
|
+
return {
|
|
71
|
+
content,
|
|
72
|
+
isTruncated: true,
|
|
73
|
+
totalChars,
|
|
74
|
+
totalCharsExact: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const tail = decoder.decode();
|
|
80
|
+
if (tail) {
|
|
81
|
+
totalChars += tail.length;
|
|
82
|
+
const remaining = maxContentChars - content.length;
|
|
83
|
+
if (tail.length <= remaining) {
|
|
84
|
+
content += tail;
|
|
85
|
+
} else {
|
|
86
|
+
content += tail.slice(0, remaining);
|
|
87
|
+
isTruncated = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
content,
|
|
93
|
+
isTruncated,
|
|
94
|
+
totalChars,
|
|
95
|
+
totalCharsExact: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create a browser-native URL fetch tool.
|
|
101
|
+
*
|
|
102
|
+
* This is best-effort and subject to normal browser constraints (CORS, CSP,
|
|
103
|
+
* mixed content, bot protections).
|
|
104
|
+
*/
|
|
105
|
+
export function createBrowserFetchTool(): ITool {
|
|
106
|
+
return tool({
|
|
107
|
+
title: 'Browser Fetch',
|
|
108
|
+
description:
|
|
109
|
+
'Fetch a URL directly from the browser using HTTP GET for exact URL inspection when CORS/access permits.',
|
|
110
|
+
inputSchema: z.object({
|
|
111
|
+
url: z.string().describe('HTTP(S) URL to fetch'),
|
|
112
|
+
maxContentChars: z
|
|
113
|
+
.number()
|
|
114
|
+
.int()
|
|
115
|
+
.min(1)
|
|
116
|
+
.max(MAX_ALLOWED_CONTENT_CHARS)
|
|
117
|
+
.optional()
|
|
118
|
+
.describe(
|
|
119
|
+
`Maximum number of response characters to return (default: ${DEFAULT_MAX_CONTENT_CHARS})`
|
|
120
|
+
),
|
|
121
|
+
timeoutMs: z
|
|
122
|
+
.number()
|
|
123
|
+
.int()
|
|
124
|
+
.min(1000)
|
|
125
|
+
.max(MAX_TIMEOUT_MS)
|
|
126
|
+
.optional()
|
|
127
|
+
.describe(
|
|
128
|
+
`Timeout in milliseconds (default: ${DEFAULT_TIMEOUT_MS}, max: ${MAX_TIMEOUT_MS})`
|
|
129
|
+
)
|
|
130
|
+
}),
|
|
131
|
+
execute: async (input: {
|
|
132
|
+
url: string;
|
|
133
|
+
maxContentChars?: number;
|
|
134
|
+
timeoutMs?: number;
|
|
135
|
+
}) => {
|
|
136
|
+
const maxContentChars = Math.min(
|
|
137
|
+
input.maxContentChars ?? DEFAULT_MAX_CONTENT_CHARS,
|
|
138
|
+
MAX_ALLOWED_CONTENT_CHARS
|
|
139
|
+
);
|
|
140
|
+
const timeoutMs = Math.min(
|
|
141
|
+
input.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
142
|
+
MAX_TIMEOUT_MS
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
let parsedUrl: URL;
|
|
146
|
+
try {
|
|
147
|
+
parsedUrl = new URL(input.url);
|
|
148
|
+
} catch {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
errorType: 'invalid_url',
|
|
152
|
+
error: 'Invalid URL format',
|
|
153
|
+
url: input.url
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
errorType: 'unsupported_protocol',
|
|
161
|
+
error: 'Only http:// and https:// URLs are supported',
|
|
162
|
+
url: input.url
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const controller = new AbortController();
|
|
167
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(parsedUrl.toString(), {
|
|
171
|
+
method: 'GET',
|
|
172
|
+
credentials: 'omit',
|
|
173
|
+
redirect: 'follow',
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
headers: {
|
|
176
|
+
Accept:
|
|
177
|
+
'text/html,text/plain,application/json,text/markdown,*/*;q=0.8'
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const contentType = response.headers.get('content-type') || '';
|
|
182
|
+
const contentLength = response.headers.get('content-length');
|
|
183
|
+
const body = await readResponseText(response, maxContentChars);
|
|
184
|
+
const success = response.ok;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
success,
|
|
188
|
+
url: response.url,
|
|
189
|
+
requestedUrl: parsedUrl.toString(),
|
|
190
|
+
status: response.status,
|
|
191
|
+
statusText: response.statusText,
|
|
192
|
+
contentType,
|
|
193
|
+
contentLength,
|
|
194
|
+
...(success
|
|
195
|
+
? {}
|
|
196
|
+
: {
|
|
197
|
+
errorType: 'http_error',
|
|
198
|
+
error: `HTTP ${response.status} ${response.statusText}`
|
|
199
|
+
}),
|
|
200
|
+
isTruncated: body.isTruncated,
|
|
201
|
+
returnedChars: body.content.length,
|
|
202
|
+
totalChars: body.totalChars,
|
|
203
|
+
totalCharsExact: body.totalCharsExact,
|
|
204
|
+
content: body.content,
|
|
205
|
+
limitations:
|
|
206
|
+
'Browser fetch is subject to CORS, site bot protections, and browser network policy.'
|
|
207
|
+
};
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if ((error as Error).name === 'AbortError') {
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
errorType: 'timeout',
|
|
213
|
+
error: `Request timed out after ${timeoutMs} ms`,
|
|
214
|
+
url: parsedUrl.toString()
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
errorType: 'network_or_cors',
|
|
221
|
+
error:
|
|
222
|
+
error instanceof Error && error.message
|
|
223
|
+
? error.message
|
|
224
|
+
: 'Fetch failed',
|
|
225
|
+
url: parsedUrl.toString(),
|
|
226
|
+
likelyCauses: [
|
|
227
|
+
'CORS blocked by the target website',
|
|
228
|
+
'DNS/network resolution failure',
|
|
229
|
+
'TLS/certificate issue',
|
|
230
|
+
'Target server rejected browser access'
|
|
231
|
+
]
|
|
232
|
+
};
|
|
233
|
+
} finally {
|
|
234
|
+
clearTimeout(timeoutHandle);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|