cursor-opencode-provider 0.7.1 → 0.7.3

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.
Files changed (50) hide show
  1. package/README.md +8 -5
  2. package/dist/context/agents.js +1 -1
  3. package/dist/context/build.d.ts +2 -0
  4. package/dist/context/build.js +22 -10
  5. package/dist/context/epoch.d.ts +59 -0
  6. package/dist/context/epoch.js +224 -0
  7. package/dist/context/frozen.d.ts +4 -2
  8. package/dist/context/frozen.js +31 -11
  9. package/dist/context/index.d.ts +2 -0
  10. package/dist/context/index.js +2 -0
  11. package/dist/context/overlay.d.ts +44 -0
  12. package/dist/context/overlay.js +96 -0
  13. package/dist/debug.d.ts +9 -1
  14. package/dist/debug.js +46 -5
  15. package/dist/host-event-bridge.d.ts +22 -0
  16. package/dist/host-event-bridge.js +22 -0
  17. package/dist/image-input.js +36 -11
  18. package/dist/index.d.ts +3 -0
  19. package/dist/language-model.d.ts +51 -3
  20. package/dist/language-model.js +569 -140
  21. package/dist/model-config.js +5 -1
  22. package/dist/plugin-opencode2.js +10 -4
  23. package/dist/plugin.js +26 -15
  24. package/dist/pricing-data.d.ts +40 -0
  25. package/dist/pricing-data.js +40 -0
  26. package/dist/protocol/ask-question.d.ts +8 -9
  27. package/dist/protocol/ask-question.js +8 -9
  28. package/dist/protocol/conversation-bind.js +18 -8
  29. package/dist/protocol/create-plan.d.ts +6 -7
  30. package/dist/protocol/create-plan.js +4 -5
  31. package/dist/protocol/messages.js +18 -1
  32. package/dist/protocol/struct.js +1 -1
  33. package/dist/protocol/switch-mode.d.ts +3 -4
  34. package/dist/protocol/switch-mode.js +21 -12
  35. package/dist/protocol/tool-call-bridge.d.ts +4 -1
  36. package/dist/protocol/tool-call-bridge.js +27 -5
  37. package/dist/protocol/tools.d.ts +4 -2
  38. package/dist/protocol/tools.js +346 -55
  39. package/dist/session-directory.d.ts +23 -4
  40. package/dist/session-directory.js +36 -4
  41. package/dist/session.d.ts +23 -3
  42. package/dist/session.js +68 -8
  43. package/dist/shared.d.ts +2 -0
  44. package/dist/shared.js +2 -0
  45. package/dist/transport/connect.js +78 -10
  46. package/dist/transport/https-proxy.d.ts +56 -0
  47. package/dist/transport/https-proxy.js +348 -0
  48. package/dist/usage.d.ts +11 -0
  49. package/dist/usage.js +33 -7
  50. package/package.json +1 -1
@@ -5,13 +5,32 @@
5
5
  * supplied once per invocation by the 1.x host. OpenCode 2.0 runs a single
6
6
  * daemon across many projects, so `process.cwd()` captured at sdk-creation
7
7
  * time is wrong for any session but the one open when the daemon started.
8
- * The only place the 2.0 runtime names a session's actual directory is
9
- * `SessionInfo.location.directory`, reachable from `session.hook("context")`'s
10
- * `sessionID` via `ctx.session.get()`. Record it there and let the provider
11
- * read it back by session id, same mechanism as `compaction-marker.ts`.
8
+ *
9
+ * The 2.0 runtime names a session directory in two places:
10
+ * - request header `x-opencode-directory` (per-request; preferred in the LM)
11
+ * - `ctx.session.get()` → flat `info.directory`, or legacy
12
+ * `info.location.directory`, reachable from `session.hook("context")`
13
+ *
14
+ * This module records the session-get value by id so the language model can
15
+ * fall back when the header is absent, same mechanism as `compaction-marker.ts`.
12
16
  *
13
17
  * Bounded so a long-lived server cannot accumulate ids for dead sessions.
14
18
  */
15
19
  export declare function markSessionDirectory(sessionID: string, directory: string | undefined): void;
16
20
  export declare function getSessionDirectory(sessionID: string | undefined): string | undefined;
17
21
  export declare function clearSessionDirectories(): void;
