agent-shim 8.20.2 → 8.21.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/dist/cli.cjs +365 -48
- package/dist/index.cjs +344 -12
- package/dist/index.mjs +336 -12
- package/dist/types/index.d.ts +92 -10
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2719,22 +2719,22 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2719
2719
|
};
|
|
2720
2720
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
2721
2721
|
const previousSticky = lastPort;
|
|
2722
|
-
let
|
|
2722
|
+
let http3;
|
|
2723
2723
|
try {
|
|
2724
|
-
|
|
2724
|
+
http3 = await ports.startProviderListener(lastPort);
|
|
2725
2725
|
} catch (error) {
|
|
2726
2726
|
return fail(`could not start the provider listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2727
2727
|
}
|
|
2728
|
-
if (previousSticky !== void 0 &&
|
|
2729
|
-
ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(
|
|
2728
|
+
if (previousSticky !== void 0 && http3.port !== previousSticky) {
|
|
2729
|
+
ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http3.port)} (sessions launched against the old port are stale until they relaunch)`);
|
|
2730
2730
|
}
|
|
2731
|
-
lastPort =
|
|
2731
|
+
lastPort = http3.port;
|
|
2732
2732
|
const previousStickyConnect = lastConnectPort;
|
|
2733
2733
|
let connect3;
|
|
2734
2734
|
try {
|
|
2735
2735
|
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
2736
2736
|
} catch (error) {
|
|
2737
|
-
await
|
|
2737
|
+
await http3.close();
|
|
2738
2738
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2739
2739
|
}
|
|
2740
2740
|
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
@@ -2747,16 +2747,16 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2747
2747
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
2748
2748
|
} catch (error) {
|
|
2749
2749
|
await connect3.close();
|
|
2750
|
-
await
|
|
2750
|
+
await http3.close();
|
|
2751
2751
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2752
2752
|
}
|
|
2753
2753
|
if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
|
|
2754
2754
|
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2755
2755
|
}
|
|
2756
2756
|
lastDirectPort = direct.port;
|
|
2757
|
-
writeFrontDoorState(fs12, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port:
|
|
2757
|
+
writeFrontDoorState(fs12, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http3.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2758
2758
|
fs12.removeRecursive(paths.frontdoorLockFile);
|
|
2759
|
-
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(
|
|
2759
|
+
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http3.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
2760
2760
|
let idleSince;
|
|
2761
2761
|
let ticks = 0;
|
|
2762
2762
|
for (; ; ) {
|
|
@@ -2774,7 +2774,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2774
2774
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2775
2775
|
await direct.close();
|
|
2776
2776
|
await connect3.close();
|
|
2777
|
-
await
|
|
2777
|
+
await http3.close();
|
|
2778
2778
|
ports.closeRcStreamHub();
|
|
2779
2779
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, sticky());
|
|
2780
2780
|
return 0;
|
|
@@ -9988,6 +9988,252 @@ function createRcStreamHub(deps) {
|
|
|
9988
9988
|
};
|
|
9989
9989
|
}
|
|
9990
9990
|
|
|
9991
|
+
// src/frontdoor/rcWebFetch.ts
|
|
9992
|
+
import * as dns2 from "node:dns";
|
|
9993
|
+
import * as http2 from "node:http";
|
|
9994
|
+
import * as https3 from "node:https";
|
|
9995
|
+
import * as net3 from "node:net";
|
|
9996
|
+
var RC_WEB_FETCH_MAX_BYTES = 12582912;
|
|
9997
|
+
var RC_WEB_FETCH_DEADLINE_MS = 4e4;
|
|
9998
|
+
var RC_WEB_FETCH_MAX_REDIRECTS = 20;
|
|
9999
|
+
var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
|
|
10000
|
+
function rcWebFetchAllowsPrivateFromEnv(env) {
|
|
10001
|
+
return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
|
|
10002
|
+
}
|
|
10003
|
+
var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
|
|
10004
|
+
var ERROR_PREFIX = "web_fetch_";
|
|
10005
|
+
var V4_THIS_NETWORK_OCTET = 0;
|
|
10006
|
+
var V4_PRIVATE_A_OCTET = 10;
|
|
10007
|
+
var V4_LOOPBACK_OCTET = 127;
|
|
10008
|
+
var V4_CGNAT_OCTET = 100;
|
|
10009
|
+
var V4_CGNAT_SECOND_FROM = 64;
|
|
10010
|
+
var V4_CGNAT_SECOND_TO = 127;
|
|
10011
|
+
var V4_LINK_LOCAL_OCTET = 169;
|
|
10012
|
+
var V4_LINK_LOCAL_SECOND = 254;
|
|
10013
|
+
var V4_PRIVATE_B_OCTET = 172;
|
|
10014
|
+
var V4_PRIVATE_B_SECOND_FROM = 16;
|
|
10015
|
+
var V4_PRIVATE_B_SECOND_TO = 31;
|
|
10016
|
+
var V4_PRIVATE_C_OCTET = 192;
|
|
10017
|
+
var V4_PRIVATE_C_SECOND = 168;
|
|
10018
|
+
var V4_MULTICAST_FROM_OCTET = 224;
|
|
10019
|
+
var V6_UNIQUE_LOCAL_FROM = 64512;
|
|
10020
|
+
var V6_UNIQUE_LOCAL_TO = 65023;
|
|
10021
|
+
var V6_LINK_LOCAL_FROM = 65152;
|
|
10022
|
+
var V6_LINK_LOCAL_TO = 65215;
|
|
10023
|
+
var V6_MULTICAST_FROM = 65280;
|
|
10024
|
+
var IP_VERSION_4 = 4;
|
|
10025
|
+
var IP_VERSION_6 = 6;
|
|
10026
|
+
var HTTP_MOVED_PERMANENTLY = 301;
|
|
10027
|
+
var HTTP_FOUND = 302;
|
|
10028
|
+
var HTTP_SEE_OTHER = 303;
|
|
10029
|
+
var HTTP_TEMPORARY_REDIRECT = 307;
|
|
10030
|
+
var HTTP_PERMANENT_REDIRECT = 308;
|
|
10031
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE4 = 300;
|
|
10032
|
+
function refused(errorType, errorMessage2) {
|
|
10033
|
+
return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
|
|
10034
|
+
}
|
|
10035
|
+
function isPrivateAddress(address) {
|
|
10036
|
+
const version = net3.isIP(address);
|
|
10037
|
+
if (version === IP_VERSION_4) {
|
|
10038
|
+
const octets = address.split(".").map((part) => Number(part));
|
|
10039
|
+
const first = octets[0] ?? -1;
|
|
10040
|
+
const second = octets[1] ?? -1;
|
|
10041
|
+
if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
|
|
10042
|
+
return true;
|
|
10043
|
+
}
|
|
10044
|
+
if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
|
|
10045
|
+
return true;
|
|
10046
|
+
}
|
|
10047
|
+
if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
|
|
10048
|
+
return true;
|
|
10049
|
+
}
|
|
10050
|
+
if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
|
|
10051
|
+
return true;
|
|
10052
|
+
}
|
|
10053
|
+
if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
|
|
10054
|
+
return true;
|
|
10055
|
+
}
|
|
10056
|
+
return first >= V4_MULTICAST_FROM_OCTET;
|
|
10057
|
+
}
|
|
10058
|
+
if (version === IP_VERSION_6) {
|
|
10059
|
+
const lower = address.toLowerCase();
|
|
10060
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
|
|
10061
|
+
if (mapped !== null) {
|
|
10062
|
+
return isPrivateAddress(mapped[1] ?? "");
|
|
10063
|
+
}
|
|
10064
|
+
if (lower === "::" || lower === "::1") {
|
|
10065
|
+
return true;
|
|
10066
|
+
}
|
|
10067
|
+
const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
|
|
10068
|
+
if (Number.isNaN(firstGroup)) {
|
|
10069
|
+
return true;
|
|
10070
|
+
}
|
|
10071
|
+
if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
|
|
10072
|
+
return true;
|
|
10073
|
+
}
|
|
10074
|
+
if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
|
|
10075
|
+
return true;
|
|
10076
|
+
}
|
|
10077
|
+
return firstGroup >= V6_MULTICAST_FROM;
|
|
10078
|
+
}
|
|
10079
|
+
return true;
|
|
10080
|
+
}
|
|
10081
|
+
function isSuccessful4(status) {
|
|
10082
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE4;
|
|
10083
|
+
}
|
|
10084
|
+
function isRedirect(status) {
|
|
10085
|
+
return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
|
|
10086
|
+
}
|
|
10087
|
+
function readableContentType(contentType) {
|
|
10088
|
+
if (contentType === void 0 || contentType.trim() === "") {
|
|
10089
|
+
return true;
|
|
10090
|
+
}
|
|
10091
|
+
const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
10092
|
+
const slash = normalised.indexOf("/");
|
|
10093
|
+
const main = slash === -1 ? normalised : normalised.slice(0, slash);
|
|
10094
|
+
const sub = slash === -1 ? "" : normalised.slice(slash + 1);
|
|
10095
|
+
if (main === "text") {
|
|
10096
|
+
return true;
|
|
10097
|
+
}
|
|
10098
|
+
return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
|
|
10099
|
+
}
|
|
10100
|
+
async function readCapped(chunks) {
|
|
10101
|
+
const collected = [];
|
|
10102
|
+
let total = 0;
|
|
10103
|
+
for await (const chunk of chunks) {
|
|
10104
|
+
total += chunk.byteLength;
|
|
10105
|
+
if (total > RC_WEB_FETCH_MAX_BYTES) {
|
|
10106
|
+
return { tooLarge: true, text: "" };
|
|
10107
|
+
}
|
|
10108
|
+
collected.push(Buffer.from(chunk));
|
|
10109
|
+
}
|
|
10110
|
+
return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
|
|
10111
|
+
}
|
|
10112
|
+
async function checkTarget(url, deps) {
|
|
10113
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
10114
|
+
return refused("scheme", `the web-fetch proxy dials http and https only, not ${url.protocol}`);
|
|
10115
|
+
}
|
|
10116
|
+
if (deps.allowPrivate) {
|
|
10117
|
+
return void 0;
|
|
10118
|
+
}
|
|
10119
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
10120
|
+
let addresses;
|
|
10121
|
+
try {
|
|
10122
|
+
addresses = net3.isIP(hostname) === 0 ? await deps.lookup(hostname) : [hostname];
|
|
10123
|
+
} catch {
|
|
10124
|
+
return refused("resolve", `${hostname} did not resolve`);
|
|
10125
|
+
}
|
|
10126
|
+
for (const address of addresses) {
|
|
10127
|
+
if (isPrivateAddress(address)) {
|
|
10128
|
+
return refused("private_address", `${hostname} resolves to ${address}, a private address this web-fetch proxy refuses; set ${RC_WEB_FETCH_ALLOW_PRIVATE_ENV}=1 on the door if serving internal targets is intended`);
|
|
10129
|
+
}
|
|
10130
|
+
}
|
|
10131
|
+
return void 0;
|
|
10132
|
+
}
|
|
10133
|
+
async function fetchThroughWebProxy(url, deps) {
|
|
10134
|
+
let current;
|
|
10135
|
+
try {
|
|
10136
|
+
current = new URL(url);
|
|
10137
|
+
} catch {
|
|
10138
|
+
return refused("malformed_url", `${url} is not a URL this proxy can dial`);
|
|
10139
|
+
}
|
|
10140
|
+
const controller = new AbortController();
|
|
10141
|
+
const timer = setTimeout(() => {
|
|
10142
|
+
controller.abort();
|
|
10143
|
+
}, deps.deadlineMs);
|
|
10144
|
+
timer.unref();
|
|
10145
|
+
try {
|
|
10146
|
+
let redirects = 0;
|
|
10147
|
+
for (; ; ) {
|
|
10148
|
+
const refusal = await checkTarget(current, deps);
|
|
10149
|
+
if (refusal !== void 0) {
|
|
10150
|
+
return refusal;
|
|
10151
|
+
}
|
|
10152
|
+
let hop;
|
|
10153
|
+
try {
|
|
10154
|
+
hop = await deps.dial(current, controller.signal);
|
|
10155
|
+
} catch (error) {
|
|
10156
|
+
if (controller.signal.aborted) {
|
|
10157
|
+
return refused("deadline", `the fetch of ${url} did not settle within its deadline`);
|
|
10158
|
+
}
|
|
10159
|
+
return refused("transport", `the dial of ${current.toString()} failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
10160
|
+
}
|
|
10161
|
+
if (isRedirect(hop.status) && hop.location !== void 0) {
|
|
10162
|
+
if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
|
|
10163
|
+
controller.abort();
|
|
10164
|
+
return refused("too_many_redirects", `${url} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
|
|
10165
|
+
}
|
|
10166
|
+
let next;
|
|
10167
|
+
try {
|
|
10168
|
+
next = new URL(hop.location, current);
|
|
10169
|
+
} catch {
|
|
10170
|
+
controller.abort();
|
|
10171
|
+
return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
|
|
10172
|
+
}
|
|
10173
|
+
const drained = await readCapped(hop.chunks);
|
|
10174
|
+
if (drained.tooLarge) {
|
|
10175
|
+
controller.abort();
|
|
10176
|
+
return refused("too_large", `the body of a redirect from ${url} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
|
|
10177
|
+
}
|
|
10178
|
+
redirects += 1;
|
|
10179
|
+
current = next;
|
|
10180
|
+
continue;
|
|
10181
|
+
}
|
|
10182
|
+
if (!isSuccessful4(hop.status)) {
|
|
10183
|
+
controller.abort();
|
|
10184
|
+
return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
|
|
10185
|
+
}
|
|
10186
|
+
const contentType = hop.contentType ?? "";
|
|
10187
|
+
if (!readableContentType(contentType)) {
|
|
10188
|
+
controller.abort();
|
|
10189
|
+
return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
|
|
10190
|
+
}
|
|
10191
|
+
const read = await readCapped(hop.chunks);
|
|
10192
|
+
if (read.tooLarge) {
|
|
10193
|
+
controller.abort();
|
|
10194
|
+
return refused("too_large", `the body of ${current.toString()} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
|
|
10195
|
+
}
|
|
10196
|
+
return { kind: "fetched", url, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
|
|
10197
|
+
}
|
|
10198
|
+
} finally {
|
|
10199
|
+
clearTimeout(timer);
|
|
10200
|
+
}
|
|
10201
|
+
}
|
|
10202
|
+
function realRcWebFetch(options) {
|
|
10203
|
+
const plainAgent = new ExemptHttpAgent();
|
|
10204
|
+
const tlsAgent = new ExemptTlsAgent();
|
|
10205
|
+
const deps = {
|
|
10206
|
+
dial: async (url, signal) => await new Promise((resolve, reject) => {
|
|
10207
|
+
const request4 = (url.protocol === "https:" ? https3 : http2).request(
|
|
10208
|
+
{
|
|
10209
|
+
hostname: url.hostname,
|
|
10210
|
+
port: url.port === "" ? void 0 : Number(url.port),
|
|
10211
|
+
method: "GET",
|
|
10212
|
+
path: `${url.pathname}${url.search}`,
|
|
10213
|
+
headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
|
|
10214
|
+
agent: url.protocol === "https:" ? tlsAgent : plainAgent,
|
|
10215
|
+
signal
|
|
10216
|
+
},
|
|
10217
|
+
(response) => {
|
|
10218
|
+
const location = response.headers.location;
|
|
10219
|
+
resolve({
|
|
10220
|
+
status: response.statusCode ?? 0,
|
|
10221
|
+
location: typeof location === "string" ? location : void 0,
|
|
10222
|
+
contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
|
|
10223
|
+
chunks: response
|
|
10224
|
+
});
|
|
10225
|
+
}
|
|
10226
|
+
);
|
|
10227
|
+
request4.once("error", reject);
|
|
10228
|
+
request4.end();
|
|
10229
|
+
}),
|
|
10230
|
+
lookup: async (hostname) => (await dns2.promises.lookup(hostname, { all: true })).map((entry) => entry.address),
|
|
10231
|
+
allowPrivate: options.allowPrivate,
|
|
10232
|
+
deadlineMs: RC_WEB_FETCH_DEADLINE_MS
|
|
10233
|
+
};
|
|
10234
|
+
return async (url) => await fetchThroughWebProxy(url, deps);
|
|
10235
|
+
}
|
|
10236
|
+
|
|
9991
10237
|
// src/frontdoor/rcSelfHost.ts
|
|
9992
10238
|
var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
|
|
9993
10239
|
function rcSelfHostFromEnv(env) {
|
|
@@ -10017,6 +10263,9 @@ var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
|
|
|
10017
10263
|
function isRecord6(value) {
|
|
10018
10264
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10019
10265
|
}
|
|
10266
|
+
function isWebSearchRefusal(value) {
|
|
10267
|
+
return !Array.isArray(value);
|
|
10268
|
+
}
|
|
10020
10269
|
function nonEmptyString2(record, field) {
|
|
10021
10270
|
const value = record === void 0 ? void 0 : record[field];
|
|
10022
10271
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -10031,6 +10280,20 @@ function numberOf(record, field) {
|
|
|
10031
10280
|
}
|
|
10032
10281
|
return void 0;
|
|
10033
10282
|
}
|
|
10283
|
+
function stringArray(record, field) {
|
|
10284
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
10285
|
+
if (!Array.isArray(value)) {
|
|
10286
|
+
return void 0;
|
|
10287
|
+
}
|
|
10288
|
+
const members = [];
|
|
10289
|
+
for (const member of value) {
|
|
10290
|
+
if (typeof member !== "string") {
|
|
10291
|
+
return void 0;
|
|
10292
|
+
}
|
|
10293
|
+
members.push(member);
|
|
10294
|
+
}
|
|
10295
|
+
return members;
|
|
10296
|
+
}
|
|
10034
10297
|
function singleHeader2(headers, name) {
|
|
10035
10298
|
const value = headers[name];
|
|
10036
10299
|
return typeof value === "string" ? value : void 0;
|
|
@@ -10261,11 +10524,12 @@ data: ${JSON.stringify(envelope)}
|
|
|
10261
10524
|
return;
|
|
10262
10525
|
}
|
|
10263
10526
|
if (tail === "worker" || tail.startsWith("worker/")) {
|
|
10264
|
-
|
|
10527
|
+
const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
|
|
10528
|
+
const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
|
|
10529
|
+
if (!(webProxy ? presentsCredential(request4.headers) || presentsWorkerJwt(request4.headers, session) : presentsWorkerJwt(request4.headers, session))) {
|
|
10265
10530
|
await unauthorized(response);
|
|
10266
10531
|
return;
|
|
10267
10532
|
}
|
|
10268
|
-
const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
|
|
10269
10533
|
if (workerTail === "" && method === "GET") {
|
|
10270
10534
|
await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
|
|
10271
10535
|
return;
|
|
@@ -10364,6 +10628,58 @@ data: ${JSON.stringify(envelope)}
|
|
|
10364
10628
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
10365
10629
|
return;
|
|
10366
10630
|
}
|
|
10631
|
+
if (workerTail === "web-fetch" && method === "POST") {
|
|
10632
|
+
const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10633
|
+
const target = nonEmptyString2(body, "url");
|
|
10634
|
+
if (target === void 0) {
|
|
10635
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
|
|
10636
|
+
return;
|
|
10637
|
+
}
|
|
10638
|
+
const outcome = await deps.webFetch(target);
|
|
10639
|
+
if (outcome.kind === "fetched") {
|
|
10640
|
+
const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
|
|
10641
|
+
if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
|
|
10642
|
+
deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
|
|
10643
|
+
await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: "web_fetch_too_large", error_message: `the answer for ${target} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads` } });
|
|
10644
|
+
return;
|
|
10645
|
+
}
|
|
10646
|
+
deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
|
|
10647
|
+
await answerJson2(response, HTTP_STATUS.ok, answer);
|
|
10648
|
+
return;
|
|
10649
|
+
}
|
|
10650
|
+
deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
|
|
10651
|
+
await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
|
|
10652
|
+
return;
|
|
10653
|
+
}
|
|
10654
|
+
if (workerTail === "web-search" && method === "POST") {
|
|
10655
|
+
const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10656
|
+
const query = nonEmptyString2(body, "query");
|
|
10657
|
+
if (query === void 0) {
|
|
10658
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
|
|
10659
|
+
return;
|
|
10660
|
+
}
|
|
10661
|
+
const allowedDomains = stringArray(body, "allowed_domains");
|
|
10662
|
+
const blockedDomains = stringArray(body, "blocked_domains");
|
|
10663
|
+
if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
|
|
10664
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "the domain filters of a worker web-search are string arrays" } });
|
|
10665
|
+
return;
|
|
10666
|
+
}
|
|
10667
|
+
const searchProfile = nonEmptyString2(body, "search_profile");
|
|
10668
|
+
const backend = deps.webSearch;
|
|
10669
|
+
if (backend === void 0) {
|
|
10670
|
+
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
|
|
10671
|
+
await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: "web_search_unavailable", error_message: "this front door serves the Remote Control web-search proxy without a search backend, so no query can be served here" } });
|
|
10672
|
+
return;
|
|
10673
|
+
}
|
|
10674
|
+
const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
|
|
10675
|
+
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
|
|
10676
|
+
if (isWebSearchRefusal(answered)) {
|
|
10677
|
+
await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
|
|
10678
|
+
return;
|
|
10679
|
+
}
|
|
10680
|
+
await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
|
|
10681
|
+
return;
|
|
10682
|
+
}
|
|
10367
10683
|
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
|
|
10368
10684
|
return;
|
|
10369
10685
|
}
|
|
@@ -11909,6 +12225,10 @@ export {
|
|
|
11909
12225
|
RC_SELF_HOST_WORKER_JWT_TTL_SECONDS,
|
|
11910
12226
|
RC_SESSIONS_PATH_PREFIX,
|
|
11911
12227
|
RC_STREAM_BACKOFF_MS,
|
|
12228
|
+
RC_WEB_FETCH_ALLOW_PRIVATE_ENV,
|
|
12229
|
+
RC_WEB_FETCH_DEADLINE_MS,
|
|
12230
|
+
RC_WEB_FETCH_MAX_BYTES,
|
|
12231
|
+
RC_WEB_FETCH_MAX_REDIRECTS,
|
|
11912
12232
|
ROUTED_PATH_PREFIX,
|
|
11913
12233
|
RcPermissionModeSchema,
|
|
11914
12234
|
RcStreamEnvelopeSchema,
|
|
@@ -11967,6 +12287,7 @@ export {
|
|
|
11967
12287
|
ensureHeadroom,
|
|
11968
12288
|
evaluateAmbientCredentialGuard,
|
|
11969
12289
|
evaluateWhen,
|
|
12290
|
+
fetchThroughWebProxy,
|
|
11970
12291
|
formatAmbientCredentialGuardMessage,
|
|
11971
12292
|
formatCheckReport,
|
|
11972
12293
|
formatDoctorReport,
|
|
@@ -11983,6 +12304,7 @@ export {
|
|
|
11983
12304
|
interruptRcSession,
|
|
11984
12305
|
isIdentityDirectoryName,
|
|
11985
12306
|
isInterceptedHost,
|
|
12307
|
+
isPrivateAddress,
|
|
11986
12308
|
isRcPermissionMode,
|
|
11987
12309
|
lateRcEventDial,
|
|
11988
12310
|
lateRcStreamDial,
|
|
@@ -12007,6 +12329,7 @@ export {
|
|
|
12007
12329
|
rcFanoutOnDoorHub,
|
|
12008
12330
|
rcObserverAsPassthrough,
|
|
12009
12331
|
rcSelfHostFromEnv,
|
|
12332
|
+
rcWebFetchAllowsPrivateFromEnv,
|
|
12010
12333
|
readActiveIdentity,
|
|
12011
12334
|
readDirectoryRules,
|
|
12012
12335
|
readFarmManifest,
|
|
@@ -12022,6 +12345,7 @@ export {
|
|
|
12022
12345
|
realRcControlTransport,
|
|
12023
12346
|
realRcEventDial,
|
|
12024
12347
|
realRcStreamDial,
|
|
12348
|
+
realRcWebFetch,
|
|
12025
12349
|
recoverFarm,
|
|
12026
12350
|
recoveryDiagnostics,
|
|
12027
12351
|
removeDirectoryRule,
|