cli-wechat-bridge 1.1.2 → 1.1.4

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 (46) hide show
  1. package/README.md +129 -150
  2. package/bin/_run-entry.mjs +12 -0
  3. package/bin/wechat-claude-start.mjs +7 -2
  4. package/bin/wechat-claude.mjs +1 -1
  5. package/bin/wechat-codex-start.mjs +7 -2
  6. package/bin/wechat-codex.mjs +1 -1
  7. package/bin/wechat-opencode-start.mjs +7 -2
  8. package/bin/wechat-opencode.mjs +1 -1
  9. package/bin/wechat-pi-start.mjs +10 -0
  10. package/bin/wechat-pi.mjs +5 -0
  11. package/dist/bridge/bridge-adapter-common.js +2 -0
  12. package/dist/bridge/bridge-adapters.claude.js +21 -1
  13. package/dist/bridge/bridge-adapters.codex.js +85 -6
  14. package/dist/bridge/bridge-adapters.core.js +112 -6
  15. package/dist/bridge/bridge-adapters.js +5 -3
  16. package/dist/bridge/bridge-adapters.opencode.js +390 -52
  17. package/dist/bridge/bridge-adapters.pi.js +707 -0
  18. package/dist/bridge/bridge-adapters.shared.js +22 -102
  19. package/dist/bridge/bridge-final-reply.js +17 -2
  20. package/dist/bridge/bridge-process-reaper.js +14 -4
  21. package/dist/bridge/bridge-state.js +42 -6
  22. package/dist/bridge/bridge-utils.js +94 -237
  23. package/dist/bridge/wechat-bridge.js +77 -131
  24. package/dist/bridge/wechat-forwarding.js +58 -0
  25. package/dist/companion/codex-remote-client.js +20 -6
  26. package/dist/companion/local-companion-link.js +13 -3
  27. package/dist/companion/local-companion-start.js +64 -16
  28. package/dist/companion/local-companion.js +8 -5
  29. package/dist/companion/pi-tui-bridge-extension.js +256 -0
  30. package/dist/daemon/daemon-link.js +25 -3
  31. package/dist/daemon/wechat-daemon.js +264 -77
  32. package/dist/i18n/index.js +0 -3
  33. package/dist/i18n/messages-en.js +4 -5
  34. package/dist/i18n/messages-zh.js +4 -5
  35. package/dist/utils/atomic-file.js +15 -0
  36. package/dist/utils/doctor.js +27 -11
  37. package/dist/wechat/channel-config.js +0 -2
  38. package/dist/wechat/setup.js +5 -9
  39. package/dist/wechat/wechat-transport.js +14 -8
  40. package/package.json +113 -102
  41. package/bin/wechat-bridge-claude.mjs +0 -5
  42. package/bin/wechat-bridge-codex.mjs +0 -5
  43. package/bin/wechat-bridge-opencode.mjs +0 -5
  44. package/bin/wechat-bridge-shell.mjs +0 -5
  45. package/bin/wechat-bridge.mjs +0 -5
  46. package/dist/bridge/bridge-adapters.shell.js +0 -363
@@ -10,6 +10,7 @@ import { ensureWorkspaceChannelDir } from "../wechat/channel-config.js";
10
10
  import { CODEX_REMOTE_AUTH_TOKEN_ENV, LOCAL_CLIENT_PROTOCOL_VERSION, } from "../runtime/runtime-types.js";
