@cortexkit/aft-opencode 0.18.2 → 0.18.4
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/dist/bg-notifications.d.ts +13 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge.d.ts +26 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +450 -73
- package/dist/logger.d.ts +3 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.d.ts.map +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/pool.d.ts +2 -0
- package/dist/pool.d.ts.map +1 -1
- package/dist/shared/last-user-model.d.ts +38 -0
- package/dist/shared/last-user-model.d.ts.map +1 -0
- package/dist/shared/rpc-client.d.ts +2 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/url-fetch.d.ts +16 -0
- package/dist/shared/url-fetch.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +2 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +14 -0
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/refactoring.d.ts.map +1 -1
- package/dist/tui.js +38 -12
- package/dist/workflow-hints.d.ts +25 -0
- package/dist/workflow-hints.d.ts.map +1 -0
- package/package.json +8 -6
- package/src/logger.ts +9 -4
- package/src/shared/last-user-model.ts +133 -0
- package/src/shared/rpc-client.ts +38 -5
- package/src/shared/rpc-server.ts +3 -2
- package/src/shared/url-fetch.ts +53 -8
package/src/shared/url-fetch.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "node:fs";
|
|
11
11
|
import { isIP } from "node:net";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { Agent, type Dispatcher, fetch as undiciFetch } from "undici";
|
|
13
14
|
import { log, warn } from "../logger";
|
|
14
15
|
|
|
15
16
|
/** Max response body size (10 MB) */
|
|
@@ -22,8 +23,24 @@ const MAX_REDIRECTS = 5;
|
|
|
22
23
|
|
|
23
24
|
interface FetchUrlOptions {
|
|
24
25
|
allowPrivate?: boolean;
|
|
26
|
+
/** Test-only injection point. Production fetches use undici with DNS pinning. */
|
|
27
|
+
fetchImpl?: FetchImpl;
|
|
28
|
+
/** Test-only injection point. Production DNS resolution uses node:dns/promises.lookup. */
|
|
29
|
+
lookup?: LookupFn;
|
|
30
|
+
/** Test-only injection point for observing the DNS-pinned dispatcher. */
|
|
31
|
+
dispatcherFactory?: (validatedIp: string) => Dispatcher;
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
type LookupFn = typeof lookup;
|
|
35
|
+
interface FetchInit {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
redirect?: "manual";
|
|
38
|
+
dispatcher?: Dispatcher;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type FetchImpl = (input: string, init: FetchInit) => Promise<Response>;
|
|
43
|
+
|
|
27
44
|
interface CacheMeta {
|
|
28
45
|
url: string;
|
|
29
46
|
contentType: string;
|
|
@@ -163,11 +180,15 @@ function isPrivateIp(address: string): boolean {
|
|
|
163
180
|
return true;
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
async function assertPublicUrl(
|
|
183
|
+
async function assertPublicUrl(
|
|
184
|
+
url: URL,
|
|
185
|
+
allowPrivate: boolean,
|
|
186
|
+
dnsLookup: LookupFn = lookup,
|
|
187
|
+
): Promise<string | undefined> {
|
|
167
188
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
168
189
|
throw new Error(`Only http:// and https:// URLs are supported, got: ${url.protocol}`);
|
|
169
190
|
}
|
|
170
|
-
if (allowPrivate) return;
|
|
191
|
+
if (allowPrivate) return undefined;
|
|
171
192
|
|
|
172
193
|
// URL.hostname returns IPv6 literals wrapped in brackets ("[::1]") per WHATWG.
|
|
173
194
|
// Strip them before checking the address.
|
|
@@ -180,15 +201,29 @@ async function assertPublicUrl(url: URL, allowPrivate: boolean): Promise<void> {
|
|
|
180
201
|
if (isPrivateIp(hostname)) {
|
|
181
202
|
throw new Error(`Blocked private URL host ${url.hostname} (${hostname})`);
|
|
182
203
|
}
|
|
183
|
-
return;
|
|
204
|
+
return hostname;
|
|
184
205
|
}
|
|
185
206
|
|
|
186
|
-
const addresses = await
|
|
207
|
+
const addresses = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
187
208
|
for (const { address } of addresses) {
|
|
188
209
|
if (isPrivateIp(address)) {
|
|
189
210
|
throw new Error(`Blocked private URL host ${url.hostname} (${address})`);
|
|
190
211
|
}
|
|
191
212
|
}
|
|
213
|
+
if (addresses.length === 0) {
|
|
214
|
+
throw new Error(`Failed to resolve URL host ${url.hostname}`);
|
|
215
|
+
}
|
|
216
|
+
return addresses[0].address;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createPinnedDispatcher(validatedIp: string): Dispatcher {
|
|
220
|
+
return new Agent({
|
|
221
|
+
connect: {
|
|
222
|
+
lookup: (_hostname, _opts, callback) => {
|
|
223
|
+
callback(null, validatedIp, validatedIp.includes(":") ? 6 : 4);
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
192
227
|
}
|
|
193
228
|
|
|
194
229
|
function resolveRedirectUrl(currentUrl: URL, location: string | null): URL {
|
|
@@ -198,19 +233,29 @@ function resolveRedirectUrl(currentUrl: URL, location: string | null): URL {
|
|
|
198
233
|
return new URL(location, currentUrl);
|
|
199
234
|
}
|
|
200
235
|
|
|
201
|
-
async function fetchWithRedirects(
|
|
236
|
+
async function fetchWithRedirects(
|
|
237
|
+
startUrl: URL,
|
|
238
|
+
allowPrivate: boolean,
|
|
239
|
+
deps: Pick<FetchUrlOptions, "dispatcherFactory" | "fetchImpl" | "lookup"> = {},
|
|
240
|
+
): Promise<Response> {
|
|
202
241
|
let currentUrl = startUrl;
|
|
242
|
+
const fetchImpl = deps.fetchImpl ?? (undiciFetch as FetchImpl);
|
|
243
|
+
const dnsLookup = deps.lookup ?? lookup;
|
|
203
244
|
|
|
204
245
|
for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) {
|
|
205
|
-
await assertPublicUrl(currentUrl, allowPrivate);
|
|
246
|
+
const validatedIp = await assertPublicUrl(currentUrl, allowPrivate, dnsLookup);
|
|
247
|
+
const dispatcher = validatedIp
|
|
248
|
+
? (deps.dispatcherFactory ?? createPinnedDispatcher)(validatedIp)
|
|
249
|
+
: undefined;
|
|
206
250
|
|
|
207
251
|
const controller = new AbortController();
|
|
208
252
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
209
253
|
let response: Response;
|
|
210
254
|
try {
|
|
211
|
-
response = await
|
|
255
|
+
response = await fetchImpl(currentUrl.href, {
|
|
212
256
|
signal: controller.signal,
|
|
213
257
|
redirect: "manual",
|
|
258
|
+
dispatcher,
|
|
214
259
|
headers: {
|
|
215
260
|
"user-agent": "aft-opencode-plugin",
|
|
216
261
|
// Prioritize markdown for content-negotiating servers (GitHub API, many docs sites).
|
|
@@ -316,7 +361,7 @@ export async function fetchUrlToTempFile(
|
|
|
316
361
|
}
|
|
317
362
|
|
|
318
363
|
log(`Fetching URL: ${url}`);
|
|
319
|
-
const response = await fetchWithRedirects(parsed, allowPrivate);
|
|
364
|
+
const response = await fetchWithRedirects(parsed, allowPrivate, options);
|
|
320
365
|
|
|
321
366
|
if (!response.ok) {
|
|
322
367
|
throw new Error(`HTTP ${response.status} ${response.statusText} fetching ${url}`);
|