@agentproto/acp 0.1.0-alpha.2 → 0.2.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/client/index.d.ts +35 -1
- package/dist/client/index.mjs +45 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/tunnel/index.d.ts +383 -6
- package/dist/tunnel/index.mjs +740 -23
- package/dist/tunnel/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/tunnel/index.d.ts
CHANGED
|
@@ -81,6 +81,10 @@ interface SpawnFrame {
|
|
|
81
81
|
* line-delimited JSON-RPC.
|
|
82
82
|
*/
|
|
83
83
|
pty?: boolean;
|
|
84
|
+
/** Initial PTY column width. Ignored when `pty` is false. Default 80. */
|
|
85
|
+
cols?: number;
|
|
86
|
+
/** Initial PTY row height. Ignored when `pty` is false. Default 24. */
|
|
87
|
+
rows?: number;
|
|
84
88
|
}
|
|
85
89
|
interface StdinFrame {
|
|
86
90
|
t: "stdin";
|
|
@@ -130,6 +134,20 @@ interface HttpRequestFrame {
|
|
|
130
134
|
/**
|
|
131
135
|
* Generic HTTP-over-tunnel response (DAEMON → HOST). Mirrors
|
|
132
136
|
* `HttpRequestFrame` — see its docs.
|
|
137
|
+
*
|
|
138
|
+
* Two-frame protocol:
|
|
139
|
+
* - SHORT RESPONSES (most HTTP): daemon emits a single `http_response`
|
|
140
|
+
* with body inline. The buffered `forwardHttp` resolves directly
|
|
141
|
+
* from this frame.
|
|
142
|
+
* - STREAMED RESPONSES (text/event-stream, long-lived chunked
|
|
143
|
+
* transfers, large downloads): daemon emits `http_response_head`
|
|
144
|
+
* first with status+headers, then one or more `http_response_chunk`
|
|
145
|
+
* frames, the last of which has `end: true`. The streaming
|
|
146
|
+
* `forwardHttpStream` builds a `ReadableStream` from the chunks.
|
|
147
|
+
*
|
|
148
|
+
* Hosts that only support the legacy buffered API can detect the
|
|
149
|
+
* stream protocol by looking for `http_response_head` and either
|
|
150
|
+
* pull the chunks into a buffer (compat) or refuse to handle.
|
|
133
151
|
*/
|
|
134
152
|
interface HttpResponseFrame {
|
|
135
153
|
t: "http_response";
|
|
@@ -146,6 +164,43 @@ interface HttpResponseFrame {
|
|
|
146
164
|
message: string;
|
|
147
165
|
}>;
|
|
148
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Streaming HTTP response head (DAEMON → HOST). Sent ONCE per
|
|
169
|
+
* streaming response, before any chunk frames. Carries status +
|
|
170
|
+
* headers so the host can finalize the response shape (e.g. write
|
|
171
|
+
* Content-Type) before any body bytes arrive.
|
|
172
|
+
*/
|
|
173
|
+
interface HttpResponseHeadFrame {
|
|
174
|
+
t: "http_response_head";
|
|
175
|
+
reqId: string;
|
|
176
|
+
status: number;
|
|
177
|
+
headers?: Readonly<Record<string, string>>;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Streaming HTTP response body chunk (DAEMON → HOST). Each chunk
|
|
181
|
+
* carries some bytes of the response body. The terminal chunk has
|
|
182
|
+
* `end: true` and MAY include final bytes. An immediate `end: true`
|
|
183
|
+
* with no data signals empty-body close.
|
|
184
|
+
*
|
|
185
|
+
* `error` set on a chunk frame indicates the daemon's upstream
|
|
186
|
+
* failed mid-stream; host SHOULD propagate it as a stream error.
|
|
187
|
+
*/
|
|
188
|
+
interface HttpResponseChunkFrame {
|
|
189
|
+
t: "http_response_chunk";
|
|
190
|
+
reqId: string;
|
|
191
|
+
/** Base64-encoded chunk bytes. Omitted on pure end-of-stream
|
|
192
|
+
* markers (those carry `end: true` only). */
|
|
193
|
+
data?: string;
|
|
194
|
+
/** Set on the last chunk. After this frame the host SHOULD ignore
|
|
195
|
+
* any further frames for this reqId. */
|
|
196
|
+
end?: boolean;
|
|
197
|
+
/** Set when the upstream failed mid-stream. Host surfaces this as
|
|
198
|
+
* a stream error and closes the readable. */
|
|
199
|
+
error?: Readonly<{
|
|
200
|
+
code: string;
|
|
201
|
+
message: string;
|
|
202
|
+
}>;
|
|
203
|
+
}
|
|
149
204
|
interface HelloFrame {
|
|
150
205
|
t: "hello";
|
|
151
206
|
version: typeof TUNNEL_VERSION;
|
|
@@ -155,6 +210,19 @@ interface HelloFrame {
|
|
|
155
210
|
*/
|
|
156
211
|
capabilities: Readonly<{
|
|
157
212
|
pty: boolean;
|
|
213
|
+
/** Daemon can bridge browser-WS upgrades to its local upstream via
|
|
214
|
+
* `ws_open` / `ws_message` / `ws_close`. Hosts MUST gate
|
|
215
|
+
* `forwardWebSocket()` on this — older daemons ignore the frames. */
|
|
216
|
+
wsForward?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Identifiers of the capabilities the daemon serves — its registered
|
|
219
|
+
* agent adapters / tool surfaces. Lets a host that fronts several
|
|
220
|
+
* daemons enumerate and route by what each one can do, without a
|
|
221
|
+
* round-trip. Omitted by older daemons (host treats as "unknown",
|
|
222
|
+
* not "none"). Advisory: the authoritative surface is still the
|
|
223
|
+
* daemon's live `tools/list` over the HTTP relay.
|
|
224
|
+
*/
|
|
225
|
+
tools?: ReadonlyArray<string>;
|
|
158
226
|
}>;
|
|
159
227
|
/** User-friendly daemon label, surfaced in host UIs. */
|
|
160
228
|
label?: string;
|
|
@@ -209,8 +277,105 @@ interface PongFrame {
|
|
|
209
277
|
t: "pong";
|
|
210
278
|
nonce: string;
|
|
211
279
|
}
|
|
212
|
-
|
|
213
|
-
|
|
280
|
+
/**
|
|
281
|
+
* HOST → DAEMON. Graceful drain signal sent during host rollover (e.g.
|
|
282
|
+
* Kubernetes preStop). On receipt the daemon SHOULD:
|
|
283
|
+
* 1. Stop accepting new in-flight work where it can.
|
|
284
|
+
* 2. Close the WS cleanly (does not need to wait — host will follow
|
|
285
|
+
* up with a `close(1012, "service_restart")` ~2 s later as a
|
|
286
|
+
* hard backstop).
|
|
287
|
+
* 3. Reconnect IMMEDIATELY without the usual exponential backoff —
|
|
288
|
+
* the host is mid-deploy, the new replica is already listening.
|
|
289
|
+
*
|
|
290
|
+
* Backward-compatible: daemons that don't know the frame just ignore
|
|
291
|
+
* it (parseFrame returns null for unknown types). The host's close
|
|
292
|
+
* then triggers the daemon's normal backoff loop — worst case ~30 s
|
|
293
|
+
* reconnect gap instead of ~2 s with the handler.
|
|
294
|
+
*/
|
|
295
|
+
interface ReconnectSoonFrame {
|
|
296
|
+
t: "reconnect_soon";
|
|
297
|
+
/** Hint of how long the host plans to wait before forcing close.
|
|
298
|
+
* Optional — daemons MAY use it to decide whether to finish
|
|
299
|
+
* in-flight work or close immediately. */
|
|
300
|
+
reasonMs?: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* HOST → DAEMON. Open a WS connection to the daemon's upstream.
|
|
304
|
+
* Path semantics match `HttpRequestFrame` — absolute URLs rejected,
|
|
305
|
+
* daemon prepends its `ws://<upstream>` base. Headers forwarded as-is
|
|
306
|
+
* (cookies are typically NOT forwarded since the daemon's upstream is
|
|
307
|
+
* loopback; cookie passthrough is opt-in via daemon config).
|
|
308
|
+
*/
|
|
309
|
+
interface WsOpenFrame {
|
|
310
|
+
t: "ws_open";
|
|
311
|
+
/** Correlates every WS frame in both directions for this connection. */
|
|
312
|
+
reqId: string;
|
|
313
|
+
/** Path + querystring on the daemon-local upstream, e.g.
|
|
314
|
+
* `/sessions/abc/pty?cols=80&rows=24`. */
|
|
315
|
+
path: string;
|
|
316
|
+
/** Optional registered upstream to dial instead of the daemon's
|
|
317
|
+
* default gateway — a NAME the daemon resolves to a base URL (never
|
|
318
|
+
* a URL itself, so the host can't point the daemon at an arbitrary
|
|
319
|
+
* origin). Used to reach an imported local service (e.g. a browser
|
|
320
|
+
* capability server) by alias. Absent ⇒ the daemon's own gateway. */
|
|
321
|
+
upstream?: string;
|
|
322
|
+
/** Headers to set on the upstream WS request. Hop-by-hop headers
|
|
323
|
+
* (Connection, Upgrade, Sec-WebSocket-*) MUST be stripped — the
|
|
324
|
+
* daemon's `ws` client manages those. */
|
|
325
|
+
headers?: Readonly<Record<string, string>>;
|
|
326
|
+
/** Subprotocols requested by the browser; forwarded to upstream. */
|
|
327
|
+
protocols?: readonly string[];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* DAEMON → HOST. Result of a `ws_open`. On success the upstream WS is
|
|
331
|
+
* connected and frames can flow. On failure `error` is set; no further
|
|
332
|
+
* frames for this `reqId` will arrive.
|
|
333
|
+
*/
|
|
334
|
+
interface WsOpenAckFrame {
|
|
335
|
+
t: "ws_open_ack";
|
|
336
|
+
reqId: string;
|
|
337
|
+
/** HTTP status from the upstream upgrade. 101 on success; 4xx/5xx
|
|
338
|
+
* when the upstream refused the upgrade. */
|
|
339
|
+
status: number;
|
|
340
|
+
/** Selected subprotocol, if any. */
|
|
341
|
+
protocol?: string;
|
|
342
|
+
/** Set when the daemon couldn't open the upstream (DNS failure,
|
|
343
|
+
* refused, timeout). Distinct from a clean upstream-side close,
|
|
344
|
+
* which arrives as `ws_close`. */
|
|
345
|
+
error?: Readonly<{
|
|
346
|
+
code: string;
|
|
347
|
+
message: string;
|
|
348
|
+
}>;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Either direction. One application-level WS frame's payload. Text
|
|
352
|
+
* frames carry UTF-8 bytes (`binary: false`); binary frames carry
|
|
353
|
+
* arbitrary bytes (`binary: true`). The `data` field is base64-encoded
|
|
354
|
+
* either way so the transport stays JSON.
|
|
355
|
+
*/
|
|
356
|
+
interface WsMessageFrame {
|
|
357
|
+
t: "ws_message";
|
|
358
|
+
reqId: string;
|
|
359
|
+
/** Base64-encoded WS frame payload. */
|
|
360
|
+
data: string;
|
|
361
|
+
/** True for binary frames; false (or omitted) for text. */
|
|
362
|
+
binary?: boolean;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Either direction. Close the bridged WS connection. Includes the
|
|
366
|
+
* close code (1000 normal, 1006 abnormal, etc.) and optional reason.
|
|
367
|
+
* After sending or receiving this frame, neither side will send
|
|
368
|
+
* further frames for `reqId`.
|
|
369
|
+
*/
|
|
370
|
+
interface WsCloseFrame {
|
|
371
|
+
t: "ws_close";
|
|
372
|
+
reqId: string;
|
|
373
|
+
/** WebSocket close code. */
|
|
374
|
+
code: number;
|
|
375
|
+
reason?: string;
|
|
376
|
+
}
|
|
377
|
+
type HostToDaemonFrame = SpawnFrame | StdinFrame | KillFrame | ResizeFrame | HttpRequestFrame | WsOpenFrame | WsMessageFrame | WsCloseFrame | PingFrame | PongFrame | ErrorFrame | ReconnectSoonFrame;
|
|
378
|
+
type DaemonToHostFrame = HelloFrame | SpawnedFrame | StdoutFrame | StderrFrame | ExitFrame | HttpResponseFrame | HttpResponseHeadFrame | HttpResponseChunkFrame | WsOpenAckFrame | WsMessageFrame | WsCloseFrame | PingFrame | PongFrame | ErrorFrame;
|
|
214
379
|
type TunnelFrame = HostToDaemonFrame | DaemonToHostFrame;
|
|
215
380
|
/**
|
|
216
381
|
* Parse a raw WS message into a `TunnelFrame`. Returns null when the
|
|
@@ -271,6 +436,19 @@ interface FrameSink {
|
|
|
271
436
|
* and can mutate it (e.g. force `cwd` into a workspace) or reject.
|
|
272
437
|
*/
|
|
273
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Default ceiling for an upstream WS dial. An upstream that accepts the TCP
|
|
441
|
+
* connection but never completes (or rejects) the WebSocket upgrade would
|
|
442
|
+
* otherwise leave the `ws_open` pending forever — the host never gets an ack or
|
|
443
|
+
* a reject. Bounded so a hung upstream surfaces as a 504 instead.
|
|
444
|
+
*/
|
|
445
|
+
declare const DEFAULT_WS_DIAL_TIMEOUT_MS = 10000;
|
|
446
|
+
/**
|
|
447
|
+
* Default ceiling for an `http_request` forward when the frame carries no
|
|
448
|
+
* `timeoutMs`. Covers connect + buffered body (or connect + headers for a
|
|
449
|
+
* stream, after which the bound is handed to `httpStreamIdleTimeoutMs`).
|
|
450
|
+
*/
|
|
451
|
+
declare const DEFAULT_HTTP_FORWARD_TIMEOUT_MS = 30000;
|
|
274
452
|
interface TunnelServerOptions {
|
|
275
453
|
sink: FrameSink;
|
|
276
454
|
/**
|
|
@@ -291,14 +469,49 @@ interface TunnelServerOptions {
|
|
|
291
469
|
* `error: { code: "http_upstream_not_configured" }`.
|
|
292
470
|
*/
|
|
293
471
|
httpUpstream?: string;
|
|
472
|
+
/**
|
|
473
|
+
* Default ceiling for an `http_request` forward (connect + buffered body, or
|
|
474
|
+
* connect + headers for a stream) when the frame carries no `timeoutMs`.
|
|
475
|
+
* Defaults to DEFAULT_HTTP_FORWARD_TIMEOUT_MS. Disarmed once a streaming
|
|
476
|
+
* response is detected — see `httpStreamIdleTimeoutMs` for the stream phase.
|
|
477
|
+
*/
|
|
478
|
+
httpForwardTimeoutMs?: number;
|
|
479
|
+
/**
|
|
480
|
+
* Idle ceiling between chunks of a STREAMING response (SSE / ndjson). A
|
|
481
|
+
* stalled upstream that sends headers then hangs forever between chunks would
|
|
482
|
+
* otherwise block indefinitely (the regular timeout is disarmed for streams,
|
|
483
|
+
* since they're long-lived by design). Counts time since the last byte and
|
|
484
|
+
* resets on every chunk, so a heartbeating stream is never cut. Omitted ⇒ no
|
|
485
|
+
* idle bound (current behaviour); set it to guard against silent stalls.
|
|
486
|
+
*/
|
|
487
|
+
httpStreamIdleTimeoutMs?: number;
|
|
294
488
|
/** User-friendly label sent in the hello frame. */
|
|
295
489
|
label?: string;
|
|
296
490
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
491
|
+
* Capability identifiers advertised in the hello frame — the agent
|
|
492
|
+
* adapters / tool surfaces this daemon serves (e.g. the gateway's
|
|
493
|
+
* registered adapter ids). Lets a multi-daemon host enumerate and
|
|
494
|
+
* route by capability without a round-trip. Omit to advertise none.
|
|
495
|
+
*/
|
|
496
|
+
tools?: ReadonlyArray<string>;
|
|
497
|
+
/**
|
|
498
|
+
* Whether this daemon advertises PTY support. Set to true only when
|
|
499
|
+
* `spawnPty` is also provided.
|
|
300
500
|
*/
|
|
301
501
|
pty?: boolean;
|
|
502
|
+
/**
|
|
503
|
+
* Factory for PTY-backed processes. When provided (and `pty: true`),
|
|
504
|
+
* spawn frames with `pty: true` use this instead of node:child_process.
|
|
505
|
+
* Injected from the CLI layer so the ACP package stays native-free.
|
|
506
|
+
*/
|
|
507
|
+
spawnPty?: (opts: {
|
|
508
|
+
command: string;
|
|
509
|
+
args: string[];
|
|
510
|
+
cwd?: string;
|
|
511
|
+
env?: Record<string, string>;
|
|
512
|
+
cols: number;
|
|
513
|
+
rows: number;
|
|
514
|
+
}) => PtyProcess;
|
|
302
515
|
/**
|
|
303
516
|
* Optional hook fired once per successful spawn — after authorize
|
|
304
517
|
* passed and the ChildProcess has emitted its `spawn` event.
|
|
@@ -316,6 +529,89 @@ interface TunnelServerOptions {
|
|
|
316
529
|
child: ChildProcess;
|
|
317
530
|
request: SpawnFrame;
|
|
318
531
|
}) => void;
|
|
532
|
+
/**
|
|
533
|
+
* Optional hook fired when the host sends a `reconnect_soon` frame —
|
|
534
|
+
* graceful drain signal during host rollover. The daemon SHOULD
|
|
535
|
+
* close + reconnect immediately (skipping any backoff) so the new
|
|
536
|
+
* host replica picks it up. Default: ignore (the host follows up
|
|
537
|
+
* with `close(1012)` and the daemon's own reconnect supervisor
|
|
538
|
+
* handles it via normal backoff, ~30 s gap).
|
|
539
|
+
*
|
|
540
|
+
* Implementations typically call into their connection supervisor
|
|
541
|
+
* to mark the next reconnect as "no-backoff" then close the sink.
|
|
542
|
+
* `reasonMs` is the host's hint for how long it will wait before
|
|
543
|
+
* forcing close — daemons MAY use it to drain in-flight RPCs first.
|
|
544
|
+
*/
|
|
545
|
+
onReconnectSoon?: (info: {
|
|
546
|
+
reasonMs?: number;
|
|
547
|
+
}) => void;
|
|
548
|
+
/**
|
|
549
|
+
* Factory that dials a WS connection to the local upstream. When
|
|
550
|
+
* provided (and `httpUpstream` is set), the daemon advertises
|
|
551
|
+
* `wsForward` capability and handles `ws_open` frames by piping the
|
|
552
|
+
* upstream WS through the tunnel. Injected so `@agentproto/acp`
|
|
553
|
+
* stays free of a hard `ws` dependency — the CLI provides this
|
|
554
|
+
* factory using its already-pulled-in `ws` package.
|
|
555
|
+
*/
|
|
556
|
+
dialUpstreamWs?: (params: {
|
|
557
|
+
url: string;
|
|
558
|
+
protocols?: readonly string[];
|
|
559
|
+
headers?: Readonly<Record<string, string>>;
|
|
560
|
+
/**
|
|
561
|
+
* Aborted when the dial exceeds `wsDialTimeoutMs` (or the tunnel tears down
|
|
562
|
+
* mid-dial). A cooperating implementation SHOULD abort the in-flight upgrade
|
|
563
|
+
* and reject so the socket isn't left half-open; even if it doesn't, the
|
|
564
|
+
* server bounds the wait independently and rejects the `ws_open`.
|
|
565
|
+
*/
|
|
566
|
+
signal?: AbortSignal;
|
|
567
|
+
}) => Promise<UpstreamWebSocket>;
|
|
568
|
+
/**
|
|
569
|
+
* Ceiling for `dialUpstreamWs` to resolve before the `ws_open` is rejected
|
|
570
|
+
* with 504 `upstream_ws_timeout`. Defaults to DEFAULT_WS_DIAL_TIMEOUT_MS.
|
|
571
|
+
*/
|
|
572
|
+
wsDialTimeoutMs?: number;
|
|
573
|
+
/**
|
|
574
|
+
* Resolve a named upstream (a `ws_open` frame's `upstream` field) to an
|
|
575
|
+
* HTTP base URL the daemon should dial instead of `httpUpstream`. Lets a
|
|
576
|
+
* host reach an imported local service by alias (e.g. a browser capability
|
|
577
|
+
* server) without ever naming a raw origin — the daemon owns the name→URL
|
|
578
|
+
* mapping. Return undefined for an unknown alias (the open is rejected).
|
|
579
|
+
* Omitted ⇒ every `ws_open` dials the default `httpUpstream`.
|
|
580
|
+
*/
|
|
581
|
+
resolveWsUpstream?: (upstream: string) => string | undefined | Promise<string | undefined>;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Minimal upstream WS surface — satisfied by the `ws` library's
|
|
585
|
+
* client socket. Daemons supplying `dialUpstreamWs` resolve into
|
|
586
|
+
* one of these as soon as the WS is OPEN. `protocol` is the
|
|
587
|
+
* subprotocol negotiated by the upstream (empty string when none).
|
|
588
|
+
*/
|
|
589
|
+
interface UpstreamWebSocket {
|
|
590
|
+
readonly protocol: string;
|
|
591
|
+
/** Send a frame to the upstream. */
|
|
592
|
+
send(data: Buffer | string, opts: {
|
|
593
|
+
binary: boolean;
|
|
594
|
+
}): void;
|
|
595
|
+
/** Close the upstream connection. */
|
|
596
|
+
close(code?: number, reason?: string): void;
|
|
597
|
+
/** Subscribe to inbound frames. */
|
|
598
|
+
onMessage(handler: (data: Buffer, isBinary: boolean) => void): void;
|
|
599
|
+
/** Subscribe to upstream close. */
|
|
600
|
+
onClose(handler: (code: number, reason: string) => void): void;
|
|
601
|
+
/** Subscribe to transport errors. */
|
|
602
|
+
onError(handler: (err: Error) => void): void;
|
|
603
|
+
}
|
|
604
|
+
/** Minimal PTY process surface — satisfied by node-pty's IPty. */
|
|
605
|
+
interface PtyProcess {
|
|
606
|
+
readonly pid: number;
|
|
607
|
+
write(data: string): void;
|
|
608
|
+
resize(cols: number, rows: number): void;
|
|
609
|
+
kill(signal?: string): void;
|
|
610
|
+
onData(handler: (data: string) => void): void;
|
|
611
|
+
onExit(handler: (event: {
|
|
612
|
+
exitCode: number;
|
|
613
|
+
signal?: number;
|
|
614
|
+
}) => void): void;
|
|
319
615
|
}
|
|
320
616
|
interface TunnelServer {
|
|
321
617
|
/** Currently-live execId → ChildProcess. Read-only diagnostic. */
|
|
@@ -352,6 +648,16 @@ interface TunnelHttpResponse {
|
|
|
352
648
|
message: string;
|
|
353
649
|
}>;
|
|
354
650
|
}
|
|
651
|
+
/** Resolved response from `forwardHttpStream`. Body is a Web
|
|
652
|
+
* `ReadableStream<Uint8Array>` — consume it to receive chunks as
|
|
653
|
+
* they arrive from the daemon's upstream (SSE, long-poll, NDJSON).
|
|
654
|
+
* The stream closes when the daemon's upstream closes OR an upstream
|
|
655
|
+
* error occurs (surfaced as a stream error on the reader). */
|
|
656
|
+
interface TunnelHttpStreamResponse {
|
|
657
|
+
status: number;
|
|
658
|
+
headers: Readonly<Record<string, string>>;
|
|
659
|
+
body: ReadableStream<Uint8Array>;
|
|
660
|
+
}
|
|
355
661
|
interface TunnelHttpRequest {
|
|
356
662
|
method: string;
|
|
357
663
|
path: string;
|
|
@@ -360,6 +666,48 @@ interface TunnelHttpRequest {
|
|
|
360
666
|
/** Per-request timeout in ms. Default 30_000. */
|
|
361
667
|
timeoutMs?: number;
|
|
362
668
|
}
|
|
669
|
+
interface TunnelWebSocketOpenRequest {
|
|
670
|
+
/** Path + querystring on the daemon-local upstream. Daemon prepends
|
|
671
|
+
* its `ws://<upstream>` base; absolute URLs are rejected. */
|
|
672
|
+
path: string;
|
|
673
|
+
/** Optional registered upstream to dial instead of the daemon's default
|
|
674
|
+
* gateway — a NAME the daemon resolves to a base URL (e.g. an imported
|
|
675
|
+
* service alias). The host never names a raw origin. */
|
|
676
|
+
upstream?: string;
|
|
677
|
+
/** Headers forwarded to the upstream upgrade. Hop-by-hop / Sec-* are
|
|
678
|
+
* stripped by the daemon. Cookies are NOT forwarded unless the
|
|
679
|
+
* daemon was started with `--upstream-cookies`. */
|
|
680
|
+
headers?: Readonly<Record<string, string>>;
|
|
681
|
+
/** Subprotocols requested. Echoed in `protocol` on success. */
|
|
682
|
+
protocols?: readonly string[];
|
|
683
|
+
/** How long to wait for the upstream upgrade before erroring. The
|
|
684
|
+
* message stream itself has no timeout (WS lives as long as either
|
|
685
|
+
* end keeps it open). Default 30_000. */
|
|
686
|
+
openTimeoutMs?: number;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Bridged WS connection. Mirrors the surface of a browser WebSocket
|
|
690
|
+
* minus URL/protocol bookkeeping — the host already chose those when
|
|
691
|
+
* calling `forwardWebSocket`. Messages are typed as text (UTF-8 string)
|
|
692
|
+
* or binary (Uint8Array); the bridge preserves the upstream's framing.
|
|
693
|
+
*/
|
|
694
|
+
interface TunnelWebSocket {
|
|
695
|
+
/** True until `close` runs in either direction. */
|
|
696
|
+
readonly readyState: "open" | "closing" | "closed";
|
|
697
|
+
/** Selected subprotocol from the upstream's upgrade response, if any. */
|
|
698
|
+
readonly protocol: string | null;
|
|
699
|
+
/** Send a frame to the daemon's upstream. No-op when not open. */
|
|
700
|
+
send(data: string | Uint8Array): void;
|
|
701
|
+
/** Initiate a clean close. Idempotent. */
|
|
702
|
+
close(code?: number, reason?: string): void;
|
|
703
|
+
/** Subscribe to inbound frames. Returns an unsubscribe fn. */
|
|
704
|
+
onMessage(listener: (data: Buffer, isBinary: boolean) => void): () => void;
|
|
705
|
+
/** Subscribe to close. Fires exactly once (after either side closes
|
|
706
|
+
* OR the tunnel itself drops). Returns an unsubscribe fn. */
|
|
707
|
+
onClose(listener: (code: number, reason: string) => void): () => void;
|
|
708
|
+
/** Subscribe to transport-level errors. Closes follow shortly. */
|
|
709
|
+
onError(listener: (err: Error) => void): () => void;
|
|
710
|
+
}
|
|
363
711
|
interface TunnelClientOptions {
|
|
364
712
|
sink: FrameSink;
|
|
365
713
|
/**
|
|
@@ -374,6 +722,10 @@ interface TunnelSpawnOptions {
|
|
|
374
722
|
env?: Readonly<Record<string, string>>;
|
|
375
723
|
/** Allocate a PTY on the daemon. Requires `hello.capabilities.pty`. */
|
|
376
724
|
pty?: boolean;
|
|
725
|
+
/** Initial PTY column width (ignored when `pty` is false). Default 80. */
|
|
726
|
+
cols?: number;
|
|
727
|
+
/** Initial PTY row height (ignored when `pty` is false). Default 24. */
|
|
728
|
+
rows?: number;
|
|
377
729
|
}
|
|
378
730
|
/**
|
|
379
731
|
* Minimal ChildProcess-shaped duck. Covers the surface our ACP arm
|
|
@@ -389,6 +741,8 @@ interface TunnelChildProcess {
|
|
|
389
741
|
on(event: "exit", listener: (code: number | null, signal: string | null) => void): this;
|
|
390
742
|
on(event: "error", listener: (err: Error) => void): this;
|
|
391
743
|
kill(signal?: NodeJS.Signals | number): boolean;
|
|
744
|
+
/** Send a terminal resize to the daemon. No-op if the exec isn't PTY-backed. */
|
|
745
|
+
resize(cols: number, rows: number): void;
|
|
392
746
|
}
|
|
393
747
|
interface TunnelClient {
|
|
394
748
|
/**
|
|
@@ -405,8 +759,31 @@ interface TunnelClient {
|
|
|
405
759
|
* which this promise resolves to. Rejects on tunnel-transport
|
|
406
760
|
* failure; the daemon-reported HTTP status (incl. 5xx) is returned
|
|
407
761
|
* in the resolved value.
|
|
762
|
+
*
|
|
763
|
+
* Streaming responses (text/event-stream, NDJSON) are buffered into
|
|
764
|
+
* the returned Buffer. For real streaming, use `forwardHttpStream`.
|
|
408
765
|
*/
|
|
409
766
|
forwardHttp(req: TunnelHttpRequest): Promise<TunnelHttpResponse>;
|
|
767
|
+
/**
|
|
768
|
+
* Stream an HTTP response through the tunnel. The promise resolves
|
|
769
|
+
* as soon as the response head (status + headers) arrives — the
|
|
770
|
+
* body chunks then flow through `body` (a ReadableStream) until the
|
|
771
|
+
* daemon's upstream closes. Required for SSE / NDJSON / long-poll
|
|
772
|
+
* upstreams where buffering until the end would defeat the point.
|
|
773
|
+
* For one-shot HTTP, prefer `forwardHttp`.
|
|
774
|
+
*/
|
|
775
|
+
forwardHttpStream(req: TunnelHttpRequest): Promise<TunnelHttpStreamResponse>;
|
|
776
|
+
/**
|
|
777
|
+
* Open a bridged WebSocket through the tunnel. The promise resolves
|
|
778
|
+
* once the daemon has completed the upstream upgrade; from that
|
|
779
|
+
* point messages flow via the returned `TunnelWebSocket`.
|
|
780
|
+
*
|
|
781
|
+
* Rejects on:
|
|
782
|
+
* - missing `hello.capabilities.wsForward` — older daemon, no bridge
|
|
783
|
+
* - upstream upgrade failure (4xx/5xx) — `cause` carries the status
|
|
784
|
+
* - timeout waiting for `ws_open_ack`
|
|
785
|
+
*/
|
|
786
|
+
forwardWebSocket(req: TunnelWebSocketOpenRequest): Promise<TunnelWebSocket>;
|
|
410
787
|
close(): Promise<void>;
|
|
411
788
|
}
|
|
412
789
|
declare function createTunnelClient(opts: TunnelClientOptions): TunnelClient;
|
|
@@ -439,4 +816,4 @@ interface WebSocketLike {
|
|
|
439
816
|
}
|
|
440
817
|
declare function wrapWebSocket(ws: WebSocketLike): FrameSink;
|
|
441
818
|
|
|
442
|
-
export { type DaemonToHostFrame, type ErrorFrame, type ExitFrame, type FrameSink, type HelloFrame, type HostToDaemonFrame, type KillFrame, type PingFrame, type PongFrame, type ResizeFrame, type SpawnFrame, type SpawnedFrame, type StderrFrame, type StdinFrame, type StdoutFrame, TUNNEL_VERSION, type TunnelChildProcess, type TunnelClient, type TunnelClientOptions, type TunnelFrame, type TunnelServer, type TunnelServerOptions, type TunnelSpawnOptions, type WebSocketLike, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };
|
|
819
|
+
export { DEFAULT_HTTP_FORWARD_TIMEOUT_MS, DEFAULT_WS_DIAL_TIMEOUT_MS, type DaemonToHostFrame, type ErrorFrame, type ExitFrame, type FrameSink, type HelloFrame, type HostToDaemonFrame, type KillFrame, type PingFrame, type PongFrame, type PtyProcess, type ReconnectSoonFrame, type ResizeFrame, type SpawnFrame, type SpawnedFrame, type StderrFrame, type StdinFrame, type StdoutFrame, TUNNEL_VERSION, type TunnelChildProcess, type TunnelClient, type TunnelClientOptions, type TunnelFrame, type TunnelHttpRequest, type TunnelHttpResponse, type TunnelHttpStreamResponse, type TunnelServer, type TunnelServerOptions, type TunnelSpawnOptions, type TunnelWebSocket, type TunnelWebSocketOpenRequest, type UpstreamWebSocket, type WebSocketLike, type WsCloseFrame, type WsMessageFrame, type WsOpenAckFrame, type WsOpenFrame, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };
|