11
11
  const { CODEX_APP_SERVER_HOST, CODEX_APP_SERVER_READY_TIMEOUT_MS, CODEX_FINAL_REPLY_SETTLE_DELAY_MS, CODEX_RECENT_SESSION_KEY_LIMIT, CODEX_RPC_CONNECT_RETRY_MS, CODEX_RPC_RECONNECT_TIMEOUT_MS, CODEX_SESSION_FALLBACK_SCAN_INTERVAL_MS, CODEX_SESSION_LOCAL_MIRROR_FALLBACK_WINDOW_MS, CODEX_SESSION_POLL_INTERVAL_MS, CODEX_STARTUP_WARMUP_MS, CODEX_THREAD_SIGNAL_TTL_MS, INTERRUPT_SETTLE_DELAY_MS, appendBoundedLog, buildCodexApprovalRequest, buildCodexCliArgs, buildCodexDynamicToolCallFailureResponse, buildCodexMcpServerElicitationDeclineResponse, buildCodexPermissionsRequestApprovalResponse, buildCodexUserInputRequest, coerceWebSocketMessageData, delay, describeUnknownError, extractCodexFinalTextFromItem, extractCodexThreadFollowIdFromStatusChanged, extractCodexThreadStartedThreadId, extractCodexUserMessageText, findCodexSessionFile, findRecentCodexSessionFileForCwd, getCodexRpcRequestId, getCodexApprovalAutoResponse, getCodexWechatOutboundAttachmentDenyMessage, getNotificationThreadId, getNotificationTurnId, isRecord, isRecentIsoTimestamp, listCodexResumeSessions, normalizeComparablePath, normalizeCodexRpcError, reserveLocalPort, resolveSpawnTarget, shouldAutoCompleteCodexWechatTurnAfterFinalReply, shouldIgnoreCodexSessionReplayEntry, shouldRecoverCodexStaleBusyState, waitForTcpPort, } = shared;
12
12
  const CODEX_LOCAL_THREAD_ANNOUNCE_SETTLE_MS = 150;
13
+ const CODEX_NON_BLOCKING_USER_INPUT_TIMEOUT_MS = 120_000;
13
14
  export class CodexPtyAdapter extends AbstractPtyAdapter {
14
15
  runtimeKind = "codex_runtime_host";
15
16
  appServer = null;
@@ -38,6 +39,8 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
38
39
  pendingThreadFollowId = null;
39
40
  pendingApprovalRequests = [];
40
41
  pendingUserInputRequest = null;
42
+ pendingUserInputTimer = null;
43
+ userInputAutoResolutionDelayMs = CODEX_NON_BLOCKING_USER_INPUT_TIMEOUT_MS;
41
44
  queuedTurnNotifications = [];
42
45
  queuedTurnServerRequests = [];
43
46
  mirroredUserInputTurnIds = new Set();
@@ -526,6 +529,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
526
529
  this.sessionPartialLine = "";
527
530
  }
528
531
  if (stat.size === this.sessionReadOffset) {
532
+ this.flushCompleteSessionPartialLine();
529
533
  return;
530
534
  }
531
535
  const fd = fs.openSync(this.sessionFilePath, "r");
@@ -551,6 +555,22 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
551
555
  for (const line of lines) {
552
556
  this.handleSessionLogLine(line);
553
557
  }
558
+ this.flushCompleteSessionPartialLine();
559
+ }
560
+ flushCompleteSessionPartialLine() {
561
+ const line = this.sessionPartialLine.trim();
562
+ if (!line) {
563
+ this.sessionPartialLine = "";
564
+ return;
565
+ }
566
+ try {
567
+ JSON.parse(line);
568
+ }
569
+ catch {
570
+ return;
571
+ }
572
+ this.sessionPartialLine = "";
573
+ this.handleSessionLogLine(line);
554
574
  }
555
575
  seedSessionReplayCutoff(startedAtMs) {
556
576
  if (this.sessionIgnoreBeforeMs !== null ||
@@ -1166,6 +1186,14 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1166
1186
  Date.now() < reconnectDeadline) {
1167
1187
  try {
1168
1188
  await this.connectRpcClient();
1189
+ // The new connection starts with an empty subscription set.
1190
+ // Resubscribe the shared thread so turn notifications and approval
1191
+ // requests keep flowing: without this, a mid-turn reconnect
1192
+ // silently stops delivery, and approvals answered over the new
1193
+ // connection (with the old request id) are ignored by the server.
1194
+ if (this.state.sharedThreadId) {
1195
+ await this.tryEnsureSharedThreadSubscribed(this.state.sharedThreadId);
1196
+ }
1169
1197
  return true;
1170
1198
  }
1171
1199
  catch (error) {
@@ -1211,6 +1239,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1211
1239
  },
1212
1240
  capabilities: {
1213
1241
  experimentalApi: true,
1242
+ requestAttestation: false,
1214
1243
  },
1215
1244
  });
