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 CHANGED
@@ -6891,7 +6891,7 @@ var require_util2 = __commonJS({
6891
6891
  var { kDestroyed, kBodyUsed, kListeners, kBody } = require_symbols();
6892
6892
  var { IncomingMessage } = require("node:http");
6893
6893
  var stream = require("node:stream");
6894
- var net3 = require("node:net");
6894
+ var net4 = require("node:net");
6895
6895
  var { stringify } = require("node:querystring");
6896
6896
  var { EventEmitter: EE } = require("node:events");
6897
6897
  var timers = require_timers();
@@ -7040,7 +7040,7 @@ var require_util2 = __commonJS({
7040
7040
  }
7041
7041
  assert2(typeof host === "string");
7042
7042
  const servername = getHostname(host);
7043
- if (net3.isIP(servername)) {
7043
+ if (net4.isIP(servername)) {
7044
7044
  return "";
7045
7045
  }
7046
7046
  return servername;
@@ -8703,7 +8703,7 @@ var require_dispatcher_base = __commonJS({
8703
8703
  var require_connect = __commonJS({
8704
8704
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/core/connect.js"(exports2, module2) {
8705
8705
  "use strict";
8706
- var net3 = require("node:net");
8706
+ var net4 = require("node:net");
8707
8707
  var assert2 = require("node:assert");
8708
8708
  var util = require_util2();
8709
8709
  var { InvalidArgumentError: InvalidArgumentError2 } = require_errors();
@@ -8786,7 +8786,7 @@ var require_connect = __commonJS({
8786
8786
  } else {
8787
8787
  assert2(!httpSocket, "httpSocket can only be sent on TLS update");
8788
8788
  port = port || 80;
8789
- socket = net3.connect({
8789
+ socket = net4.connect({
8790
8790
  highWaterMark: 64 * 1024,
8791
8791
  // Same as nodejs fs streams.
8792
8792
  ...options,
@@ -14463,8 +14463,8 @@ var require_client = __commonJS({
14463
14463
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/dispatcher/client.js"(exports2, module2) {
14464
14464
  "use strict";
14465
14465
  var assert2 = require("node:assert");
14466
- var net3 = require("node:net");
14467
- var http7 = require("node:http");
14466
+ var net4 = require("node:net");
14467
+ var http8 = require("node:http");
14468
14468
  var util = require_util2();
14469
14469
  var { ClientStats } = require_stats();
14470
14470
  var { channels } = require_diagnostics();
@@ -14522,7 +14522,7 @@ var require_client = __commonJS({
14522
14522
  var connectH1 = require_client_h1();
14523
14523
  var connectH2 = require_client_h2();
14524
14524
  var kClosedResolve = /* @__PURE__ */ Symbol("kClosedResolve");
14525
- var getDefaultNodeMaxHeaderSize = http7 && http7.maxHeaderSize && Number.isInteger(http7.maxHeaderSize) && http7.maxHeaderSize > 0 ? () => http7.maxHeaderSize : () => {
14525
+ var getDefaultNodeMaxHeaderSize = http8 && http8.maxHeaderSize && Number.isInteger(http8.maxHeaderSize) && http8.maxHeaderSize > 0 ? () => http8.maxHeaderSize : () => {
14526
14526
  throw new InvalidArgumentError2("http module not available or http.maxHeaderSize invalid");
14527
14527
  };
14528
14528
  var noop = () => {
@@ -14618,7 +14618,7 @@ var require_client = __commonJS({
14618
14618
  if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
14619
14619
  throw new InvalidArgumentError2("maxRequestsPerClient must be a positive number");
14620
14620
  }
14621
- if (localAddress != null && (typeof localAddress !== "string" || net3.isIP(localAddress) === 0)) {
14621
+ if (localAddress != null && (typeof localAddress !== "string" || net4.isIP(localAddress) === 0)) {
14622
14622
  throw new InvalidArgumentError2("localAddress must be valid string IP address");
14623
14623
  }
14624
14624
  if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) {
@@ -14798,7 +14798,7 @@ var require_client = __commonJS({
14798
14798
  const idx = hostname3.indexOf("]");
14799
14799
  assert2(idx !== -1);
14800
14800
  const ip = hostname3.substring(1, idx);
14801
- assert2(net3.isIPv6(ip));
14801
+ assert2(net4.isIPv6(ip));
14802
14802
  hostname3 = ip;
14803
14803
  }
14804
14804
  client[kConnecting] = true;
@@ -15743,10 +15743,10 @@ var require_socks5_utils = __commonJS({
15743
15743
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/core/socks5-utils.js"(exports2, module2) {
15744
15744
  "use strict";
15745
15745
  var { Buffer: Buffer3 } = require("node:buffer");
15746
- var net3 = require("node:net");
15746
+ var net4 = require("node:net");
15747
15747
  var { InvalidArgumentError: InvalidArgumentError2 } = require_errors();
15748
15748
  function parseAddress(address) {
15749
- if (net3.isIPv4(address)) {
15749
+ if (net4.isIPv4(address)) {
15750
15750
  const parts = address.split(".").map(Number);
15751
15751
  return {
15752
15752
  type: 1,
@@ -15754,7 +15754,7 @@ var require_socks5_utils = __commonJS({
15754
15754
  buffer: Buffer3.from(parts)
15755
15755
  };
15756
15756
  }
15757
- if (net3.isIPv6(address)) {
15757
+ if (net4.isIPv6(address)) {
15758
15758
  return {
15759
15759
  type: 4,
15760
15760
  // IPv6
@@ -15777,7 +15777,7 @@ var require_socks5_utils = __commonJS({
15777
15777
  if (address.includes(".")) {
15778
15778
  const lastColonIndex = address.lastIndexOf(":");
15779
15779
  const ipv4Part = address.slice(lastColonIndex + 1);
15780
- if (net3.isIPv4(ipv4Part)) {
15780
+ if (net4.isIPv4(ipv4Part)) {
15781
15781
  const octets = ipv4Part.split(".").map(Number);
15782
15782
  const high = (octets[0] << 8 | octets[1]).toString(16);
15783
15783
  const low = (octets[2] << 8 | octets[3]).toString(16);
@@ -16658,18 +16658,18 @@ var require_proxy_agent = __commonJS({
16658
16658
  }
16659
16659
  }
16660
16660
  [kClose]() {
16661
- const promises = [this[kAgent].close()];
16661
+ const promises2 = [this[kAgent].close()];
16662
16662
  if (this[kClient]) {
16663
- promises.push(this[kClient].close());
16663
+ promises2.push(this[kClient].close());
16664
16664
  }
16665
- return Promise.all(promises);
16665
+ return Promise.all(promises2);
16666
16666
  }
16667
16667
  [kDestroy]() {
16668
- const promises = [this[kAgent].destroy()];
16668
+ const promises2 = [this[kAgent].destroy()];
16669
16669
  if (this[kClient]) {
16670
- promises.push(this[kClient].destroy());
16670
+ promises2.push(this[kClient].destroy());
16671
16671
  }
16672
- return Promise.all(promises);
16672
+ return Promise.all(promises2);
16673
16673
  }
16674
16674
  };
16675
16675
  function buildHeaders(headers) {
@@ -20817,7 +20817,7 @@ var require_dump = __commonJS({
20817
20817
  var require_dns = __commonJS({
20818
20818
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/interceptor/dns.js"(exports2, module2) {
20819
20819
  "use strict";
20820
- var { isIP: isIP2 } = require("node:net");
20820
+ var { isIP: isIP3 } = require("node:net");
20821
20821
  var { lookup } = require("node:dns");
20822
20822
  var DecoratorHandler = require_decorator_handler();
20823
20823
  var { InvalidArgumentError: InvalidArgumentError2, InformationalError } = require_errors();
@@ -21223,7 +21223,7 @@ var require_dns = __commonJS({
21223
21223
  return (dispatch) => {
21224
21224
  return function dnsInterceptor(origDispatchOpts, handler) {
21225
21225
  const origin = origDispatchOpts.origin.constructor === URL ? origDispatchOpts.origin : new URL(origDispatchOpts.origin);
21226
- if (isIP2(origin.hostname) !== 0) {
21226
+ if (isIP3(origin.hostname) !== 0) {
21227
21227
  return dispatch(origDispatchOpts, handler);
21228
21228
  }
21229
21229
  instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => {
@@ -36009,8 +36009,8 @@ function getElementAtPath(obj, path44) {
36009
36009
  }
36010
36010
  function promiseAllObject(promisesObj) {
36011
36011
  const keys = Object.keys(promisesObj);
36012
- const promises = keys.map((key) => promisesObj[key]);
36013
- return Promise.all(promises).then((results) => {
36012
+ const promises2 = keys.map((key) => promisesObj[key]);
36013
+ return Promise.all(promises2).then((results) => {
36014
36014
  const resolvedObj = {};
36015
36015
  for (let i2 = 0; i2 < keys.length; i2++) {
36016
36016
  resolvedObj[keys[i2]] = results[i2];
@@ -61790,7 +61790,7 @@ function registerIdentityCommand(program2, deps) {
61790
61790
  // package.json
61791
61791
  var package_default = {
61792
61792
  name: "agent-shim",
61793
- version: "8.20.2",
61793
+ version: "8.21.0",
61794
61794
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61795
61795
  license: "MIT",
61796
61796
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -65671,22 +65671,22 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65671
65671
  };
65672
65672
  writeFrontDoorState(fs19, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
65673
65673
  const previousSticky = lastPort;
65674
- let http7;
65674
+ let http8;
65675
65675
  try {
65676
- http7 = await ports.startProviderListener(lastPort);
65676
+ http8 = await ports.startProviderListener(lastPort);
65677
65677
  } catch (error62) {
65678
65678
  return fail(`could not start the provider listener: ${error62 instanceof Error ? error62.message : String(error62)}`);
65679
65679
  }
65680
- if (previousSticky !== void 0 && http7.port !== previousSticky) {
65681
- ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http7.port)} (sessions launched against the old port are stale until they relaunch)`);
65680
+ if (previousSticky !== void 0 && http8.port !== previousSticky) {
65681
+ ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http8.port)} (sessions launched against the old port are stale until they relaunch)`);
65682
65682
  }
65683
- lastPort = http7.port;
65683
+ lastPort = http8.port;
65684
65684
  const previousStickyConnect = lastConnectPort;
65685
65685
  let connect3;
65686
65686
  try {
65687
65687
  connect3 = await ports.startConnectListener(lastConnectPort);
65688
65688
  } catch (error62) {
65689
- await http7.close();
65689
+ await http8.close();
65690
65690
  return fail(`could not start the CONNECT surface: ${error62 instanceof Error ? error62.message : String(error62)}`);
65691
65691
  }
65692
65692
  if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
@@ -65699,16 +65699,16 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65699
65699
  direct = await ports.startDirectListener(lastDirectPort);
65700
65700
  } catch (error62) {
65701
65701
  await connect3.close();
65702
- await http7.close();
65702
+ await http8.close();
65703
65703
  return fail(`could not start the direct listener: ${error62 instanceof Error ? error62.message : String(error62)}`);
65704
65704
  }
65705
65705
  if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
65706
65706
  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)`);
65707
65707
  }
65708
65708
  lastDirectPort = direct.port;
65709
- writeFrontDoorState(fs19, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http7.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
65709
+ writeFrontDoorState(fs19, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http8.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
65710
65710
  fs19.removeRecursive(paths.frontdoorLockFile);
65711
- ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http7.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
65711
+ ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http8.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
65712
65712
  let idleSince;
65713
65713
  let ticks = 0;
65714
65714
  for (; ; ) {
@@ -65726,7 +65726,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65726
65726
  ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
65727
65727
  await direct.close();
65728
65728
  await connect3.close();
65729
- await http7.close();
65729
+ await http8.close();
65730
65730
  ports.closeRcStreamHub();
65731
65731
  writeFrontDoorState(fs19, paths.frontdoorStateFile, sticky());
65732
65732
  return 0;
@@ -73994,6 +73994,252 @@ function createRcClientPage(expectedToken) {
73994
73994
  };
73995
73995
  }
73996
73996
 
73997
+ // src/frontdoor/rcWebFetch.ts
73998
+ var dns2 = __toESM(require("node:dns"), 1);
73999
+ var http6 = __toESM(require("node:http"), 1);
74000
+ var https5 = __toESM(require("node:https"), 1);
74001
+ var net3 = __toESM(require("node:net"), 1);
74002
+ var RC_WEB_FETCH_MAX_BYTES = 12582912;
74003
+ var RC_WEB_FETCH_DEADLINE_MS = 4e4;
74004
+ var RC_WEB_FETCH_MAX_REDIRECTS = 20;
74005
+ var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
74006
+ function rcWebFetchAllowsPrivateFromEnv(env) {
74007
+ return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
74008
+ }
74009
+ var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
74010
+ var ERROR_PREFIX = "web_fetch_";
74011
+ var V4_THIS_NETWORK_OCTET = 0;
74012
+ var V4_PRIVATE_A_OCTET = 10;
74013
+ var V4_LOOPBACK_OCTET = 127;
74014
+ var V4_CGNAT_OCTET = 100;
74015
+ var V4_CGNAT_SECOND_FROM = 64;
74016
+ var V4_CGNAT_SECOND_TO = 127;
74017
+ var V4_LINK_LOCAL_OCTET = 169;
74018
+ var V4_LINK_LOCAL_SECOND = 254;
74019
+ var V4_PRIVATE_B_OCTET = 172;
74020
+ var V4_PRIVATE_B_SECOND_FROM = 16;
74021
+ var V4_PRIVATE_B_SECOND_TO = 31;
74022
+ var V4_PRIVATE_C_OCTET = 192;
74023
+ var V4_PRIVATE_C_SECOND = 168;
74024
+ var V4_MULTICAST_FROM_OCTET = 224;
74025
+ var V6_UNIQUE_LOCAL_FROM = 64512;
74026
+ var V6_UNIQUE_LOCAL_TO = 65023;
74027
+ var V6_LINK_LOCAL_FROM = 65152;
74028
+ var V6_LINK_LOCAL_TO = 65215;
74029
+ var V6_MULTICAST_FROM = 65280;
74030
+ var IP_VERSION_4 = 4;
74031
+ var IP_VERSION_6 = 6;
74032
+ var HTTP_MOVED_PERMANENTLY = 301;
74033
+ var HTTP_FOUND = 302;
74034
+ var HTTP_SEE_OTHER = 303;
74035
+ var HTTP_TEMPORARY_REDIRECT = 307;
74036
+ var HTTP_PERMANENT_REDIRECT = 308;
74037
+ var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
74038
+ function refused(errorType, errorMessage2) {
74039
+ return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
74040
+ }
74041
+ function isPrivateAddress(address) {
74042
+ const version2 = net3.isIP(address);
74043
+ if (version2 === IP_VERSION_4) {
74044
+ const octets = address.split(".").map((part) => Number(part));
74045
+ const first = octets[0] ?? -1;
74046
+ const second = octets[1] ?? -1;
74047
+ if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
74048
+ return true;
74049
+ }
74050
+ if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
74051
+ return true;
74052
+ }
74053
+ if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
74054
+ return true;
74055
+ }
74056
+ if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
74057
+ return true;
74058
+ }
74059
+ if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
74060
+ return true;
74061
+ }
74062
+ return first >= V4_MULTICAST_FROM_OCTET;
74063
+ }
74064
+ if (version2 === IP_VERSION_6) {
74065
+ const lower = address.toLowerCase();
74066
+ const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
74067
+ if (mapped !== null) {
74068
+ return isPrivateAddress(mapped[1] ?? "");
74069
+ }
74070
+ if (lower === "::" || lower === "::1") {
74071
+ return true;
74072
+ }
74073
+ const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
74074
+ if (Number.isNaN(firstGroup)) {
74075
+ return true;
74076
+ }
74077
+ if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
74078
+ return true;
74079
+ }
74080
+ if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
74081
+ return true;
74082
+ }
74083
+ return firstGroup >= V6_MULTICAST_FROM;
74084
+ }
74085
+ return true;
74086
+ }
74087
+ function isSuccessful3(status) {
74088
+ return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
74089
+ }
74090
+ function isRedirect(status) {
74091
+ return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
74092
+ }
74093
+ function readableContentType(contentType) {
74094
+ if (contentType === void 0 || contentType.trim() === "") {
74095
+ return true;
74096
+ }
74097
+ const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
74098
+ const slash = normalised.indexOf("/");
74099
+ const main2 = slash === -1 ? normalised : normalised.slice(0, slash);
74100
+ const sub = slash === -1 ? "" : normalised.slice(slash + 1);
74101
+ if (main2 === "text") {
74102
+ return true;
74103
+ }
74104
+ return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
74105
+ }
74106
+ async function readCapped(chunks) {
74107
+ const collected = [];
74108
+ let total = 0;
74109
+ for await (const chunk of chunks) {
74110
+ total += chunk.byteLength;
74111
+ if (total > RC_WEB_FETCH_MAX_BYTES) {
74112
+ return { tooLarge: true, text: "" };
74113
+ }
74114
+ collected.push(Buffer.from(chunk));
74115
+ }
74116
+ return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
74117
+ }
74118
+ async function checkTarget(url2, deps) {
74119
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
74120
+ return refused("scheme", `the web-fetch proxy dials http and https only, not ${url2.protocol}`);
74121
+ }
74122
+ if (deps.allowPrivate) {
74123
+ return void 0;
74124
+ }
74125
+ const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
74126
+ let addresses;
74127
+ try {
74128
+ addresses = net3.isIP(hostname3) === 0 ? await deps.lookup(hostname3) : [hostname3];
74129
+ } catch {
74130
+ return refused("resolve", `${hostname3} did not resolve`);
74131
+ }
74132
+ for (const address of addresses) {
74133
+ if (isPrivateAddress(address)) {
74134
+ return refused("private_address", `${hostname3} 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`);
74135
+ }
74136
+ }
74137
+ return void 0;
74138
+ }
74139
+ async function fetchThroughWebProxy(url2, deps) {
74140
+ let current;
74141
+ try {
74142
+ current = new URL(url2);
74143
+ } catch {
74144
+ return refused("malformed_url", `${url2} is not a URL this proxy can dial`);
74145
+ }
74146
+ const controller = new AbortController();
74147
+ const timer = setTimeout(() => {
74148
+ controller.abort();
74149
+ }, deps.deadlineMs);
74150
+ timer.unref();
74151
+ try {
74152
+ let redirects = 0;
74153
+ for (; ; ) {
74154
+ const refusal = await checkTarget(current, deps);
74155
+ if (refusal !== void 0) {
74156
+ return refusal;
74157
+ }
74158
+ let hop;
74159
+ try {
74160
+ hop = await deps.dial(current, controller.signal);
74161
+ } catch (error62) {
74162
+ if (controller.signal.aborted) {
74163
+ return refused("deadline", `the fetch of ${url2} did not settle within its deadline`);
74164
+ }
74165
+ return refused("transport", `the dial of ${current.toString()} failed: ${error62 instanceof Error ? error62.message : "unknown error"}`);
74166
+ }
74167
+ if (isRedirect(hop.status) && hop.location !== void 0) {
74168
+ if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
74169
+ controller.abort();
74170
+ return refused("too_many_redirects", `${url2} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
74171
+ }
74172
+ let next;
74173
+ try {
74174
+ next = new URL(hop.location, current);
74175
+ } catch {
74176
+ controller.abort();
74177
+ return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
74178
+ }
74179
+ const drained = await readCapped(hop.chunks);
74180
+ if (drained.tooLarge) {
74181
+ controller.abort();
74182
+ return refused("too_large", `the body of a redirect from ${url2} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
74183
+ }
74184
+ redirects += 1;
74185
+ current = next;
74186
+ continue;
74187
+ }
74188
+ if (!isSuccessful3(hop.status)) {
74189
+ controller.abort();
74190
+ return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
74191
+ }
74192
+ const contentType = hop.contentType ?? "";
74193
+ if (!readableContentType(contentType)) {
74194
+ controller.abort();
74195
+ return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
74196
+ }
74197
+ const read = await readCapped(hop.chunks);
74198
+ if (read.tooLarge) {
74199
+ controller.abort();
74200
+ 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`);
74201
+ }
74202
+ return { kind: "fetched", url: url2, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
74203
+ }
74204
+ } finally {
74205
+ clearTimeout(timer);
74206
+ }
74207
+ }
74208
+ function realRcWebFetch(options) {
74209
+ const plainAgent = new ExemptHttpAgent();
74210
+ const tlsAgent = new ExemptTlsAgent();
74211
+ const deps = {
74212
+ dial: async (url2, signal) => await new Promise((resolve, reject) => {
74213
+ const request7 = (url2.protocol === "https:" ? https5 : http6).request(
74214
+ {
74215
+ hostname: url2.hostname,
74216
+ port: url2.port === "" ? void 0 : Number(url2.port),
74217
+ method: "GET",
74218
+ path: `${url2.pathname}${url2.search}`,
74219
+ headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
74220
+ agent: url2.protocol === "https:" ? tlsAgent : plainAgent,
74221
+ signal
74222
+ },
74223
+ (response) => {
74224
+ const location = response.headers.location;
74225
+ resolve({
74226
+ status: response.statusCode ?? 0,
74227
+ location: typeof location === "string" ? location : void 0,
74228
+ contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
74229
+ chunks: response
74230
+ });
74231
+ }
74232
+ );
74233
+ request7.once("error", reject);
74234
+ request7.end();
74235
+ }),
74236
+ lookup: async (hostname3) => (await dns2.promises.lookup(hostname3, { all: true })).map((entry) => entry.address),
74237
+ allowPrivate: options.allowPrivate,
74238
+ deadlineMs: RC_WEB_FETCH_DEADLINE_MS
74239
+ };
74240
+ return async (url2) => await fetchThroughWebProxy(url2, deps);
74241
+ }
74242
+
73997
74243
  // src/frontdoor/rcSelfHost.ts
73998
74244
  var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
73999
74245
  function rcSelfHostFromEnv(env) {
@@ -74023,6 +74269,9 @@ var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
74023
74269
  function isRecord5(value2) {
74024
74270
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74025
74271
  }
74272
+ function isWebSearchRefusal(value2) {
74273
+ return !Array.isArray(value2);
74274
+ }
74026
74275
  function nonEmptyString2(record2, field) {
74027
74276
  const value2 = record2 === void 0 ? void 0 : record2[field];
74028
74277
  return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
@@ -74037,6 +74286,20 @@ function numberOf(record2, field) {
74037
74286
  }
74038
74287
  return void 0;
74039
74288
  }
74289
+ function stringArray(record2, field) {
74290
+ const value2 = record2 === void 0 ? void 0 : record2[field];
74291
+ if (!Array.isArray(value2)) {
74292
+ return void 0;
74293
+ }
74294
+ const members2 = [];
74295
+ for (const member of value2) {
74296
+ if (typeof member !== "string") {
74297
+ return void 0;
74298
+ }
74299
+ members2.push(member);
74300
+ }
74301
+ return members2;
74302
+ }
74040
74303
  function singleHeader2(headers, name) {
74041
74304
  const value2 = headers[name];
74042
74305
  return typeof value2 === "string" ? value2 : void 0;
@@ -74267,11 +74530,12 @@ data: ${JSON.stringify(envelope)}
74267
74530
  return;
74268
74531
  }
74269
74532
  if (tail === "worker" || tail.startsWith("worker/")) {
74270
- if (!presentsWorkerJwt(request7.headers, session)) {
74533
+ const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
74534
+ const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
74535
+ if (!(webProxy ? presentsCredential(request7.headers) || presentsWorkerJwt(request7.headers, session) : presentsWorkerJwt(request7.headers, session))) {
74271
74536
  await unauthorized(response);
74272
74537
  return;
74273
74538
  }
74274
- const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
74275
74539
  if (workerTail === "" && method === "GET") {
74276
74540
  await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
74277
74541
  return;
@@ -74370,6 +74634,58 @@ data: ${JSON.stringify(envelope)}
74370
74634
  await answerJson2(response, HTTP_STATUS.ok, {});
74371
74635
  return;
74372
74636
  }
74637
+ if (workerTail === "web-fetch" && method === "POST") {
74638
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74639
+ const target = nonEmptyString2(body, "url");
74640
+ if (target === void 0) {
74641
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
74642
+ return;
74643
+ }
74644
+ const outcome = await deps.webFetch(target);
74645
+ if (outcome.kind === "fetched") {
74646
+ const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
74647
+ if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
74648
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
74649
+ 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` } });
74650
+ return;
74651
+ }
74652
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
74653
+ await answerJson2(response, HTTP_STATUS.ok, answer);
74654
+ return;
74655
+ }
74656
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
74657
+ await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
74658
+ return;
74659
+ }
74660
+ if (workerTail === "web-search" && method === "POST") {
74661
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74662
+ const query = nonEmptyString2(body, "query");
74663
+ if (query === void 0) {
74664
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
74665
+ return;
74666
+ }
74667
+ const allowedDomains = stringArray(body, "allowed_domains");
74668
+ const blockedDomains = stringArray(body, "blocked_domains");
74669
+ if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
74670
+ 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" } });
74671
+ return;
74672
+ }
74673
+ const searchProfile = nonEmptyString2(body, "search_profile");
74674
+ const backend = deps.webSearch;
74675
+ if (backend === void 0) {
74676
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
74677
+ 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" } });
74678
+ return;
74679
+ }
74680
+ const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
74681
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
74682
+ if (isWebSearchRefusal(answered)) {
74683
+ await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
74684
+ return;
74685
+ }
74686
+ await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
74687
+ return;
74688
+ }
74373
74689
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
74374
74690
  return;
74375
74691
  }
@@ -74684,9 +75000,9 @@ function mintRcSelfHostCredential(deps) {
74684
75000
  var RC_STREAM_EVENT_NAME2 = "client_event";
74685
75001
  var SSE_BYTE_ORDER_MARK = 65279;
74686
75002
  var RC_STREAM_BACKOFF_MS = 45e3;
74687
- var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
74688
- function isSuccessful3(status) {
74689
- return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
75003
+ var SUCCESS_STATUS_MAX_EXCLUSIVE4 = 300;
75004
+ function isSuccessful4(status) {
75005
+ return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE4;
74690
75006
  }
74691
75007
  function createSseParser() {
74692
75008
  let buffer = "";
@@ -74853,7 +75169,7 @@ function createRcStreamHub(deps) {
74853
75169
  if (presence.status === HTTP_STATUS.unauthorized) {
74854
75170
  return "unauthorized";
74855
75171
  }
74856
- if (!isSuccessful3(presence.status)) {
75172
+ if (!isSuccessful4(presence.status)) {
74857
75173
  throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
74858
75174
  }
74859
75175
  const cursor3 = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
@@ -74862,7 +75178,7 @@ function createRcStreamHub(deps) {
74862
75178
  if (answer.status === HTTP_STATUS.unauthorized) {
74863
75179
  return "unauthorized";
74864
75180
  }
74865
- if (!isSuccessful3(answer.status)) {
75181
+ if (!isSuccessful4(answer.status)) {
74866
75182
  throw new Error(`the client read stream answered HTTP ${String(answer.status)}`);
74867
75183
  }
74868
75184
  deps.log?.(`rc stream ${attachment.sessionId}: attached as client ${attachment.clientId}${resume === void 0 ? ", reading from the stream's own head" : `, resuming after sequence number ${String(resume.fromSequenceNum)}`}`);
@@ -75066,8 +75382,8 @@ function zaiWindow(limit) {
75066
75382
  var zaiQuotaAdapter = {
75067
75383
  source: ZAI_SOURCE,
75068
75384
  matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === ZAI_HOST,
75069
- fetch: async (apiKey, http7, signal, nowMs) => {
75070
- const response = await http7(ZAI_QUOTA_URL, { authorization: apiKey, accept: "application/json" }, signal);
75385
+ fetch: async (apiKey, http8, signal, nowMs) => {
75386
+ const response = await http8(ZAI_QUOTA_URL, { authorization: apiKey, accept: "application/json" }, signal);
75071
75387
  if (response.status !== HTTP_OK) {
75072
75388
  throw new ProviderQuotaError(ZAI_SOURCE, `answered HTTP ${String(response.status)}`);
75073
75389
  }
@@ -75108,8 +75424,8 @@ var OpenRouterKeySchema = external_exports.object({
75108
75424
  })
75109
75425
  });
75110
75426
  var OpenRouterCreditsSchema = external_exports.object({ data: external_exports.object({ total_credits: external_exports.number().nonnegative(), total_usage: external_exports.number().nonnegative() }) });
75111
- async function openRouterGet(url2, schema, apiKey, http7, signal) {
75112
- const response = await http7(url2, { authorization: `Bearer ${apiKey}`, accept: "application/json" }, signal);
75427
+ async function openRouterGet(url2, schema, apiKey, http8, signal) {
75428
+ const response = await http8(url2, { authorization: `Bearer ${apiKey}`, accept: "application/json" }, signal);
75113
75429
  if (response.status === HTTP_FORBIDDEN) {
75114
75430
  return "forbidden";
75115
75431
  }
@@ -75134,12 +75450,12 @@ function nextUtcMidnight(nowMs) {
75134
75450
  var openRouterQuotaAdapter = {
75135
75451
  source: OPENROUTER_SOURCE,
75136
75452
  matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === OPENROUTER_HOST,
75137
- fetch: async (apiKey, http7, signal, nowMs) => {
75138
- const key = await openRouterGet(OPENROUTER_KEY_URL, OpenRouterKeySchema, apiKey, http7, signal);
75453
+ fetch: async (apiKey, http8, signal, nowMs) => {
75454
+ const key = await openRouterGet(OPENROUTER_KEY_URL, OpenRouterKeySchema, apiKey, http8, signal);
75139
75455
  if (key === "forbidden") {
75140
75456
  throw new ProviderQuotaError(OPENROUTER_SOURCE, "/api/v1/key refused the key (HTTP 403)");
75141
75457
  }
75142
- const credits = await openRouterGet(OPENROUTER_CREDITS_URL, OpenRouterCreditsSchema, apiKey, http7, signal);
75458
+ const credits = await openRouterGet(OPENROUTER_CREDITS_URL, OpenRouterCreditsSchema, apiKey, http8, signal);
75143
75459
  const { limit, limit_remaining: limitRemaining, limit_reset: limitReset, free_model_daily_requests: free, is_free_tier: isFreeTier } = key.data;
75144
75460
  const windows = [];
75145
75461
  if (limit !== null && limit > 0) {
@@ -75335,6 +75651,7 @@ function realFrontDoorSupervisorPorts(paths) {
75335
75651
  newUuid: import_node_crypto14.randomUUID,
75336
75652
  randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
75337
75653
  credentialRecord: () => readRcSelfHostRecord(realFarmFs, paths.frontdoorDir),
75654
+ webFetch: realRcWebFetch({ allowPrivate: rcWebFetchAllowsPrivateFromEnv(process.env) }),
75338
75655
  log
75339
75656
  }) : void 0;
75340
75657
  const rcCredentialStore = createRcCredentialStore(realFarmFs, paths.frontdoorRcCredentialsDir);