tywrap 0.1.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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. package/src/utils/runtime.ts +616 -0
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Node.js runtime bridge (minimal MVP)
3
+ */
4
+
5
+ import { existsSync } from 'node:fs';
6
+ import { delimiter, isAbsolute, join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ import { decodeValueAsync } from '../utils/codec.js';
10
+ import { getDefaultPythonPath } from '../utils/python.js';
11
+ import { getVenvBinDir, getVenvPythonExe } from '../utils/runtime.js';
12
+ import type { BridgeInfo } from '../types/index.js';
13
+
14
+ import { RuntimeBridge } from './base.js';
15
+ import {
16
+ BridgeDisposedError,
17
+ BridgeExecutionError,
18
+ BridgeProtocolError,
19
+ BridgeTimeoutError,
20
+ } from './errors.js';
21
+ import { TYWRAP_PROTOCOL, TYWRAP_PROTOCOL_VERSION } from './protocol.js';
22
+
23
+ interface RpcRequest {
24
+ id: number;
25
+ protocol: string;
26
+ method: 'call' | 'instantiate' | 'call_method' | 'dispose_instance' | 'meta';
27
+ params: unknown;
28
+ }
29
+
30
+ interface RpcResponse<T = unknown> {
31
+ id: number;
32
+ protocol: string;
33
+ result?: T;
34
+ error?: { type: string; message: string; traceback?: string };
35
+ }
36
+
37
+ export interface NodeBridgeOptions {
38
+ pythonPath?: string;
39
+ scriptPath?: string; // path to python_bridge.py
40
+ virtualEnv?: string;
41
+ cwd?: string;
42
+ timeoutMs?: number;
43
+ /**
44
+ * When true, sets TYWRAP_CODEC_FALLBACK=json for the Python process to prefer JSON encoding
45
+ * for rich types (ndarray/dataframe/series). Default: false for fast-fail on Arrow path issues.
46
+ */
47
+ enableJsonFallback?: boolean;
48
+ /**
49
+ * Optional extra environment variables to pass to the Python subprocess.
50
+ */
51
+ env?: Record<string, string | undefined>;
52
+ }
53
+
54
+ interface ResolvedNodeBridgeOptions {
55
+ pythonPath: string;
56
+ scriptPath: string;
57
+ virtualEnv?: string;
58
+ cwd: string;
59
+ timeoutMs: number;
60
+ enableJsonFallback: boolean;
61
+ env: Record<string, string | undefined>;
62
+ }
63
+
64
+ function resolveDefaultScriptPath(): string {
65
+ try {
66
+ return fileURLToPath(new URL('../../runtime/python_bridge.py', import.meta.url));
67
+ } catch {
68
+ return 'runtime/python_bridge.py';
69
+ }
70
+ }
71
+
72
+ function resolveVirtualEnv(
73
+ virtualEnv: string,
74
+ cwd: string
75
+ ): {
76
+ venvPath: string;
77
+ binDir: string;
78
+ pythonPath: string;
79
+ } {
80
+ const venvPath = resolve(cwd, virtualEnv);
81
+ const binDir = join(venvPath, getVenvBinDir());
82
+ const pythonPath = join(binDir, getVenvPythonExe());
83
+ return { venvPath, binDir, pythonPath };
84
+ }
85
+
86
+ export class NodeBridge extends RuntimeBridge {
87
+ private child?: import('child_process').ChildProcess;
88
+ private nextId = 1;
89
+ private readonly pending = new Map<
90
+ number,
91
+ { resolve: (v: unknown) => void; reject: (e: unknown) => void; timer?: NodeJS.Timeout }
92
+ >();
93
+ private readonly options: ResolvedNodeBridgeOptions;
94
+ private stderrBuffer = '';
95
+ private disposed = false;
96
+ private protocolError = false;
97
+ private initPromise?: Promise<void>;
98
+ private bridgeInfo?: BridgeInfo;
99
+
100
+ constructor(options: NodeBridgeOptions = {}) {
101
+ super();
102
+ const cwd = options.cwd ?? process.cwd();
103
+ const virtualEnv = options.virtualEnv ? resolve(cwd, options.virtualEnv) : undefined;
104
+ const venv = virtualEnv ? resolveVirtualEnv(virtualEnv, cwd) : undefined;
105
+ const scriptPath = options.scriptPath ?? resolveDefaultScriptPath();
106
+ const resolvedScriptPath = isAbsolute(scriptPath) ? scriptPath : resolve(cwd, scriptPath);
107
+ this.options = {
108
+ pythonPath:
109
+ options.pythonPath ??
110
+ venv?.pythonPath ??
111
+ getDefaultPythonPath(),
112
+ scriptPath: resolvedScriptPath,
113
+ virtualEnv,
114
+ cwd,
115
+ timeoutMs: options.timeoutMs ?? 30000,
116
+ enableJsonFallback: options.enableJsonFallback ?? false,
117
+ env: options.env ?? {},
118
+ };
119
+ }
120
+
121
+ async init(): Promise<void> {
122
+ if (this.disposed) {
123
+ throw new BridgeDisposedError('Bridge has been disposed');
124
+ }
125
+ if (this.child) {
126
+ return;
127
+ }
128
+ if (this.initPromise) {
129
+ return this.initPromise;
130
+ }
131
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- script path is user-configured
132
+ if (!existsSync(this.options.scriptPath)) {
133
+ throw new BridgeProtocolError(`Python bridge script not found at ${this.options.scriptPath}`);
134
+ }
135
+ this.initPromise = this.startProcess();
136
+ return this.initPromise;
137
+ }
138
+
139
+ async getBridgeInfo(options: { refresh?: boolean } = {}): Promise<BridgeInfo> {
140
+ await this.init();
141
+ if (!this.bridgeInfo || options.refresh) {
142
+ await this.refreshBridgeInfo();
143
+ }
144
+ if (!this.bridgeInfo) {
145
+ throw new BridgeProtocolError('Bridge info unavailable');
146
+ }
147
+ return this.bridgeInfo;
148
+ }
149
+
150
+ async call<T = unknown>(
151
+ module: string,
152
+ functionName: string,
153
+ args: unknown[],
154
+ kwargs?: Record<string, unknown>
155
+ ): Promise<T> {
156
+ await this.init();
157
+ return this.send<T>({ method: 'call', params: { module, functionName, args, kwargs } });
158
+ }
159
+
160
+ async instantiate<T = unknown>(
161
+ module: string,
162
+ className: string,
163
+ args: unknown[],
164
+ kwargs?: Record<string, unknown>
165
+ ): Promise<T> {
166
+ await this.init();
167
+ return this.send<T>({ method: 'instantiate', params: { module, className, args, kwargs } });
168
+ }
169
+
170
+ async callMethod<T = unknown>(
171
+ handle: string,
172
+ methodName: string,
173
+ args: unknown[],
174
+ kwargs?: Record<string, unknown>
175
+ ): Promise<T> {
176
+ await this.init();
177
+ return this.send<T>({ method: 'call_method', params: { handle, methodName, args, kwargs } });
178
+ }
179
+
180
+ async disposeInstance(handle: string): Promise<void> {
181
+ await this.init();
182
+ await this.send<void>({ method: 'dispose_instance', params: { handle } });
183
+ }
184
+
185
+ async dispose(): Promise<void> {
186
+ this.disposed = true;
187
+ this.initPromise = undefined;
188
+ this.bridgeInfo = undefined;
189
+ if (!this.child) {
190
+ return;
191
+ }
192
+ this.child.kill('SIGTERM');
193
+ this.child = undefined;
194
+ }
195
+
196
+ private async send<T>(payload: Omit<RpcRequest, 'id' | 'protocol'>): Promise<T> {
197
+ if (this.disposed) {
198
+ throw new BridgeDisposedError('Bridge has been disposed');
199
+ }
200
+ const id = this.nextId++;
201
+ const message: RpcRequest = { id, protocol: TYWRAP_PROTOCOL, ...payload } as RpcRequest;
202
+ const text = `${JSON.stringify(message)}\n`;
203
+ let timer: NodeJS.Timeout | undefined;
204
+ const promise = new Promise<T>((resolvePromise, reject) => {
205
+ timer = setTimeout(() => {
206
+ this.pending.delete(id);
207
+ const stderrTail = this.stderrBuffer.trim();
208
+ const msg = stderrTail
209
+ ? `Python call timed out. Recent stderr from Python:\n${stderrTail}`
210
+ : 'Python call timed out';
211
+ reject(new BridgeTimeoutError(msg));
212
+ }, this.options.timeoutMs);
213
+ const resolveWrapped = (v: unknown): void => {
214
+ resolvePromise(v as T);
215
+ };
216
+ this.pending.set(id, { resolve: resolveWrapped, reject, timer });
217
+ });
218
+ try {
219
+ if (!this.child?.stdin) {
220
+ throw new BridgeProtocolError('Python process not available');
221
+ }
222
+ this.child.stdin.write(text);
223
+ } catch (err) {
224
+ this.pending.delete(id);
225
+ if (timer) {
226
+ clearTimeout(timer);
227
+ }
228
+ throw new BridgeProtocolError(`IPC failure: ${(err as Error).message}`);
229
+ }
230
+ return promise;
231
+ }
232
+
233
+ private errorFrom(err: { type: string; message: string; traceback?: string }): Error {
234
+ const e = new BridgeExecutionError(`${err.type}: ${err.message}`);
235
+ e.traceback = err.traceback;
236
+ return e;
237
+ }
238
+
239
+ private handleProtocolError(details: string, line?: string): void {
240
+ if (this.protocolError) {
241
+ return;
242
+ }
243
+ this.protocolError = true;
244
+ const snippet = line ? (line.length > 500 ? `${line.slice(0, 500)}…` : line) : undefined;
245
+ const hint =
246
+ 'Ensure your Python code does not print to stdout and that the bridge outputs only JSON lines.';
247
+ const msg = snippet
248
+ ? `Protocol error from Python bridge. ${details}\n${hint}\nOffending line: ${snippet}`
249
+ : `Protocol error from Python bridge. ${details}\n${hint}`;
250
+ const error = new BridgeProtocolError(msg);
251
+ for (const [, p] of this.pending) {
252
+ p.reject(error);
253
+ }
254
+ this.pending.clear();
255
+ this.child?.kill('SIGTERM');
256
+ this.child = undefined;
257
+ this.initPromise = undefined;
258
+ this.bridgeInfo = undefined;
259
+ }
260
+
261
+ private async startProcess(): Promise<void> {
262
+ try {
263
+ const { spawn } = await import('child_process');
264
+ const allowedPrefixes = ['TYWRAP_'];
265
+ const allowedKeys = new Set(['PATH', 'PYTHONPATH', 'VIRTUAL_ENV', 'PYTHONHOME']);
266
+ const baseEnv = new Map<string, string | undefined>();
267
+ for (const [k, v] of Object.entries(process.env)) {
268
+ if (allowedKeys.has(k) || allowedPrefixes.some(p => k.startsWith(p))) {
269
+ baseEnv.set(k, v);
270
+ }
271
+ }
272
+ const env: NodeJS.ProcessEnv = {
273
+ ...(Object.fromEntries(baseEnv) as NodeJS.ProcessEnv),
274
+ ...this.options.env,
275
+ };
276
+ if (this.options.virtualEnv) {
277
+ const venv = resolveVirtualEnv(this.options.virtualEnv, this.options.cwd);
278
+ env.VIRTUAL_ENV = venv.venvPath;
279
+ const currentPath = env.PATH ?? process.env.PATH ?? '';
280
+ env.PATH = `${venv.binDir}${delimiter}${currentPath}`;
281
+ }
282
+ if (!env.PYTHONUTF8) {
283
+ env.PYTHONUTF8 = '1';
284
+ }
285
+ if (!env.PYTHONIOENCODING) {
286
+ env.PYTHONIOENCODING = 'UTF-8';
287
+ }
288
+ // Respect explicit request for JSON fallback only; otherwise fast-fail by default
289
+ if (this.options.enableJsonFallback && !env.TYWRAP_CODEC_FALLBACK) {
290
+ env.TYWRAP_CODEC_FALLBACK = 'json';
291
+ }
292
+
293
+ let child: ReturnType<typeof spawn>;
294
+ try {
295
+ child = spawn(this.options.pythonPath, [this.options.scriptPath], {
296
+ cwd: this.options.cwd,
297
+ stdio: ['pipe', 'pipe', 'pipe'],
298
+ env,
299
+ });
300
+ } catch (err) {
301
+ throw new BridgeProtocolError(`Failed to start Python process: ${(err as Error).message}`);
302
+ }
303
+
304
+ const startupError = await new Promise<Error | null>(done => {
305
+ child.once('error', e => done(e));
306
+ child.once('spawn', () => done(null));
307
+ });
308
+ if (startupError) {
309
+ throw new BridgeProtocolError(`Failed to start Python process: ${startupError.message}`);
310
+ }
311
+
312
+ this.child = child;
313
+ this.protocolError = false;
314
+
315
+ this.child?.on('error', err => {
316
+ const msg = `Python process error: ${err instanceof Error ? err.message : String(err)}`;
317
+ for (const [, p] of this.pending) {
318
+ p.reject(new BridgeProtocolError(msg));
319
+ }
320
+ this.pending.clear();
321
+ this.child = undefined;
322
+ this.initPromise = undefined;
323
+ this.bridgeInfo = undefined;
324
+ });
325
+
326
+ let buffer = '';
327
+ this.child.stdout?.on('data', (chunk: Buffer): void => {
328
+ buffer += chunk.toString();
329
+ let idx: number;
330
+ while ((idx = buffer.indexOf('\n')) !== -1) {
331
+ const line = buffer.slice(0, idx);
332
+ buffer = buffer.slice(idx + 1);
333
+ if (!line.trim()) {
334
+ continue;
335
+ }
336
+ (async (): Promise<void> => {
337
+ try {
338
+ const msg = JSON.parse(line) as RpcResponse;
339
+ if (msg.protocol !== TYWRAP_PROTOCOL) {
340
+ this.handleProtocolError(
341
+ `Invalid protocol. Expected ${TYWRAP_PROTOCOL} but received ${String(
342
+ msg.protocol
343
+ )}`,
344
+ line
345
+ );
346
+ return;
347
+ }
348
+ if (typeof msg.id !== 'number') {
349
+ this.handleProtocolError('Invalid response id', line);
350
+ return;
351
+ }
352
+ const pending = this.pending.get(msg.id);
353
+ if (!pending) {
354
+ this.handleProtocolError(`Unexpected response id ${msg.id}`, line);
355
+ return;
356
+ }
357
+ this.pending.delete(msg.id);
358
+ if (pending.timer) {
359
+ clearTimeout(pending.timer);
360
+ }
361
+ if (msg.error) {
362
+ pending.reject(this.errorFrom(msg.error));
363
+ } else {
364
+ try {
365
+ const decoded = await decodeValueAsync(msg.result);
366
+ pending.resolve(decoded);
367
+ } catch (err) {
368
+ pending.reject(
369
+ new BridgeProtocolError(
370
+ `Failed to decode Python response: ${
371
+ err instanceof Error ? err.message : String(err)
372
+ }`
373
+ )
374
+ );
375
+ }
376
+ }
377
+ } catch (err) {
378
+ const parseMessage = err instanceof Error ? err.message : String(err);
379
+ this.handleProtocolError(`Invalid JSON: ${parseMessage}`, line);
380
+ }
381
+ })().catch(() => {
382
+ /* ignore */
383
+ });
384
+ }
385
+ });
386
+
387
+ this.child?.stderr?.on('data', (chunk: Buffer) => {
388
+ // Buffer stderr for better error diagnostics on failures/exits
389
+ try {
390
+ this.stderrBuffer += chunk.toString();
391
+ // Truncate to last 8KB to avoid unbounded growth
392
+ const MAX = 8 * 1024;
393
+ if (this.stderrBuffer.length > MAX) {
394
+ this.stderrBuffer = this.stderrBuffer.slice(this.stderrBuffer.length - MAX);
395
+ }
396
+ } catch {
397
+ // ignore
398
+ }
399
+ });
400
+
401
+ this.child?.on('exit', () => {
402
+ for (const [, p] of this.pending) {
403
+ const stderrTail = this.stderrBuffer.trim();
404
+ const msg = stderrTail
405
+ ? `Python process exited. Stderr:\n${stderrTail}`
406
+ : 'Python process exited';
407
+ p.reject(new BridgeProtocolError(msg));
408
+ }
409
+ this.pending.clear();
410
+ this.child = undefined;
411
+ this.initPromise = undefined;
412
+ this.bridgeInfo = undefined;
413
+ });
414
+
415
+ await this.refreshBridgeInfo();
416
+ } catch (err) {
417
+ this.child?.kill('SIGTERM');
418
+ this.child = undefined;
419
+ this.initPromise = undefined;
420
+ throw err;
421
+ }
422
+ }
423
+
424
+ private async refreshBridgeInfo(): Promise<void> {
425
+ const info = await this.send<BridgeInfo>({ method: 'meta', params: {} });
426
+ if (info.protocol !== TYWRAP_PROTOCOL || info.protocolVersion !== TYWRAP_PROTOCOL_VERSION) {
427
+ throw new BridgeProtocolError('Invalid bridge info payload');
428
+ }
429
+ this.bridgeInfo = info;
430
+ }
431
+ }