1216
1245
  }
@@ -1243,8 +1272,6 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1243
1272
  approvalsReviewer: "user",
1244
1273
  sandbox: "workspace-write",
1245
1274
  serviceName: "wechat-bridge",
1246
- experimentalRawEvents: false,
1247
- persistExtendedHistory: true,
1248
1275
  });
1249
1276
  const threadId = this.extractThreadIdFromResponse(response);
1250
1277
  if (!threadId) {
@@ -1375,7 +1402,20 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1375
1402
  this.state.status !== "awaiting_input") {
1376
1403
  return;
1377
1404
  }
1378
- this.resetTurnTracking({ preserveThread: true });
1405
+ // Remember the interrupted turn as completed before resetting: the
1406
+ // server may still deliver a late turn/completed for it, and without
1407
+ // this entry it would be tracked as a brand-new local turn — emitting a
1408
+ // duplicate task_complete (or a ghost reply) for work we already
1409
+ // reported as interrupted.
1410
+ const interruptedTurnId = this.activeTurn?.turnId;
1411
+ this.resetTurnTracking({
1412
+ preserveThread: true,
1413
+ preserveCompletedTurns: true,
1414
+ });
1415
+ if (interruptedTurnId) {
1416
+ this.rememberCompletedTurn(interruptedTurnId);
1417
+ this.bridgeOwnedTurnIds.add(interruptedTurnId);
1418
+ }
1379
1419
  this.setStatus("idle", "Codex task interrupted.");