22
+ /**
23
+ * Active session workspace directory from OpenCode 2.0 request headers.
24
+ * Values may be URI-encoded.
25
+ */
26
+ export declare function opencodeDirectoryHeader(headers: Record<string, string | undefined> | undefined): string | undefined;
27
+ /**
28
+ * Resolve the workspace root for a model turn.
29
+ * Prefer the per-request header, then the session mark, then static options/cwd.
30
+ */
31
+ export declare function resolveSessionWorkspaceRoot(input: {
32
+ sessionKey?: string;
33
+ headers?: Record<string, string | undefined>;
34
+ workspaceRoot?: string;
35
+ cwd?: string;
36
+ }): string;
@@ -5,13 +5,18 @@
5
5
  * supplied once per invocation by the 1.x host. OpenCode 2.0 runs a single
6
6
  * daemon across many projects, so `process.cwd()` captured at sdk-creation
7
7
  * time is wrong for any session but the one open when the daemon started.
8
- * The only place the 2.0 runtime names a session's actual directory is
9
- * `SessionInfo.location.directory`, reachable from `session.hook("context")`'s
10
- * `sessionID` via `ctx.session.get()`. Record it there and let the provider
11
- * read it back by session id, same mechanism as `compaction-marker.ts`.
8
+ *
9
+ * The 2.0 runtime names a session directory in two places:
10
+ * - request header `x-opencode-directory` (per-request; preferred in the LM)
11
+ * - `ctx.session.get()` → flat `info.directory`, or legacy
12
+ * `info.location.directory`, reachable from `session.hook("context")`
13
+ *
14
+ * This module records the session-get value by id so the language model can
15
+ * fall back when the header is absent, same mechanism as `compaction-marker.ts`.
12
16
  *
13
17
  * Bounded so a long-lived server cannot accumulate ids for dead sessions.
14
18
  */
19
+ import path from "node:path";
15
20
  const MAX_TRACKED_SESSIONS = 256;
16
21
  const sessionDirectories = new Map();
