@agentproto/acp 0.1.0 → 0.3.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.
@@ -58,6 +58,8 @@ function encodeData(bytes) {
58
58
  function decodeData(data) {
59
59
  return Buffer.from(data, "base64");
60
60
  }
61
+ var DEFAULT_WS_DIAL_TIMEOUT_MS = 1e4;
62
+ var DEFAULT_HTTP_FORWARD_TIMEOUT_MS = 3e4;
61
63
  function createTunnelServer(opts) {
62
64
  const children = /* @__PURE__ */ new Map();
63
65
  const ptyProcs = /* @__PURE__ */ new Map();
@@ -68,7 +70,8 @@ function createTunnelServer(opts) {
68
70
  version: TUNNEL_VERSION,
69
71
  capabilities: {
70
72
  pty: opts.pty === true,
71
- wsForward: opts.dialUpstreamWs !== void 0 && !!opts.httpUpstream
73
+ wsForward: opts.dialUpstreamWs !== void 0 && !!opts.httpUpstream,
74
+ ...opts.tools && opts.tools.length ? { tools: opts.tools } : {}
72
75
  },
73
76
  label: opts.label,
74
77
  daemon: {
@@ -383,22 +386,69 @@ function createTunnelServer(opts) {
383
386
  if (HOP_BY_HOP.has(k.toLowerCase())) continue;
384
387
  headers[k] = v;
385
388
  }
386
- const base = opts.httpUpstream.replace(/^http(s?):\/\//, "ws$1://");
389
+ let httpBase = opts.httpUpstream;
390
+ if (req.upstream) {
391
+ const resolved = await opts.resolveWsUpstream?.(req.upstream);
392
+ if (!resolved) {
393
+ reject(
394
+ 502,
395
+ "unknown_ws_upstream",
396
+ `No WS upstream registered for '${req.upstream}'.`
397
+ );
398
+ return;
399
+ }
400
+ httpBase = resolved;
401
+ }
402
+ const base = httpBase.replace(/^http(s?):\/\//, "ws$1://");
387
403
  const url = `${base.replace(/\/$/, "")}${req.path}`;
404
+ const dialTimeoutMs = opts.wsDialTimeoutMs ?? DEFAULT_WS_DIAL_TIMEOUT_MS;
405
+ const dialAbort = new AbortController();
406
+ let dialTimedOut = false;
407
+ let dialTimer = null;
408
+ const dialPromise = opts.dialUpstreamWs({
409
+ url,
410
+ ...req.protocols && req.protocols.length > 0 ? { protocols: req.protocols } : {},
411
+ headers,
412
+ signal: dialAbort.signal
413
+ });
414
+ const timeoutPromise = new Promise((_resolve, rej) => {
415
+ dialTimer = setTimeout(() => {
416
+ dialTimedOut = true;
417
+ dialAbort.abort(new Error("ws_dial_timeout"));
418
+ rej(new Error(`upstream WS dial timed out after ${dialTimeoutMs}ms`));
419
+ }, dialTimeoutMs);
420
+ if (typeof dialTimer !== "number") dialTimer.unref();
421
+ });
388
422
  let upstream;
389
423
  try {
390
- upstream = await opts.dialUpstreamWs({
391
- url,
392
- ...req.protocols && req.protocols.length > 0 ? { protocols: req.protocols } : {},
393
- headers
394
- });
424
+ upstream = await Promise.race([dialPromise, timeoutPromise]);
395
425
  } catch (err) {
426
+ if (dialTimer) clearTimeout(dialTimer);
427
+ if (dialTimedOut) {
428
+ dialPromise.then(
429
+ (sock) => {
430
+ try {
431
+ sock.close(1001, "dial_timeout");
432
+ } catch {
433
+ }
434
+ },
435
+ () => {
436
+ }
437
+ );
438
+ reject(
439
+ 504,
440
+ "upstream_ws_timeout",
441
+ `Upstream did not complete the WS upgrade within ${dialTimeoutMs}ms.`
442
+ );
443
+ return;
444
+ }
396
445
  const message = err instanceof Error ? err.message : String(err);
397
446
  const m = message.match(/Unexpected server response: (\d+)/);
398
447
  const status = m ? Number.parseInt(m[1], 10) : 502;
399
448
  reject(status, "upstream_ws_failed", message);
400
449
  return;
401
450
  }
451
+ if (dialTimer) clearTimeout(dialTimer);
402
452
  if (closed) {
403
453
  try {
404
454
  upstream.close(1001, "tunnel_closed");
@@ -501,7 +551,7 @@ function createTunnelServer(opts) {
501
551
  }
502
552
  const url = `${opts.httpUpstream.replace(/\/$/, "")}${req.path}`;
503
553
  const controller = new AbortController();
504
- const timeoutMs = req.timeoutMs ?? 3e4;
554
+ const timeoutMs = req.timeoutMs ?? opts.httpForwardTimeoutMs ?? DEFAULT_HTTP_FORWARD_TIMEOUT_MS;
505
555
  let timer = setTimeout(
506
556
  () => controller.abort(),
507
557
  timeoutMs
@@ -537,9 +587,41 @@ function createTunnelServer(opts) {
537
587
  headers: outHeaders
538
588
  });
539
589
  const reader = upstreamRes.body.getReader();
590
+ const idleMs = opts.httpStreamIdleTimeoutMs;
591
+ const IDLE = /* @__PURE__ */ Symbol("idle");
592
+ const readChunk = async () => {
593
+ if (!idleMs) return reader.read();
594
+ let idleTimer = null;
595
+ const idle = new Promise((resolve) => {
596
+ idleTimer = setTimeout(() => resolve(IDLE), idleMs);
597
+ if (typeof idleTimer !== "number") idleTimer.unref();
598
+ });
599
+ try {
600
+ return await Promise.race([reader.read(), idle]);
601
+ } finally {
602
+ if (idleTimer) clearTimeout(idleTimer);
603
+ }
604
+ };
540
605
  try {
541
606
  while (true) {
542
- const { value, done } = await reader.read();
607
+ const chunk = await readChunk();
608
+ if (chunk === IDLE) {
609
+ try {
610
+ await reader.cancel();
611
+ } catch {
612
+ }
613
+ opts.sink.send({
614
+ t: "http_response_chunk",
615
+ reqId: req.reqId,
616
+ end: true,
617
+ error: {
618
+ code: "upstream_stream_idle_timeout",
619
+ message: `No data from upstream for ${idleMs}ms.`
620
+ }
621
+ });
622
+ return;
623
+ }
624
+ const { value, done } = chunk;
543
625
  if (done) {
544
626
  opts.sink.send({
545
627
  t: "http_response_chunk",
@@ -1031,6 +1113,7 @@ function createTunnelClient(opts) {
1031
1113
  t: "ws_open",
1032
1114
  reqId,
1033
1115
  path: req.path,
1116
+ ...req.upstream ? { upstream: req.upstream } : {},
1034
1117
  ...req.headers ? { headers: req.headers } : {},
1035
1118
  ...req.protocols && req.protocols.length > 0 ? { protocols: req.protocols } : {}
1036
1119
  };
@@ -1280,6 +1363,6 @@ function wrapWebSocket(ws) {
1280
1363
  };
1281
1364
  }
1282
1365
 
1283
- export { TUNNEL_VERSION, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };
1366
+ export { DEFAULT_HTTP_FORWARD_TIMEOUT_MS, DEFAULT_WS_DIAL_TIMEOUT_MS, TUNNEL_VERSION, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };
1284
1367
  //# sourceMappingURL=index.mjs.map
1285
1368
  //# sourceMappingURL=index.mjs.map