1380
1420
  this.emit({
1381
1421
  type: "task_complete",
@@ -1444,9 +1484,11 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1444
1484
  this.turnErrorById.clear();
1445
1485
  this.turnLastActivityAtMs.clear();
1446
1486
  this.mirroredUserInputTurnIds.clear();
1447
- this.bridgeOwnedTurnIds.clear();
1448
- this.completedTurnIds.clear();
1449
- this.completedTurnOrder = [];
1487
+ if (!options.preserveCompletedTurns) {
1488
+ this.bridgeOwnedTurnIds.clear();
1489
+ this.completedTurnIds.clear();
1490
+ this.completedTurnOrder = [];
1491
+ }
1450
1492
  this.pendingInjectedInputs = [];
1451
1493
  this.recentBridgeThreadSignalAtById.clear();
1452
1494
  this.sessionFinalText = null;
@@ -1640,6 +1682,10 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1640
1682
  this.state.pendingApprovalOrigin = undefined;
1641
1683
  }
1642
1684
  clearPendingUserInputState() {
1685
+ if (this.pendingUserInputTimer) {
1686
+ clearTimeout(this.pendingUserInputTimer);
1687
+ this.pendingUserInputTimer = null;
1688
+ }
1643
1689
  this.pendingUserInputRequest = null;
1644
1690
  this.state.pendingUserInput = null;
1645
1691
  this.state.pendingUserInputOrigin = undefined;
@@ -1816,6 +1862,9 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1816
1862
  if (!threadId || !turnId) {
1817
1863
  return null;
1818
1864
  }
1865
+ if (this.hasCompletedTurn(turnId)) {
1866
+ return null;
1867
+ }
1819
1868
  if (this.bridgeOwnedTurnIds.has(turnId)) {
1820
1869
  return {
1821
1870
  threadId,
@@ -1926,6 +1975,15 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1926
1975
  }
1927
1976
  }
1928
1977
  handleRpcServerRequest(requestId, method, params) {
1978
+ if (method === "currentTime/read") {
1979
+ this.sendRpcMessage({
1980
+ id: requestId,
1981
+ result: {
1982
+ currentTimeAt: Math.floor(Date.now() / 1_000),
1983
+ },
1984
+ });
1985
+ return;
1986
+ }
1929
1987
  if (method === "mcpServer/elicitation/request") {
1930
1988
  this.sendRpcMessage({
1931
1989
  id: requestId,
@@ -2076,6 +2134,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2076
2134
  threadId: trackedTurn.threadId,
2077
2135
  turnId: trackedTurn.turnId,
2078
2136
  origin: trackedTurn.origin,
2137
+ isBlocking: params.isBlocking !== false,
2079
2138
  };
2080
2139
  this.state.pendingUserInput = request;
2081
2140
  this.state.pendingUserInputOrigin = trackedTurn.origin;
@@ -2086,6 +2145,26 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2086
2145
  request,
2087
2146
  timestamp: nowIso(),
2088
2147
  });
2148
+ if (this.pendingUserInputRequest.isBlocking) {
2149
+ return;
2150
+ }
2151
+ const pendingRequest = this.pendingUserInputRequest;
2152
+ this.pendingUserInputTimer = setTimeout(() => {
2153
+ if (this.pendingUserInputRequest !== pendingRequest) {
2154
+ return;
2155
+ }
2156
+ this.sendRpcMessage({
2157
+ id: pendingRequest.requestId,
2158
+ result: {
2159
+ answers: {},
2160
+ },
2161
+ });
2162
+ this.clearPendingUserInputState();
2163
+ if (this.state.status === "awaiting_input") {
2164
+ this.setStatus("busy", "Codex non-blocking user input timed out.");
2165
+ }
2166
+ }, this.userInputAutoResolutionDelayMs);
2167
+ this.pendingUserInputTimer.unref?.();
2089
2168
  }
2090
2169
  handleThreadStatusChanged(params) {
2091
2170
  const threadId = extractCodexThreadFollowIdFromStatusChanged(params);
@@ -1,4 +1,5 @@
1
1
  import net from "node:net";
2
+ import path from "node:path";
2
3
  import { spawn as spawnPty } from "node-pty";
3
4
  import { t } from "../i18n/index.js";
4
5
  import { attachLocalCompanionMessageListener, buildLocalCompanionToken, clearLocalCompanionEndpoint, clearLocalCompanionOccupancy, sendLocalCompanionMessage, updateLocalCompanionOccupancy, writeLocalCompanionEndpoint, } from "../companion/local-companion-link.js";
@@ -6,12 +7,46 @@ import { detectCliApproval, normalizeOutput, nowIso, truncatePreview, } from "./
6
7
  import * as shared from "./bridge-adapters.shared.js";
7
8
  import { LOCAL_CLIENT_PROTOCOL_VERSION } from "../runtime/runtime-types.js";
8
9
  const { CODEX_APP_SERVER_HOST, INTERRUPT_SETTLE_DELAY_MS, buildCliEnvironment, buildPtySpawnOptions, buildSpawnDiagnostic, getLocalCompanionCommandName, getSharedSessionIdFromAdapterState, LOCAL_COMPANION_RECONNECT_GRACE_MS, resolveSpawnTarget, spawnFallbackProcess, } = shared;
10
+ /**
11
+ * On Windows, node-pty (ConPTY) uses CreateProcess which can execute .cmd/.bat
12
+ * files directly — no need for the cmd.exe wrapper that resolveSpawnTarget adds
13
+ * for child_process.spawn compatibility. The wrapper causes double-quoting when
14
+ * the script path contains spaces: node-pty quotes the already-quoted path,
15
+ * making cmd.exe treat the literal quotes as part of the filename.
16
+ */
17
+ function unwrapCmdExeForPty(rawTarget) {
18
+ if (process.platform !== "win32") {
19
+ return rawTarget;
20
+ }
21
+ const fileName = path.basename(rawTarget.file).toLowerCase();
22
+ if (fileName !== "cmd.exe") {
23
+ return rawTarget;
24
+ }
25
+ const args = rawTarget.args;
26
+ if (args.length < 4 || args[0] !== "/d" || args[1] !== "/s" || args[2] !== "/c") {
27
+ return rawTarget;
28
+ }
29
+ // Strip surrounding double-quotes from the /c argument to recover the
30
+ // actual script path. quoteForCmd wraps paths containing spaces in
31
+ // double-quotes, so we reverse that here.
32
+ const commandLine = args[3] ?? "";
33
+ const actualScript = commandLine.replace(/^"(.*)"$/s, "$1");
34
+ // Remaining args beyond the /c handler (args[4..]) are extra arguments
35
+ // that were passed through wrapWithCmdExe; forward them as the new target's
36
+ // args so they stay prepended before buildSpawnArgs().
37
+ return {
38
+ file: actualScript,
39
+ args: args.slice(4),
40
+ };
41
+ }
9
42
  export class LocalCompanionProxyAdapter {
10
43
  options;
11
44
  state;
12
45
  eventSink = () => undefined;
13
46
  server = null;
14
47
  socket = null;
48
+ // Pending senders waiting for the companion's first state frame.
49
+ readyWaiters = new Set();
15
50
  detachMessageListener = null;
16
51
  requestCounter = 0;
17
52
  endpoint = null;
@@ -35,7 +70,7 @@ export class LocalCompanionProxyAdapter {
35
70
  sharedThreadId: options.kind === "codex" || options.kind === "opencode"
36
71
  ? initialSharedSessionId
37
72
  : undefined,
38
- activeRuntimeSessionId: options.kind === "claude" || options.kind === "opencode"
73
+ activeRuntimeSessionId: options.kind === "claude" || options.kind === "opencode" || options.kind === "pi"
39
74
  ? initialSharedSessionId
40
75
  : undefined,
41
76
  resumeConversationId: shouldRestoreInitialSession && options.kind === "claude"
@@ -66,11 +101,22 @@ export class LocalCompanionProxyAdapter {
66
101
  });
67
102
  this.server = server;
68
103
  server.on("error", (error) => {
104
+ // Roll back the half-initialized server: start() early-returns while
105
+ // this.server is set, so a leftover handle would make every retry
106
+ // silently "succeed" without ever listening.
107
+ if (this.server === server) {
108
+ this.server = null;
109
+ }
110
+ server.close();
69
111
  reject(error);
70
112
  });
71
113
  server.listen(0, CODEX_APP_SERVER_HOST, () => {
72
114
  const address = server.address();
73
115
  if (!address || typeof address === "string") {
116
+ if (this.server === server) {
117
+ this.server = null;
118
+ }
119
+ server.close();
74
120
  reject(new Error(`Failed to allocate a local ${this.options.kind} companion port.`));
75
121
  return;
76
122
  }
@@ -141,8 +187,17 @@ export class LocalCompanionProxyAdapter {
141
187
  const ok = await this.resolveApproval(action);
142
188
  return ok ? 1 : 0;
143
189
  }
144
- async submitUserInput(_answers) {
145
- return false;
190
+ async submitUserInput(answers) {
191
+ try {
192
+ const result = await this.sendRequest({
193
+ command: "submit_user_input",
194
+ answers,
195
+ });
196
+ return result === true;
197
+ }
198
+ catch {
199
+ return false;
200
+ }
146
201
  }
147
202
  async dispose() {
148
203
  this.shuttingDown = true;
@@ -277,6 +332,9 @@ export class LocalCompanionProxyAdapter {
277
332
  this.state.activeTurnOrigin = undefined;
278
333
  this.state.pendingApprovalOrigin = undefined;
279
334
  Object.assign(this.state, message.state);
335
+ if (this.state.pid) {
336
+ this.settleReadyWaiters();
337
+ }
280
338
  this.eventSink({
281
339
  type: "status",
282
340
  status: this.state.status,
@@ -299,6 +357,10 @@ export class LocalCompanionProxyAdapter {
299
357
  }
300
358
  }
301
359
  detachPanelSocket() {
360
+ this.settleReadyWaiters(new Error(formatCompanionNotConnectedMessage({
361
+ kind: this.options.kind,
362
+ launchMode: this.options.companionLaunchMode,
363
+ })));
302
364
  this.rejectPendingRequests("Companion socket disconnected unexpectedly.");
303
365
  this.detachMessageListener?.();
304
366
  this.detachMessageListener = null;
@@ -387,8 +449,44 @@ export class LocalCompanionProxyAdapter {
387
449
  }
388
450
  this.pendingRequests.clear();
389
451
  }
452
+ /**
453
+ * Wait for the companion's first state frame (which reports the worker
454
+ * pid). The companion sends it only after its adapter finished starting —
455
+ * for opencode that spans spawning `opencode serve`, connecting, and the
456
+ * initial session scan — so a WeChat message arriving inside that window
457
+ * must wait instead of failing with "connected but not ready yet".
458
+ */
459
+ waitForCompanionReady() {
460
+ if (this.state.pid) {
461
+ return Promise.resolve();
462
+ }
463
+ return new Promise((resolve, reject) => {
464
+ const entry = {
465
+ timer: setTimeout(() => {
466
+ this.readyWaiters.delete(entry);
467
+ reject(new Error(`${this.options.kind} companion is connected but not ready yet. Wait for it to finish starting.`));
468
+ }, 30_000),
469
+ settle: (error) => {
470
+ clearTimeout(entry.timer);
471
+ this.readyWaiters.delete(entry);
472
+ if (error) {
473
+ reject(error);
474
+ }
475
+ else {
476
+ resolve();
477
+ }
478
+ },
479
+ };
480
+ this.readyWaiters.add(entry);
481
+ });
482
+ }
483
+ settleReadyWaiters(error) {
484
+ for (const entry of [...this.readyWaiters]) {
485
+ entry.settle(error);
486
+ }
487
+ }
390
488
  async sendRequest(payload) {
391
- const socket = this.socket;
489
+ let socket = this.socket;
392
490
  if (!socket) {
393
491
  throw new Error(formatCompanionNotConnectedMessage({
394
492
  kind: this.options.kind,
@@ -396,7 +494,14 @@ export class LocalCompanionProxyAdapter {
396
494
  }));
397
495
  }
398
496
  if (!this.state.pid && payload.command !== "dispose") {
399
- throw new Error(`${this.options.kind} companion is connected but not ready yet. Wait for it to finish starting.`);
497
+ await this.waitForCompanionReady();
498
+ socket = this.socket;
499
+ if (!socket) {
500
+ throw new Error(formatCompanionNotConnectedMessage({
501
+ kind: this.options.kind,
502
+ launchMode: this.options.companionLaunchMode,
503
+ }));
504
+ }
400
505
  }
401
506
  const id = `${++this.requestCounter}`;
402
507
  const response = new Promise((resolve, reject) => {
@@ -513,7 +618,8 @@ export class AbstractPtyAdapter {
513
618
  this.setStatus("starting", `Starting ${this.options.kind} adapter...`);
514
619
  let spawnTarget = null;
515
620
  try {
516
- spawnTarget = resolveSpawnTarget(this.options.command, this.options.kind);
621
+ const rawTarget = resolveSpawnTarget(this.options.command, this.options.kind);
622
+ spawnTarget = unwrapCmdExeForPty(rawTarget);
517
623
  const env = this.buildEnv();
518
624
  const spawnArgs = [...spawnTarget.args, ...this.buildSpawnArgs()];
519
625
  let ptyProcess;
@@ -2,7 +2,7 @@ import { ClaudeCompanionAdapter } from "./bridge-adapters.claude.js";
2
2
  import { LocalCompanionProxyAdapter } from "./bridge-adapters.core.js";
3
3
  import { CodexPtyAdapter } from "./bridge-adapters.codex.js";
4
4
  import { OpenCodeServerAdapter } from "./bridge-adapters.opencode.js";
5
- import { ShellAdapter } from "./bridge-adapters.shell.js";
5
+ import { PiTuiAdapter } from "./bridge-adapters.pi.js";
6
6
  export * from "./bridge-adapters.shared.js";
7
7
  export function createBridgeAdapter(options) {
8
8
  switch (options.kind) {
@@ -18,8 +18,10 @@ export function createBridgeAdapter(options) {
18
18
  return options.renderMode === "companion"
19
19
  ? new OpenCodeServerAdapter(options)
20
20
  : new LocalCompanionProxyAdapter(options);
21
- case "shell":
22
- return new ShellAdapter(options);
21
+ case "pi":
22
+ return options.renderMode === "companion"
23
+ ? new PiTuiAdapter(options)
24
+ : new LocalCompanionProxyAdapter(options);
23
25
  default:
24
26
  throw new Error(`Unsupported adapter: ${options.kind}`);
25
27
  }