@qping/plugin-bus 0.1.0 → 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/node.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Node-side plugin SDK: fluent `createPlugin()` over the v3 named-pipe message bus.
3
+ *
4
+ * Method names map to v3 routes:
5
+ * initialize -> plugin.call.initialize
6
+ * search -> plugin.call.search
7
+ * action -> plugin.call.invokeAction
8
+ * handle(name) -> plugin.call.<name>
9
+ * publish -> plugin.event.<subjectId>
10
+ * hostCall -> host.call.<method>
11
+ */
12
+ export type PluginTheme = "light" | "dark";
13
+ export type PluginHostEnv = {
14
+ locale: string;
15
+ fallbackLocale: string;
16
+ theme: PluginTheme;
17
+ };
18
+ export type PluginContext = PluginHostEnv & {
19
+ action: string;
20
+ itemId: string;
21
+ query: string;
22
+ };
23
+ /** Payload of plugin.call.initialize. Host sends locale, theme, and the resolved message bag. */
24
+ export type PluginInitializeParams = PluginHostEnv & {
25
+ messages: Record<string, string>;
26
+ };
27
+ /** Payload of plugin.call.search. */
28
+ export type PluginSearchParams = PluginHostEnv & {
29
+ query: string;
30
+ mode: "global" | "plugin";
31
+ };
32
+ /** Payload of plugin.call.invokeAction. */
33
+ export type PluginActionParams = PluginHostEnv & {
34
+ itemId: string;
35
+ actionId: string;
36
+ query: string;
37
+ };
38
+ type PluginInitializeHandler = (params: PluginInitializeParams) => unknown | Promise<unknown>;
39
+ type PluginSearchHandler = (params: PluginSearchParams) => unknown | Promise<unknown>;
40
+ type PluginActionHandler = (params: PluginActionParams) => unknown | Promise<unknown>;
41
+ type PluginHandler = (payload: any, context: PluginContext) => unknown | Promise<unknown>;
42
+ export declare class Plugin {
43
+ #private;
44
+ initialize(handler: PluginInitializeHandler): this;
45
+ search(handler: PluginSearchHandler): this;
46
+ action(handler: PluginActionHandler): this;
47
+ handle(action: string, handler: PluginHandler): this;
48
+ /** Publishes a plugin.event.<subjectId> event to all webviews in the session. */
49
+ publish(subjectId: string, payload?: unknown): void;
50
+ /** Calls a host.call.<method> capability and awaits the response.
51
+ * `timeoutMs` defaults to the remaining timeout of the inbound plugin.call
52
+ * (from a page `bus.call`) when inside a handler; otherwise 30s.
53
+ */
54
+ hostCall(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
55
+ /** Connects to the host pipe and begins dispatching. Must be called last. */
56
+ start(): Promise<void>;
57
+ /**
58
+ * Builds the v3 route map from the fluent registrations. Exposed for unit testing the mapping
59
+ * without connecting a pipe.
60
+ */
61
+ buildRoutes(): Record<string, (payload: any) => unknown | Promise<unknown>>;
62
+ stop(): Promise<void>;
63
+ }
64
+ export declare function createPlugin(): Plugin;
65
+ export {};
@@ -110,12 +110,77 @@ var init_framing = __esm({
110
110
 
111
111
  // src/bootstrap.ts
112
112
  import readline from "node:readline/promises";
113
+ import { randomBytes as randomBytes2 } from "node:crypto";
113
114
 
114
115
  // src/transport.ts
115
116
  init_framing();
116
117
  import { connect as netConnect } from "node:net";
117
118
 
118
119
  // src/protocol.ts
120
+ var MessageKind = {
121
+ Request: "request",
122
+ Response: "response",
123
+ Event: "event"
124
+ };
125
+ var ErrorCode = {
126
+ ProtocolMismatch: "ProtocolMismatch",
127
+ HandshakeFailed: "HandshakeFailed",
128
+ CapabilityNotDeclared: "CapabilityNotDeclared",
129
+ CapabilityDenied: "CapabilityDenied",
130
+ InvalidPayload: "InvalidPayload",
131
+ MessageTooLarge: "MessageTooLarge",
132
+ RouteNotFound: "RouteNotFound",
133
+ RequestTimeout: "RequestTimeout",
134
+ TooManyRequests: "TooManyRequests",
135
+ TransportDisconnected: "TransportDisconnected",
136
+ PluginUnavailable: "PluginUnavailable",
137
+ InternalError: "InternalError",
138
+ Cancelled: "Cancelled",
139
+ RateLimited: "RateLimited"
140
+ };
141
+ var ProtocolVersion = "3.0";
142
+ var EndpointIds = {
143
+ NodeMain: "node-main",
144
+ Host: "host"
145
+ };
146
+ var Routes = {
147
+ Bus: {
148
+ Handshake: "bus.handshake",
149
+ Ping: "bus.ping",
150
+ Cancel: "bus.cancel",
151
+ Subscribe: "bus.subscribe",
152
+ Unsubscribe: "bus.unsubscribe"
153
+ },
154
+ Prefix: {
155
+ PluginCall: "plugin.call.",
156
+ HostCall: "host.call.",
157
+ PluginEvent: "plugin.event.",
158
+ HostEvent: "host.event.",
159
+ Diagnostics: "diagnostics."
160
+ },
161
+ PluginCall: {
162
+ Initialize: "plugin.call.initialize",
163
+ Search: "plugin.call.search",
164
+ InvokeAction: "plugin.call.invokeAction"
165
+ },
166
+ HostEvent: {
167
+ Initialize: "host.event.initialize",
168
+ Search: "host.event.search",
169
+ Key: "host.event.key",
170
+ LanguageChanged: "host.event.languageChanged",
171
+ ThemeChanged: "host.event.themeChanged",
172
+ InputActionCaptured: "host.event.inputActionCaptured"
173
+ }
174
+ };
175
+ function pluginCallRoute(method) {
176
+ return method.startsWith(Routes.Prefix.PluginCall) ? method : `${Routes.Prefix.PluginCall}${method}`;
177
+ }
178
+ function hostCallRoute(method) {
179
+ return method.startsWith(Routes.Prefix.HostCall) ? method : `${Routes.Prefix.HostCall}${method}`;
180
+ }
181
+ function pluginEventRoute(subjectId) {
182
+ return subjectId.startsWith(Routes.Prefix.PluginEvent) ? subjectId : `${Routes.Prefix.PluginEvent}${subjectId}`;
183
+ }
119
184
  function canonicalStringify(value) {
120
185
  return JSON.stringify(stripNulls(value));
121
186
  }
@@ -141,6 +206,9 @@ var NodeTransport = class {
141
206
  closed = false;
142
207
  onMessage(handler) {
143
208
  this.messageHandlers.add(handler);
209
+ return () => {
210
+ this.messageHandlers.delete(handler);
211
+ };
144
212
  }
145
213
  onDisconnect(handler) {
146
214
  this.disconnectHandlers.add(handler);
@@ -207,14 +275,37 @@ var NodeTransport = class {
207
275
  };
208
276
 
209
277
  // src/router.ts
278
+ import { AsyncLocalStorage } from "node:async_hooks";
210
279
  import { randomBytes } from "node:crypto";
280
+ var requestScope = new AsyncLocalStorage();
281
+ var DefaultHostCallTimeoutMs = 3e4;
282
+ function remainingTimeoutMs() {
283
+ const scope = requestScope.getStore();
284
+ if (!scope || scope.deadlineMs == null) {
285
+ return void 0;
286
+ }
287
+ return scope.deadlineMs - Date.now();
288
+ }
289
+ function resolveHostCallTimeoutMs(explicit) {
290
+ const remaining = remainingTimeoutMs();
291
+ if (explicit != null) {
292
+ return remaining == null ? explicit : Math.min(explicit, remaining);
293
+ }
294
+ return remaining ?? DefaultHostCallTimeoutMs;
295
+ }
296
+ function deadlineFromTimeoutMs(timeoutMs) {
297
+ if (typeof timeoutMs !== "number" || timeoutMs <= 0) {
298
+ return null;
299
+ }
300
+ return Date.now() + timeoutMs;
301
+ }
211
302
  var HandlerRouter = class {
212
303
  handlers = /* @__PURE__ */ new Map();
213
304
  pendingHostCalls = /* @__PURE__ */ new Map();
214
305
  pluginId = "p";
215
306
  entryId = "e";
216
307
  sessionId = "s";
217
- endpointId = "node-main";
308
+ endpointId = EndpointIds.NodeMain;
218
309
  /** Injected transport send fn; tests can override `router.send` directly. */
219
310
  send;
220
311
  constructor(deps) {
@@ -232,43 +323,48 @@ var HandlerRouter = class {
232
323
  }
233
324
  /** Dispatches an inbound request/response. Returns once handled. */
234
325
  async dispatch(env) {
235
- if (env.kind === "response") {
326
+ if (env.kind === MessageKind.Response) {
236
327
  this.handleHostResponse(env);
237
328
  return;
238
329
  }
239
- if (env.kind !== "request") return;
240
- if (env.route === "bus.ping") {
330
+ if (env.kind !== MessageKind.Request) return;
331
+ if (env.route === Routes.Bus.Ping) {
241
332
  this.send(this.responseFor(env, { ok: true }));
242
333
  return;
243
334
  }
244
335
  const handler = this.handlers.get(env.route);
245
336
  if (!handler) {
246
- this.send(this.errorResponseFor(env, "RouteNotFound", `route '${env.route}' has no handler`));
337
+ this.send(this.errorResponseFor(env, ErrorCode.RouteNotFound, `route '${env.route}' has no handler`));
247
338
  return;
248
339
  }
340
+ const deadlineMs = deadlineFromTimeoutMs(env.timeoutMs);
249
341
  try {
250
- const result = await handler(env.payload);
342
+ const result = await requestScope.run({ deadlineMs }, () => handler(env.payload));
251
343
  this.send(this.responseFor(env, result ?? {}));
252
344
  } catch (err) {
253
345
  const message = err instanceof Error ? err.message : String(err);
254
- this.send(this.errorResponseFor(env, "InternalError", message));
346
+ this.send(this.errorResponseFor(env, ErrorCode.InternalError, message));
255
347
  }
256
348
  }
257
349
  /** Calls a host.call.* capability and resolves with the response payload. */
258
- callHost(route, payload, timeoutMs = 3e4) {
350
+ callHost(route, payload, timeoutMs) {
351
+ const effectiveTimeoutMs = resolveHostCallTimeoutMs(timeoutMs);
352
+ if (effectiveTimeoutMs <= 0) {
353
+ return Promise.reject(new Error(`host call ${route} timed out (no time remaining)`));
354
+ }
259
355
  return new Promise((resolve, reject) => {
260
356
  const id = randomBytesHex();
261
357
  const req = {
262
- version: "3.0",
358
+ version: ProtocolVersion,
263
359
  id,
264
360
  traceId: id,
265
361
  sessionId: this.sessionId,
266
362
  pluginId: this.pluginId,
267
363
  entryId: this.entryId,
268
364
  endpointId: this.endpointId,
269
- kind: "request",
365
+ kind: MessageKind.Request,
270
366
  route,
271
- timeoutMs,
367
+ timeoutMs: effectiveTimeoutMs,
272
368
  payload
273
369
  };
274
370
  const pending = { resolve, reject, route };
@@ -276,9 +372,9 @@ var HandlerRouter = class {
276
372
  const timer = setTimeout(() => {
277
373
  if (this.pendingHostCalls.has(id)) {
278
374
  this.pendingHostCalls.delete(id);
279
- reject(new Error(`host call ${route} timed out after ${timeoutMs}ms`));
375
+ reject(new Error(`host call ${route} timed out after ${effectiveTimeoutMs}ms`));
280
376
  }
281
- }, timeoutMs);
377
+ }, effectiveTimeoutMs);
282
378
  const origResolve = pending.resolve;
283
379
  const origReject = pending.reject;
284
380
  pending.resolve = (v) => {
@@ -305,7 +401,7 @@ var HandlerRouter = class {
305
401
  }
306
402
  responseFor(req, payload) {
307
403
  return {
308
- version: "3.0",
404
+ version: ProtocolVersion,
309
405
  id: randomBytesHex(),
310
406
  correlationId: req.id,
311
407
  traceId: req.traceId,
@@ -313,7 +409,7 @@ var HandlerRouter = class {
313
409
  pluginId: req.pluginId,
314
410
  entryId: req.entryId,
315
411
  endpointId: this.endpointId,
316
- kind: "response",
412
+ kind: MessageKind.Response,
317
413
  route: req.route,
318
414
  payload
319
415
  };
@@ -331,12 +427,29 @@ function randomBytesHex() {
331
427
  }
332
428
 
333
429
  // src/bootstrap.ts
430
+ var SUPPORTED_VERSIONS = [ProtocolVersion];
334
431
  async function runPlugin(handlers) {
335
432
  const { pipePath, token } = await readBootstrapLine();
336
433
  const transport = new NodeTransport();
337
434
  await transport.connect(pipePath);
435
+ const identity = await completeHandshake(transport, token);
338
436
  const router = new HandlerRouter({ send: (env) => transport.send(env) });
437
+ router.setIdentity(identity);
438
+ const HOST_LOST_MS = 15e3;
439
+ let lastPingAt = Date.now();
440
+ const watchdog = setInterval(() => {
441
+ if (Date.now() - lastPingAt > HOST_LOST_MS) {
442
+ clearInterval(watchdog);
443
+ process.exit(1);
444
+ }
445
+ }, 1e3);
446
+ watchdog.unref?.();
447
+ transport.onDisconnect(() => {
448
+ clearInterval(watchdog);
449
+ process.exit(1);
450
+ });
339
451
  transport.onMessage((env) => {
452
+ if (env.route === Routes.Bus.Ping) lastPingAt = Date.now();
340
453
  router.dispatch(env);
341
454
  });
342
455
  for (const [route, handler] of Object.entries(handlers)) {
@@ -345,7 +458,10 @@ async function runPlugin(handlers) {
345
458
  return {
346
459
  transport,
347
460
  router,
348
- close: () => transport.close()
461
+ close: async () => {
462
+ clearInterval(watchdog);
463
+ await transport.close();
464
+ }
349
465
  };
350
466
  }
351
467
  async function readBootstrapLine() {
@@ -364,9 +480,55 @@ async function readBootstrapLine() {
364
480
  rl.close();
365
481
  }
366
482
  }
483
+ async function completeHandshake(transport, token, timeoutMs = 1e4) {
484
+ const id = randomBytes2(16).toString("hex");
485
+ const req = {
486
+ version: ProtocolVersion,
487
+ id,
488
+ traceId: id,
489
+ sessionId: "",
490
+ pluginId: "",
491
+ entryId: "",
492
+ endpointId: EndpointIds.NodeMain,
493
+ kind: MessageKind.Request,
494
+ route: Routes.Bus.Handshake,
495
+ timeoutMs,
496
+ payload: {
497
+ version: ProtocolVersion,
498
+ supportedVersions: SUPPORTED_VERSIONS,
499
+ token
500
+ }
501
+ };
502
+ return new Promise((resolve, reject) => {
503
+ const timer = setTimeout(() => {
504
+ unsubscribe();
505
+ reject(new Error(`bus.handshake timed out after ${timeoutMs}ms`));
506
+ }, timeoutMs);
507
+ const unsubscribe = transport.onMessage((env) => {
508
+ if (env.kind !== MessageKind.Response || env.correlationId !== id) return;
509
+ clearTimeout(timer);
510
+ unsubscribe();
511
+ if (env.error) {
512
+ reject(new Error(`${env.error.code}: ${env.error.message}`));
513
+ return;
514
+ }
515
+ const p = env.payload ?? {};
516
+ const pluginId = String(p.pluginId ?? "");
517
+ const entryId = String(p.entryId ?? "");
518
+ const sessionId = String(p.sessionId ?? "");
519
+ const endpointId = String(p.endpointId ?? EndpointIds.NodeMain);
520
+ if (!pluginId || !entryId || !sessionId) {
521
+ reject(new Error("bus.handshake success response missing bound identity"));
522
+ return;
523
+ }
524
+ resolve({ pluginId, entryId, sessionId, endpointId });
525
+ });
526
+ transport.send(req);
527
+ });
528
+ }
367
529
 
368
- // src/server.ts
369
- var NodeTool = class {
530
+ // src/node.ts
531
+ var Plugin = class {
370
532
  #handlers = /* @__PURE__ */ new Map();
371
533
  #searchHandler = null;
372
534
  #actionHandler = null;
@@ -386,35 +548,38 @@ var NodeTool = class {
386
548
  }
387
549
  handle(action, handler) {
388
550
  if (!action || typeof action !== "string") {
389
- throw new Error("tool.handle requires an action name.");
551
+ throw new Error("plugin.handle requires an action name.");
390
552
  }
391
553
  if (typeof handler !== "function") {
392
- throw new Error("tool.handle requires a handler.");
554
+ throw new Error("plugin.handle requires a handler.");
393
555
  }
394
556
  this.#handlers.set(action, handler);
395
557
  return this;
396
558
  }
397
559
  /** Publishes a plugin.event.<subjectId> event to all webviews in the session. */
398
560
  publish(subjectId, payload = {}) {
399
- if (!this.#runtime) throw new Error("tool not started");
400
- const route = subjectId.startsWith("plugin.event.") ? subjectId : `plugin.event.${subjectId}`;
561
+ if (!this.#runtime) throw new Error("plugin not started");
562
+ const route = pluginEventRoute(subjectId);
401
563
  this.#runtime.transport.send({
402
- version: "3.0",
564
+ version: ProtocolVersion,
403
565
  id: crypto.randomUUID().replace(/-/g, "").slice(0, 32),
404
566
  traceId: crypto.randomUUID().replace(/-/g, "").slice(0, 32),
405
567
  sessionId: "",
406
568
  pluginId: "",
407
569
  entryId: "",
408
- endpointId: "node-main",
409
- kind: "event",
570
+ endpointId: EndpointIds.NodeMain,
571
+ kind: MessageKind.Event,
410
572
  route,
411
573
  payload
412
574
  });
413
575
  }
414
- /** Calls a host.call.<method> capability and awaits the response. */
415
- hostCall(method, params = {}) {
416
- if (!this.#runtime) return Promise.reject(new Error("tool not started"));
417
- return this.#runtime.router.callHost(`host.call.${method}`, params);
576
+ /** Calls a host.call.<method> capability and awaits the response.
577
+ * `timeoutMs` defaults to the remaining timeout of the inbound plugin.call
578
+ * (from a page `bus.call`) when inside a handler; otherwise 30s.
579
+ */
580
+ hostCall(method, params = {}, timeoutMs) {
581
+ if (!this.#runtime) return Promise.reject(new Error("plugin not started"));
582
+ return this.#runtime.router.callHost(hostCallRoute(method), params, timeoutMs);
418
583
  }
419
584
  /** Connects to the host pipe and begins dispatching. Must be called last. */
420
585
  async start() {
@@ -428,35 +593,20 @@ var NodeTool = class {
428
593
  buildRoutes() {
429
594
  const routes = {};
430
595
  if (this.#initializeHandler) {
431
- routes["plugin.call.initialize"] = (p) => this.#initializeHandler(p);
596
+ routes[Routes.PluginCall.Initialize] = (p) => this.#initializeHandler(asInitializeParams(p));
432
597
  }
433
598
  if (this.#searchHandler) {
434
- routes["plugin.call.search"] = (p) => this.#searchHandler(p);
599
+ routes[Routes.PluginCall.Search] = (p) => this.#searchHandler(asSearchParams(p));
435
600
  }
436
601
  if (this.#actionHandler) {
437
- routes["plugin.call.invokeAction"] = (p) => {
438
- return this.#actionHandler({ ...p, itemId: p.itemId, query: p.query });
439
- };
602
+ routes[Routes.PluginCall.InvokeAction] = (p) => this.#actionHandler(asActionParams(p));
440
603
  }
441
- routes["plugin.call.detailCall"] = async (p) => {
442
- const action = p?.action ?? "";
443
- const handler = this.#handlers.get(action);
444
- if (!handler) {
445
- throw new Error(`no handler registered for action '${action}'`);
446
- }
447
- const ctx = extractContext(p);
448
- const result = await handler(p?.payload ?? {}, ctx);
449
- return { result: result ?? {} };
450
- };
451
- routes["plugin.call.detailEvent"] = async (p) => {
452
- return { state: p?.payload ?? {} };
453
- };
454
604
  for (const [action, handler] of this.#handlers) {
455
- const route = `plugin.call.${action}`;
605
+ const route = pluginCallRoute(action);
456
606
  if (!routes[route]) {
457
607
  routes[route] = async (p) => {
458
- const ctx = extractContext(p);
459
- return handler(p, ctx);
608
+ const ctx = extractContext(p, action);
609
+ return handler(p ?? {}, ctx);
460
610
  };
461
611
  }
462
612
  }
@@ -466,19 +616,53 @@ var NodeTool = class {
466
616
  if (this.#runtime) await this.#runtime.close();
467
617
  }
468
618
  };
469
- function extractContext(p) {
619
+ function asHostEnv(p) {
620
+ return {
621
+ locale: typeof p?.locale === "string" ? p.locale : "en-US",
622
+ fallbackLocale: typeof p?.fallbackLocale === "string" ? p.fallbackLocale : "en-US",
623
+ theme: asTheme(p?.theme)
624
+ };
625
+ }
626
+ function asInitializeParams(p) {
627
+ const messages = p?.messages;
628
+ return {
629
+ ...asHostEnv(p),
630
+ messages: isStringRecord(messages) ? messages : {}
631
+ };
632
+ }
633
+ function asSearchParams(p) {
634
+ return {
635
+ ...asHostEnv(p),
636
+ query: typeof p?.query === "string" ? p.query : "",
637
+ mode: p?.mode === "plugin" ? "plugin" : "global"
638
+ };
639
+ }
640
+ function asActionParams(p) {
641
+ return {
642
+ ...asHostEnv(p),
643
+ itemId: typeof p?.itemId === "string" ? p.itemId : "",
644
+ actionId: typeof p?.actionId === "string" ? p.actionId : "",
645
+ query: typeof p?.query === "string" ? p.query : ""
646
+ };
647
+ }
648
+ function asTheme(value) {
649
+ return value === "light" ? "light" : "dark";
650
+ }
651
+ function isStringRecord(value) {
652
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
653
+ }
654
+ function extractContext(p, action) {
470
655
  return {
471
- action: p?.action ?? "",
472
- itemId: p?.itemId ?? "",
473
- query: p?.query ?? "",
474
- locale: p?.locale ?? "en-US",
475
- fallbackLocale: p?.fallbackLocale ?? "en-US"
656
+ ...asHostEnv(p),
657
+ action,
658
+ itemId: typeof p?.itemId === "string" ? p.itemId : "",
659
+ query: typeof p?.query === "string" ? p.query : ""
476
660
  };
477
661
  }
478
- function createTool() {
479
- return new NodeTool();
662
+ function createPlugin() {
663
+ return new Plugin();
480
664
  }
481
665
  export {
482
- NodeTool,
483
- createTool
666
+ Plugin,
667
+ createPlugin
484
668
  };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Hand-written TypeScript protocol types for the plugin message bus v3. These MUST stay
3
+ * byte-for-byte aligned with the C# types in MyTools.Protocol (see the canonical fixtures in
4
+ * MyTools.Protocol.Test/Fixtures/*.json). The drift-prevention self-check
5
+ * (fixtures-selfcheck.mjs) encodes/decodes those fixtures through these types.
6
+ *
7
+ * Field names are camelCase on the wire (System.Text.Json camelCase policy on the C# side).
8
+ * Null fields are omitted on the wire (WhenWritingNull).
9
+ *
10
+ * Runtime constants mirror MyTools.Protocol (MessageKindWire, Routes, EndpointIds,
11
+ * ProtocolVersion.CurrentWire). Do not re-hardcode those strings in SDK source.
12
+ */
13
+ export declare const MessageKind: {
14
+ readonly Request: "request";
15
+ readonly Response: "response";
16
+ readonly Event: "event";
17
+ };
18
+ export type MessageKind = (typeof MessageKind)[keyof typeof MessageKind];
19
+ export declare const ErrorCode: {
20
+ readonly ProtocolMismatch: "ProtocolMismatch";
21
+ readonly HandshakeFailed: "HandshakeFailed";
22
+ readonly CapabilityNotDeclared: "CapabilityNotDeclared";
23
+ readonly CapabilityDenied: "CapabilityDenied";
24
+ readonly InvalidPayload: "InvalidPayload";
25
+ readonly MessageTooLarge: "MessageTooLarge";
26
+ readonly RouteNotFound: "RouteNotFound";
27
+ readonly RequestTimeout: "RequestTimeout";
28
+ readonly TooManyRequests: "TooManyRequests";
29
+ readonly TransportDisconnected: "TransportDisconnected";
30
+ readonly PluginUnavailable: "PluginUnavailable";
31
+ readonly InternalError: "InternalError";
32
+ readonly Cancelled: "Cancelled";
33
+ readonly RateLimited: "RateLimited";
34
+ };
35
+ export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
36
+ export declare const ProtocolVersion = "3.0";
37
+ export declare const EndpointIds: {
38
+ readonly NodeMain: "node-main";
39
+ readonly Host: "host";
40
+ };
41
+ export declare const Routes: {
42
+ readonly Bus: {
43
+ readonly Handshake: "bus.handshake";
44
+ readonly Ping: "bus.ping";
45
+ readonly Cancel: "bus.cancel";
46
+ readonly Subscribe: "bus.subscribe";
47
+ readonly Unsubscribe: "bus.unsubscribe";
48
+ };
49
+ readonly Prefix: {
50
+ readonly PluginCall: "plugin.call.";
51
+ readonly HostCall: "host.call.";
52
+ readonly PluginEvent: "plugin.event.";
53
+ readonly HostEvent: "host.event.";
54
+ readonly Diagnostics: "diagnostics.";
55
+ };
56
+ readonly PluginCall: {
57
+ readonly Initialize: "plugin.call.initialize";
58
+ readonly Search: "plugin.call.search";
59
+ readonly InvokeAction: "plugin.call.invokeAction";
60
+ };
61
+ readonly HostEvent: {
62
+ readonly Initialize: "host.event.initialize";
63
+ readonly Search: "host.event.search";
64
+ readonly Key: "host.event.key";
65
+ readonly LanguageChanged: "host.event.languageChanged";
66
+ readonly ThemeChanged: "host.event.themeChanged";
67
+ readonly InputActionCaptured: "host.event.inputActionCaptured";
68
+ };
69
+ };
70
+ export declare function pluginCallRoute(method: string): string;
71
+ export declare function hostCallRoute(method: string): string;
72
+ export declare function pluginEventRoute(subjectId: string): string;
73
+ export interface BusError {
74
+ code: ErrorCode;
75
+ message: string;
76
+ retryable: boolean;
77
+ details?: unknown;
78
+ }
79
+ /**
80
+ * The frozen Phase-1 envelope. All fields except correlationId/timeoutMs/error/payload are
81
+ * required; the optional ones are omitted on the wire when null.
82
+ */
83
+ export interface Envelope {
84
+ version: string;
85
+ id: string;
86
+ correlationId?: string | null;
87
+ traceId: string;
88
+ sessionId: string;
89
+ pluginId: string;
90
+ entryId: string;
91
+ endpointId: string;
92
+ kind: MessageKind;
93
+ route: string;
94
+ timeoutMs?: number | null;
95
+ payload?: unknown;
96
+ error?: BusError | null;
97
+ }
98
+ /** Omit null/undefined-valued keys to match the C# WhenWritingNull behavior. */
99
+ export declare function canonicalStringify(value: unknown): string;
100
+ /** Parse + re-canonicalize, returning the canonical JSON string (stable key order via JSON.stringify). */
101
+ export declare function canonicalize(json: string): string;