17
22
  export function markSessionDirectory(sessionID, directory) {
@@ -33,3 +38,30 @@ export function getSessionDirectory(sessionID) {
33
38
  export function clearSessionDirectories() {
34
39
  sessionDirectories.clear();
35
40
  }
41
+ /**
42
+ * Active session workspace directory from OpenCode 2.0 request headers.
43
+ * Values may be URI-encoded.
44
+ */
45
+ export function opencodeDirectoryHeader(headers) {
46
+ if (!headers)
47
+ return undefined;
48
+ const raw = headers["x-opencode-directory"] ?? headers["X-Opencode-Directory"];
49
+ if (typeof raw !== "string" || raw.trim().length === 0)
50
+ return undefined;
51
+ const trimmed = raw.trim();
52
+ try {
53
+ return decodeURIComponent(trimmed);
54
+ }
55
+ catch {
56
+ return trimmed;
57
+ }
58
+ }
59
+ /**
60
+ * Resolve the workspace root for a model turn.
61
+ * Prefer the per-request header, then the session mark, then static options/cwd.
62
+ */
63
+ export function resolveSessionWorkspaceRoot(input) {
64
+ return path.resolve(opencodeDirectoryHeader(input.headers) ??
65
+ getSessionDirectory(input.sessionKey) ??
66
+ (input.workspaceRoot || input.cwd || process.cwd()));
67
+ }
package/dist/session.d.ts CHANGED
@@ -131,6 +131,8 @@ export type CursorSession = {
131
131
  stepCompletes: number;
132
132
  displayToolCalls: number;
133
133
  execRequests: number;
134
+ createPlanInTurn?: boolean;
135
+ switchModeInTurn?: boolean;
134
136
  };
135
137
  /** OpenCode session whose own or descendant activity renews tool leases. */
136
138
  openCodeSessionId?: string;
@@ -186,6 +188,12 @@ export type CursorSession = {
186
188
  * stream instead of emitting tool-call parts OpenCode will reject.
187
189
  */
188
190
  allowTools: boolean;
191
+ /**
192
+ * Host-enabled tool names for *this* Run (what actually arrived). May be a
193
+ * subset of the epoch advertisement when plan denies edit — advertise fully
194
+ * for cache stability, but refuse exec outside this set.
195
+ */
196
+ permittedToolNames?: ReadonlySet<string>;
189
197
  /**
190
198
  * Best-effort held-Run activity counters used only for diagnostics. Displayed
191
199
  * AI SDK usage comes from checkpoint occupancy (tool steps) and TurnEnded
@@ -245,12 +253,24 @@ export declare class SessionManager {
245
253
  private readonly maxOpenSessions;
246
254
  constructor(options?: SessionManagerOptions);
247
255
  registerSession(session: CursorSession): void;
256
+ /** Live open Run for this OpenCode session id, if any. */
257
+ findOpenByOpenCodeSessionId(openCodeSessionId: string | undefined): CursorSession | undefined;
258
+ isActivelyPumping(session: CursorSession): boolean;
259
+ /**
260
+ * Clear display-only bridged pendings that do not require a Cursor exec write.
261
+ * Used when the host starts a fresh user turn instead of returning the bridged
262
+ * tool result (e.g. human `continue` while a todowrite mirror is outstanding).
263
+ *
264
+ * @returns number of bridged pendings settled
265
+ */
266
+ settleBridgedPending(session: CursorSession): number;
248
267
  private isPumping;
249
268
  /**
250
269
  * Force-close the oldest idle open session(s) once registration exceeds the
251
- * cap. Never closes a session with a live pull() mid-await (see the same
252
- * guard in the supersede check above) — an unclosable backlog of genuinely
253
- * active sessions just means the cap can't be enforced until one finishes.
270
+ * cap. Never closes a session with a live pull() mid-await, or one still
271
+ * holding pending execs (continuation / fresh-turn cancel+drain owns those).
272
+ * An unclosable backlog of genuinely active sessions just means the cap
273
+ * can't be enforced until one finishes.
254
274
  */
255
275
  private enforceOpenSessionCap;
256
276
  recordSemanticProgress(session: CursorSession, at?: number): void;
package/dist/session.js CHANGED
@@ -109,10 +109,26 @@ export class SessionManager {
109
109
  `— leaving it to finish or expire naturally instead of interrupting it`);
110
110
  }
111
111
  else {
112
- trace(`sessionManager.registerSession: superseding stale session ${prior.sessionId} ` +
113
- `(openCodeSessionId=${session.openCodeSessionId}, pending=${prior.pending.size}) ` +
114
- `with new session ${session.sessionId}`);
115
- this.close(prior, "superseded-by-new-run");
112
+ // Display-only bridged pendings do not need a Cursor exec write — settle
113
+ // them first so a todowrite-only prior can still be superseded cleanly.
114
+ const settled = this.settleBridgedPending(prior);
115
+ if (settled > 0) {
116
+ trace(`sessionManager.registerSession: settled ${settled} bridged pending on prior ` +
117
+ `session ${prior.sessionId} before supersede`);
118
+ }
119
+ if (prior.pending.size > 0) {
120
+ // Real execs still outstanding — preparePriorSessionForFreshTurn should
121
+ // have cancelled + drained. Do not mid-exec supersede.
122
+ trace(`sessionManager.registerSession: openCodeSessionId=${session.openCodeSessionId} ` +
123
+ `has prior session ${prior.sessionId} with ${prior.pending.size} still-pending ` +
124
+ `exec(s) — refusing supersede; leaving held Run for cancel/drain/continuation`);
125
+ }
126
+ else {
127
+ trace(`sessionManager.registerSession: superseding stale session ${prior.sessionId} ` +
128
+ `(openCodeSessionId=${session.openCodeSessionId}, pending=${prior.pending.size}) ` +
129
+ `with new session ${session.sessionId}`);
130
+ this.close(prior, "superseded-by-new-run");
131
+ }
116
132
  }
117
133
  }
118
134
  this.byOpenCodeSessionId.set(session.openCodeSessionId, session);
@@ -120,14 +136,54 @@ export class SessionManager {
120
136
  this.enforceOpenSessionCap(session);
121
137
  this.subscribeTerminal(session);
122
138
  }
139
+ /** Live open Run for this OpenCode session id, if any. */
140
+ findOpenByOpenCodeSessionId(openCodeSessionId) {
141
+ if (!openCodeSessionId)
142
+ return undefined;
143
+ const session = this.byOpenCodeSessionId.get(openCodeSessionId);
144
+ return session && !session.closed ? session : undefined;
145
+ }
146
+ isActivelyPumping(session) {
147
+ return this.isPumping(session);
148
+ }
149
+ /**
150
+ * Clear display-only bridged pendings that do not require a Cursor exec write.
151
+ * Used when the host starts a fresh user turn instead of returning the bridged
152
+ * tool result (e.g. human `continue` while a todowrite mirror is outstanding).
153
+ *
154
+ * @returns number of bridged pendings settled
155
+ */
156
+ settleBridgedPending(session) {
157
+ if (session.closed)
158
+ return 0;
159
+ let settled = 0;
160
+ for (const [execId, pending] of [...session.pending.entries()]) {
161
+ if (!pending.bridged)
162
+ continue;
163
+ if (pending.state === "claimed")
164
+ continue;
165
+ const key = this.key(session.sessionId, execId);
166
+ this.putTombstone(key, "delivered");
167
+ session.pending.delete(execId);
168
+ this.byExecId.delete(key);
169
+ settled++;
170
+ }
171
+ if (settled > 0) {
172
+ if (session.pending.size === 0)
173
+ this.recordSemanticProgress(session);
174
+ this.scheduleHardDeadline(session);
175
+ }
176
+ return settled;
177
+ }
123
178
  isPumping(session) {
124
179
  return session.pumpOwner != null || session.pumpActive;
125
180
  }
126
181
  /**
127
182
  * Force-close the oldest idle open session(s) once registration exceeds the
128
- * cap. Never closes a session with a live pull() mid-await (see the same
129
- * guard in the supersede check above) — an unclosable backlog of genuinely
130
- * active sessions just means the cap can't be enforced until one finishes.
183
+ * cap. Never closes a session with a live pull() mid-await, or one still
184
+ * holding pending execs (continuation / fresh-turn cancel+drain owns those).
185
+ * An unclosable backlog of genuinely active sessions just means the cap
186
+ * can't be enforced until one finishes.
131
187
  */
132
188
  enforceOpenSessionCap(justRegistered) {
133
189
  while (this.sessions.size > this.maxOpenSessions) {
@@ -135,13 +191,17 @@ export class SessionManager {
135
191
  for (const candidate of this.sessions) {
136
192
  if (candidate === justRegistered || this.isPumping(candidate))
137
193
  continue;
194
+ // Held-open Runs with pending tools must not be cap-evicted — that is
195
+ // mid-exec supersede by another name and throws away the prefix.
196
+ if (candidate.pending.size > 0)
197
+ continue;
138
198
  if (!oldest || candidate.createdAt < oldest.createdAt)
139
199
  oldest = candidate;
140
200
  }
141
201
  if (!oldest) {
142
202
  trace(`sessionManager.registerSession: open session cap exceeded ` +
143
203
  `(${this.sessions.size} > ${this.maxOpenSessions}) but no idle session is ` +
144
- `available to close — all remaining sessions are actively pumping`);
204
+ `available to close — all remaining sessions are pumping or held-pending`);
145
205
  return;
146
206
  }
147
207
  trace(`sessionManager.registerSession: open session cap exceeded ` +
package/dist/shared.d.ts CHANGED
@@ -4,6 +4,8 @@ export declare const FALLBACK_CLIENT_VERSION = "cli-2026.07.09-a3815c0";
4
4
  export declare const CURSOR_PROVIDER_ID = "cursor";
5
5
  /** Private provider option injected by the OpenCode plugin for summary turns. */
6
6
  export declare const CURSOR_COMPACTION_OPTION = "opencodeCompaction";
7
+ /** OpenCode host rebuilt its history without a provider compaction turn. */
8
+ export declare const CURSOR_HISTORY_REWRITE_OPTION = "opencodeHistoryRewrite";
7
9
  /** Current OpenCode primary agent; used to invalidate incompatible checkpoints. */
8
10
  export declare const CURSOR_HOST_AGENT_OPTION = "opencodeHostAgent";
9
11
  export declare const TOKEN_EXPIRY_THRESHOLD_S = 300;
package/dist/shared.js CHANGED
@@ -4,6 +4,8 @@ export const FALLBACK_CLIENT_VERSION = "cli-2026.07.09-a3815c0";
4
4
  export const CURSOR_PROVIDER_ID = "cursor";
5
5
  /** Private provider option injected by the OpenCode plugin for summary turns. */
6
6
  export const CURSOR_COMPACTION_OPTION = "opencodeCompaction";
7
+ /** OpenCode host rebuilt its history without a provider compaction turn. */
8
+ export const CURSOR_HISTORY_REWRITE_OPTION = "opencodeHistoryRewrite";
7
9
  /** Current OpenCode primary agent; used to invalidate incompatible checkpoints. */
8
10
  export const CURSOR_HOST_AGENT_OPTION = "opencodeHostAgent";
9
11
  export const TOKEN_EXPIRY_THRESHOLD_S = 300;
@@ -7,6 +7,7 @@ import { trace } from "../debug.js";
7
7
  import { CursorLocalCancellationError, CursorProtocolError, CursorTransportError, cursorGrpcError, cursorHttpError, errorCode, CursorProviderError, } from "../errors.js";
8
8
  import { withAbortDeadline } from "../deadline.js";
9
9
  import http2 from "node:http2";
10
+ import { openProxiedTlsSocket, proxyEndpoint, resolveHttpsProxyUrl } from "./https-proxy.js";
10
11
  const API_BASE = `https://${CURSOR_API_HOST}`;
11
12
  const DEFAULT_UNARY_TIMEOUT_MS = 5_000;
12
13
  const MAX_TIMER_MS = 2_147_483_647;
@@ -407,40 +408,65 @@ function validateCachedSession(session, pingTimeoutMs) {
407
408
  }
408
409
  function connectSession(origin) {
409
410
  return new Promise((resolve, reject) => {
410
- const session = http2.connect(origin);
411
411
  let settled = false;
412
- const cleanup = () => {
412
+ let session;
413
+ let tunnelSocket;
414
+ const abort = new AbortController();
415
+ const clearConnectTimer = () => {
413
416
  clearTimeout(timer);
417
+ };
418
+ const detachSessionListeners = () => {
419
+ if (!session)
420
+ return;
414
421
  session.removeListener("error", onError);
415
422
  session.removeListener("close", onClose);
416
423
  session.removeListener("connect", onConnect);
417
424
  };
425
+ const destroyTunnel = () => {
426
+ try {
427
+ tunnelSocket?.destroy();
428
+ }
429
+ catch { /* ignore */ }
430
+ tunnelSocket = undefined;
431
+ };
418
432
  const fail = (error) => {
419
433
  if (settled)
420
434
  return;
421
435
  settled = true;
422
- cleanup();
423
- dropSession(origin, session);
436
+ clearConnectTimer();
424
437
  try {
425
- session.destroy();
438
+ abort.abort(error);
426
439
  }
427
440
  catch { /* ignore */ }
441
+ detachSessionListeners();
442
+ if (session) {
443
+ dropSession(origin, session);
444
+ try {
445
+ session.destroy();
446
+ }
447
+ catch { /* ignore */ }
448
+ }
449
+ destroyTunnel();
428
450
  reject(error);
429
451
  };
430
452
  const onError = (error) => fail(toTransportError(error, "Cursor HTTP/2 connection failed"));
431
453
  const onClose = () => fail(new CursorTransportError(`HTTP/2 connection to ${origin} closed before connecting`));
432
454
  const onConnect = () => {
433
- if (settled)
455
+ if (settled || !session)
434
456
  return;
435
457
  settled = true;
436
- cleanup();
458
+ clearConnectTimer();
459
+ detachSessionListeners();
437
460
  installSessionInvalidation(origin, session);
438
461
  if (session.destroyed || session.closed) {
439
462
  const error = new CursorTransportError(`HTTP/2 connection to ${origin} closed while connecting`);
440
463
  invalidateSession(origin, session);
464
+ destroyTunnel();
441
465
  reject(error);
442
466
  return;
443
467
  }
468
+ // The TLS socket (if any) is owned by the HTTP/2 session from here on.
469
+ tunnelSocket = undefined;
444
470
  _http2SessionCreatedAt.set(session, Date.now());
445
471
  _http2Sessions.set(origin, session);
446
472
  trace(`h2 session connected: origin=${origin}`);
@@ -450,9 +476,51 @@ function connectSession(origin) {
450
476
  fail(new CursorTransportError(`HTTP/2 connect to ${origin} timed out after ${CONNECT_TIMEOUT_MS}ms`));
451
477
  }, CONNECT_TIMEOUT_MS);
452
478
  timer.unref?.();
453
- session.on("error", onError);
454
- session.once("close", onClose);
455
- session.once("connect", onConnect);
479
+ void (async () => {
480
+ try {
481
+ const { hostname, port: originPort } = new URL(origin);
482
+ const targetPort = originPort ? Number(originPort) : 443;
483
+ const proxy = resolveHttpsProxyUrl(hostname, process.env, targetPort);
484
+ if (proxy) {
485
+ const endpoint = proxyEndpoint(proxy);
486
+ trace(`h2 connect via HTTPS proxy: origin=${origin} proxy=${endpoint.host}:${endpoint.port}`);
487
+ const tlsSocket = await openProxiedTlsSocket({
488
+ proxy,
489
+ targetHost: hostname,
490
+ targetPort,
491
+ signal: abort.signal,
492
+ });
493
+ if (settled) {
494
+ try {
495
+ tlsSocket.destroy();
496
+ }
497
+ catch { /* ignore */ }
498
+ return;
499
+ }
500
+ tunnelSocket = tlsSocket;
501
+ session = http2.connect(origin, {
502
+ createConnection: () => tlsSocket,
503
+ });
504
+ }
505
+ else {
506
+ session = http2.connect(origin);
507
+ }
508
+ if (settled) {
509
+ try {
510
+ session.destroy();
511
+ }
512
+ catch { /* ignore */ }
513
+ destroyTunnel();
514
+ return;
515
+ }
516
+ session.on("error", onError);
517
+ session.once("close", onClose);
518
+ session.once("connect", onConnect);
519
+ }
520
+ catch (error) {
521
+ fail(toTransportError(error, "Cursor HTTP/2 connection failed"));
522
+ }
523
+ })();
456
524
  });
457
525
  }
458
526
  export function getSession(baseURL, options = {}) {
@@ -0,0 +1,56 @@
1
+ /**
2
+ * HTTPS proxy helpers for the Cursor Run HTTP/2 transport.
3
+ *
4
+ * Unary RPCs use Bun `fetch()`, which already honors HTTPS_PROXY / NO_PROXY.
5
+ * `node:http2` does not, so Run sessions tunnel through an HTTP CONNECT proxy
6
+ * when those env vars apply. See GitHub issue #26.
7
+ *
8
+ * Important (Bun 1.3.x): do the CONNECT handshake on a native `net.Socket`,
9
+ * then `tls.connect({ socket })` on that same socket. Do not insert a custom
10
+ * Duplex between the proxy socket and TLS — destroying such a session mid-
11
+ * handshake can segfault Bun.
12
+ */
13
+ import net from "node:net";
14
+ import tls from "node:tls";
15
+ export type ProxyEnv = Record<string, string | undefined>;
16
+ /**
17
+ * HTTPS_PROXY / https_proxy for an HTTPS target. Like curl, HTTP_PROXY is not
18
+ * consulted for HTTPS. Only `http://` proxies can carry the CONNECT tunnel;
19
+ * `https://` (TLS to the proxy) and other schemes are ignored so the Run keeps
20
+ * its direct connection rather than failing on every connect.
21
+ */
22
+ export declare function resolveHttpsProxyUrl(targetHost: string, env?: ProxyEnv, targetPort?: number): URL | undefined;
23
+ /**
24
+ * curl/Node-style NO_PROXY matching: `*`, exact host, optional `:port`, and
25
+ * leading-dot / bare-domain suffix forms (`.corp.example` / `corp.example`).
26
+ * A port-qualified entry only bypasses that port.
27
+ */
28
+ export declare function hostMatchesNoProxy(targetHost: string, noProxy: string | undefined, targetPort?: number): boolean;
29
+ /** Dialable proxy address: IPv6 literals without URL brackets, default port 80. */
30
+ export declare function proxyEndpoint(proxy: URL): {
31
+ host: string;
32
+ port: number;
33
+ };
34
+ export type OpenHttpsConnectTunnelOptions = {
35
+ proxy: URL;
36
+ targetHost: string;
37
+ targetPort?: number;
38
+ signal?: AbortSignal;
39
+ /** Injected for tests. */
40
+ connect?: typeof net.connect;
41
+ };
42
+ /**
43
+ * Open a plain TCP connection to the proxy and establish an HTTP CONNECT tunnel
44
+ * to `targetHost:targetPort`. Returns the tunneled socket ready for TLS.
45
+ */
46
+ export declare function openHttpsConnectTunnel(options: OpenHttpsConnectTunnelOptions): Promise<net.Socket>;
47
+ export type OpenProxiedTlsSocketOptions = {
48
+ proxy: URL;
49
+ targetHost: string;
50
+ targetPort?: number;
51
+ signal?: AbortSignal;
52
+ connect?: typeof net.connect;
53
+ tlsConnect?: typeof tls.connect;
54
+ };
55
+ /** CONNECT through the proxy, then complete TLS (ALPN h2) on the native socket. */
56
+ export declare function openProxiedTlsSocket(options: OpenProxiedTlsSocketOptions): Promise<tls.TLSSocket>;