@nimblebrain/synapse 0.18.0 → 0.20.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 (69) hide show
  1. package/README.md +32 -37
  2. package/dist/check-JEM5PN64.cjs +435 -0
  3. package/dist/check-JEM5PN64.cjs.map +1 -0
  4. package/dist/check-YDZUQT6A.js +429 -0
  5. package/dist/check-YDZUQT6A.js.map +1 -0
  6. package/dist/{chunk-VDQZQDU3.cjs → chunk-BNF352SA.cjs} +56 -145
  7. package/dist/chunk-BNF352SA.cjs.map +1 -0
  8. package/dist/{chunk-HKRTDGXN.js → chunk-DJS2FKBN.js} +21 -89
  9. package/dist/chunk-DJS2FKBN.js.map +1 -0
  10. package/dist/chunk-H3INUKA3.js +32 -0
  11. package/dist/chunk-H3INUKA3.js.map +1 -0
  12. package/dist/{chunk-FB7GPBEM.cjs → chunk-HQLSVUOR.cjs} +193 -314
  13. package/dist/chunk-HQLSVUOR.cjs.map +1 -0
  14. package/dist/chunk-ISV6HFQE.cjs +40 -0
  15. package/dist/chunk-ISV6HFQE.cjs.map +1 -0
  16. package/dist/{chunk-3YSXPBEQ.js → chunk-TR2YNWZ5.js} +190 -313
  17. package/dist/chunk-TR2YNWZ5.js.map +1 -0
  18. package/dist/{chunk-ZLASWV4N.js → chunk-UKP4W6DI.js} +56 -145
  19. package/dist/chunk-UKP4W6DI.js.map +1 -0
  20. package/dist/{chunk-LPWEQCZV.cjs → chunk-YSL7KJNU.cjs} +21 -92
  21. package/dist/chunk-YSL7KJNU.cjs.map +1 -0
  22. package/dist/codegen/cli.cjs +38 -1
  23. package/dist/codegen/cli.cjs.map +1 -1
  24. package/dist/codegen/cli.js +38 -1
  25. package/dist/codegen/cli.js.map +1 -1
  26. package/dist/codegen/index.d.cts +1 -1
  27. package/dist/codegen/index.d.ts +1 -1
  28. package/dist/connect.iife.global.js +66 -43
  29. package/dist/{detect-BHYg26_d.d.cts → detect-DVGaL2bH.d.cts} +39 -33
  30. package/dist/{detect-Bf8Q_0dK.d.ts → detect-DVGaL2bH.d.ts} +39 -33
  31. package/dist/host/index.cjs +17 -17
  32. package/dist/host/index.d.cts +11 -27
  33. package/dist/host/index.d.ts +11 -27
  34. package/dist/host/index.js +2 -2
  35. package/dist/index.cjs +52 -17
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +104 -31
  38. package/dist/index.d.ts +104 -31
  39. package/dist/index.js +34 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/react/index.cjs +11 -10
  42. package/dist/react/index.cjs.map +1 -1
  43. package/dist/react/index.d.cts +49 -12
  44. package/dist/react/index.d.ts +49 -12
  45. package/dist/react/index.js +6 -5
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/{server-QIYIJHD5.cjs → server-BR5MNFOB.cjs} +40 -29
  48. package/dist/server-BR5MNFOB.cjs.map +1 -0
  49. package/dist/{server-5N76YCWC.js → server-OOEEWAEG.js} +40 -30
  50. package/dist/server-OOEEWAEG.js.map +1 -0
  51. package/dist/synapse-runtime.iife.global.js +66 -43
  52. package/dist/synapse-ui.iife.global.js +4 -4
  53. package/dist/{types-uEO4VFJ2.d.cts → types-BxPfGHKO.d.cts} +48 -125
  54. package/dist/{types-uEO4VFJ2.d.ts → types-BxPfGHKO.d.ts} +48 -125
  55. package/dist/vite/index.cjs +77 -33
  56. package/dist/vite/index.cjs.map +1 -1
  57. package/dist/vite/index.d.cts +5 -1
  58. package/dist/vite/index.d.ts +5 -1
  59. package/dist/vite/index.js +77 -33
  60. package/dist/vite/index.js.map +1 -1
  61. package/package.json +12 -6
  62. package/dist/chunk-3YSXPBEQ.js.map +0 -1
  63. package/dist/chunk-FB7GPBEM.cjs.map +0 -1
  64. package/dist/chunk-HKRTDGXN.js.map +0 -1
  65. package/dist/chunk-LPWEQCZV.cjs.map +0 -1
  66. package/dist/chunk-VDQZQDU3.cjs.map +0 -1
  67. package/dist/chunk-ZLASWV4N.js.map +0 -1
  68. package/dist/server-5N76YCWC.js.map +0 -1
  69. package/dist/server-QIYIJHD5.cjs.map +0 -1
@@ -1,7 +1,19 @@
1
1
  'use strict';
2
2
 
3
- var chunkLPWEQCZV_cjs = require('./chunk-LPWEQCZV.cjs');
3
+ var chunkYSL7KJNU_cjs = require('./chunk-YSL7KJNU.cjs');
4
+ var chunkISV6HFQE_cjs = require('./chunk-ISV6HFQE.cjs');
4
5
  var extApps = require('@modelcontextprotocol/ext-apps');
6
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
7
+
8
+ // src/errors.ts
9
+ var HostCapabilityError = class extends Error {
10
+ capability;
11
+ constructor(feature, capability) {
12
+ super(`${feature} is not supported in this host: it did not declare ${capability}`);
13
+ this.name = "HostCapabilityError";
14
+ this.capability = capability;
15
+ }
16
+ };
5
17
 
6
18
  // src/internals.ts
7
19
  var INTERNALS = /* @__PURE__ */ new WeakMap();
@@ -65,14 +77,18 @@ function parseCallToolResult(result) {
65
77
  }
66
78
 
67
79
  // src/task-handle.ts
68
- var TOOLS_CALL_METHOD = "tools/call";
80
+ var TASKS_EXTENSION_ID = "io.modelcontextprotocol/tasks";
81
+ function readHostTasksCapability(capabilities) {
82
+ const entry = capabilities?.experimental?.[TASKS_EXTENSION_ID];
83
+ return entry && typeof entry === "object" && !Array.isArray(entry) ? entry : void 0;
84
+ }
69
85
  var TASKS_GET_METHOD = "tasks/get";
70
86
  var TASKS_RESULT_METHOD = "tasks/result";
71
87
  var TASKS_CANCEL_METHOD = "tasks/cancel";
72
88
  var TASKS_STATUS_NOTIFICATION_METHOD = "notifications/tasks/status";
73
- function createTaskStatusRouter(transport) {
89
+ function createTaskStatusRouter(subscribe) {
74
90
  const listeners = /* @__PURE__ */ new Map();
75
- const unsub = transport.onMessage(TASKS_STATUS_NOTIFICATION_METHOD, (rawParams) => {
91
+ const unsub = subscribe(TASKS_STATUS_NOTIFICATION_METHOD, (rawParams) => {
76
92
  if (!rawParams) return;
77
93
  const params = rawParams;
78
94
  const taskId = params.taskId;
@@ -111,21 +127,20 @@ async function callToolAsTask(app, toolName, args, options) {
111
127
  const deps = internalsFor(app);
112
128
  const hostTasks = deps.hostTasksCapability;
113
129
  if (!hostTasks?.requests?.tools?.call) {
114
- throw new Error(
115
- "callToolAsTask: host did not advertise tasks.requests.tools.call in its capabilities. Per MCP 2025-11-25 \xA7, requestors MUST NOT task-augment a tools/call without matching receiver capability. Fall back to `app.callTool`."
130
+ throw new HostCapabilityError(
131
+ "callToolAsTask",
132
+ `${TASKS_EXTENSION_ID} with requests.tools.call`
116
133
  );
117
134
  }
118
135
  const taskParam = {};
119
136
  if (options?.ttl !== void 0) taskParam.ttl = options.ttl;
120
- const crossServer = options?.internal ?? deps.internalApp;
121
137
  const callParams = {
122
138
  name: toolName,
123
139
  arguments: args ?? {},
124
- task: taskParam,
125
- ...crossServer ? { server: deps.appName } : {}
140
+ task: taskParam
126
141
  };
127
142
  const raw = await deps.request(
128
- TOOLS_CALL_METHOD,
143
+ chunkISV6HFQE_cjs.TOOLS_CALL_METHOD,
129
144
  callParams
130
145
  );
131
146
  const createResult = raw;
@@ -234,32 +249,18 @@ function parseToolResultParams(params) {
234
249
  }
235
250
  return { content: rawContent ?? null, structuredContent: null, raw };
236
251
  }
237
- var DATA_CHANGED_METHOD = "synapse/data-changed";
238
- var ACTION_METHOD = "synapse/action";
239
- var REQUEST_FILE_METHOD = "synapse/request-file";
240
- var DOWNLOAD_FILE_METHOD = "synapse/download-file";
241
- var EVENT_MAP = {
242
- "tool-result": extApps.TOOL_RESULT_METHOD,
243
- "tool-input": extApps.TOOL_INPUT_METHOD,
244
- "tool-input-partial": extApps.TOOL_INPUT_PARTIAL_METHOD,
245
- "tool-cancelled": extApps.TOOL_CANCELLED_METHOD,
246
- teardown: extApps.RESOURCE_TEARDOWN_METHOD
247
- };
248
- function resolveEventMethod(name) {
249
- return EVENT_MAP[name] ?? name;
250
- }
251
252
 
252
253
  // src/keyboard.ts
253
254
  var KeyboardForwarder = class {
254
255
  listener;
255
256
  destroyed = false;
256
- constructor(transport, customKeys) {
257
+ constructor(send, customKeys) {
257
258
  const config = customKeys ?? null;
258
259
  this.listener = (event) => {
259
260
  if (this.destroyed) return;
260
261
  if (this.shouldForward(event, config)) {
261
262
  event.preventDefault();
262
- transport.send("synapse/keydown", {
263
+ send(chunkISV6HFQE_cjs.KEYDOWN_METHOD, {
263
264
  key: event.key,
264
265
  ctrlKey: event.ctrlKey,
265
266
  metaKey: event.metaKey,
@@ -291,271 +292,125 @@ var KeyboardForwarder = class {
291
292
  return false;
292
293
  }
293
294
  };
294
-
295
- // src/resize.ts
296
295
  function createResizer(send, autoResize) {
297
296
  let destroyed = false;
298
297
  let observer = null;
299
- let rafId = null;
300
298
  function measureAndSend() {
301
299
  if (destroyed) return;
302
300
  const width = document.body.scrollWidth;
303
301
  const height = document.body.scrollHeight;
304
- send("ui/notifications/size-changed", { width, height });
302
+ send(extApps.SIZE_CHANGED_METHOD, { width, height });
305
303
  }
306
304
  function resize(width, height) {
307
305
  if (destroyed) return;
308
306
  if (width !== void 0 && height !== void 0) {
309
- send("ui/notifications/size-changed", { width, height });
307
+ send(extApps.SIZE_CHANGED_METHOD, { width, height });
310
308
  } else {
311
309
  measureAndSend();
312
310
  }
313
311
  }
314
- if (autoResize && typeof ResizeObserver !== "undefined") {
315
- observer = new ResizeObserver(() => {
316
- if (destroyed) return;
317
- if (rafId !== null) cancelAnimationFrame(rafId);
318
- rafId = requestAnimationFrame(() => {
319
- rafId = null;
320
- measureAndSend();
321
- });
322
- });
323
- observer.observe(document.body);
324
- }
325
312
  function destroy() {
326
313
  if (destroyed) return;
327
314
  destroyed = true;
328
- if (rafId !== null) cancelAnimationFrame(rafId);
329
315
  observer?.disconnect();
330
316
  observer = null;
331
317
  }
332
318
  return { resize, measureAndSend, destroy };
333
319
  }
334
320
 
335
- // src/transport.ts
336
- var SynapseTransport = class {
337
- counter = 0;
338
- destroyed = false;
339
- pending = /* @__PURE__ */ new Map();
340
- handlers = /* @__PURE__ */ new Map();
341
- listener;
342
- constructor() {
343
- this.listener = (event) => this.handleMessage(event);
344
- window.addEventListener("message", this.listener);
345
- }
346
- send(method, params) {
347
- if (this.destroyed) return;
348
- const msg = {
349
- jsonrpc: "2.0",
350
- method,
351
- ...params !== void 0 && { params }
352
- };
353
- window.parent.postMessage(msg, "*");
354
- }
355
- request(method, params) {
356
- if (this.destroyed) {
357
- return Promise.reject(new Error("Transport destroyed"));
358
- }
359
- const id = `syn-${++this.counter}`;
360
- const msg = {
361
- jsonrpc: "2.0",
362
- method,
363
- id,
364
- ...params !== void 0 && { params }
365
- };
366
- return new Promise((resolve, reject) => {
367
- this.pending.set(id, { resolve, reject });
368
- window.parent.postMessage(msg, "*");
369
- });
370
- }
371
- onMessage(method, callback) {
372
- if (!this.handlers.has(method)) {
373
- this.handlers.set(method, /* @__PURE__ */ new Set());
374
- }
375
- this.handlers.get(method)?.add(callback);
376
- return () => {
377
- const set = this.handlers.get(method);
378
- if (set) {
379
- set.delete(callback);
380
- if (set.size === 0) {
381
- this.handlers.delete(method);
382
- }
383
- }
384
- };
385
- }
386
- destroy() {
387
- if (this.destroyed) return;
388
- this.destroyed = true;
389
- window.removeEventListener("message", this.listener);
390
- const error = new Error("Transport destroyed");
391
- for (const entry of this.pending.values()) {
392
- entry.reject(error);
393
- }
394
- this.pending.clear();
395
- this.handlers.clear();
396
- }
397
- handleMessage(event) {
398
- if (this.destroyed) return;
399
- const data = event.data;
400
- if (!data || data.jsonrpc !== "2.0") return;
401
- if ("id" in data && data.id && !("method" in data)) {
402
- const response = data;
403
- const entry = this.pending.get(response.id);
404
- if (!entry) return;
405
- this.pending.delete(response.id);
406
- if (response.error) {
407
- const err = new Error(response.error.message);
408
- err.code = response.error.code;
409
- err.data = response.error.data;
410
- entry.reject(err);
411
- } else {
412
- entry.resolve(response.result);
413
- }
414
- return;
415
- }
416
- if ("method" in data && !("id" in data && data.id)) {
417
- const notification = data;
418
- const set = this.handlers.get(notification.method);
419
- if (set) {
420
- for (const handler of set) {
421
- handler(notification.params);
422
- }
423
- }
424
- }
425
- }
426
- };
427
-
428
321
  // src/connect.ts
429
- var READ_RESOURCE_METHOD = "resources/read";
430
- var UPDATE_MODEL_CONTEXT_METHOD = "ui/update-model-context";
322
+ var NIMBLEBRAIN_HOST = "nimblebrain";
323
+ var CHAT_CONTEXT_META_KEY = "ai.nimblebrain/context";
324
+ var NO_DEADLINE = { timeout: 2147483647 };
325
+ var MAPPED_EVENTS = [
326
+ ["toolinput", extApps.TOOL_INPUT_METHOD],
327
+ ["toolinputpartial", extApps.TOOL_INPUT_PARTIAL_METHOD],
328
+ ["toolresult", extApps.TOOL_RESULT_METHOD],
329
+ ["toolcancelled", extApps.TOOL_CANCELLED_METHOD]
330
+ ];
431
331
  async function connect(options) {
432
- const { name, version, autoResize = false, internal = false, forwardKeys } = options;
433
- const transport = new SynapseTransport();
332
+ const { name, version, autoResize = false, forwardKeys } = options;
434
333
  let destroyed = false;
435
- let hostContext = {};
436
334
  let hostInfo = { name: "unknown", version: "unknown" };
437
335
  let isNimbleBrainHost = false;
438
336
  let toolInfo = null;
439
337
  let containerDimensions = null;
338
+ let hostCapabilities = {};
440
339
  let hostTasksCapability;
441
340
  let keyboard = null;
442
- let fontFaces;
443
- function resolveTheme() {
444
- const theme = chunkLPWEQCZV_cjs.extractTheme(hostContext);
445
- return fontFaces ? { ...theme, fontFaces } : theme;
446
- }
447
- const handlers = /* @__PURE__ */ new Map();
448
- const themeCallbacks = /* @__PURE__ */ new Set();
449
- const hostContextCallbacks = /* @__PURE__ */ new Set();
450
- const dataCallbacks = /* @__PURE__ */ new Set();
451
- const actionCallbacks = /* @__PURE__ */ new Set();
452
- const resizer = createResizer((method, params) => transport.send(method, params), autoResize);
453
- resizer.measureAndSend();
341
+ let appliedTheme = { mode: "light", tokens: {} };
454
342
  const appCapabilities = {
455
343
  tasks: {
456
344
  cancel: {},
457
345
  requests: { tools: { call: {} } }
458
346
  }
459
347
  };
460
- const initParams = {
461
- protocolVersion: extApps.LATEST_PROTOCOL_VERSION,
462
- appInfo: { name, version },
463
- appCapabilities
464
- };
465
- const result = await transport.request(
466
- extApps.INITIALIZE_METHOD,
467
- initParams
348
+ const client = new extApps.App(
349
+ { name, version },
350
+ appCapabilities,
351
+ { autoResize }
468
352
  );
469
- if (result) {
470
- const detected = chunkLPWEQCZV_cjs.detectHost(result);
471
- isNimbleBrainHost = detected.isNimbleBrain;
472
- hostInfo = {
473
- name: result.hostInfo?.name ?? "unknown",
474
- version: result.hostInfo?.version ?? "unknown"
475
- };
476
- const rawTasks = result.hostCapabilities?.tasks;
477
- hostTasksCapability = rawTasks && typeof rawTasks === "object" && !Array.isArray(rawTasks) ? rawTasks : void 0;
478
- const ctx = result.hostContext;
479
- if (ctx) {
480
- hostContext = ctx;
481
- fontFaces = chunkLPWEQCZV_cjs.foldFontFaces(fontFaces, ctx);
482
- if (ctx.toolInfo && typeof ctx.toolInfo === "object") {
483
- toolInfo = { tool: ctx.toolInfo.tool ?? {} };
484
- }
485
- if (ctx.containerDimensions && typeof ctx.containerDimensions === "object") {
486
- containerDimensions = ctx.containerDimensions;
487
- }
488
- const theme = resolveTheme();
489
- chunkLPWEQCZV_cjs.applyTheme(theme.mode, theme.tokens, theme.fontFaces);
490
- }
353
+ function currentContext() {
354
+ return client.getHostContext() ?? {};
355
+ }
356
+ function resolveTheme() {
357
+ return chunkYSL7KJNU_cjs.extractTheme(currentContext());
491
358
  }
492
- if (forwardKeys && isNimbleBrainHost) {
493
- keyboard = new KeyboardForwarder(transport, forwardKeys === true ? void 0 : forwardKeys);
359
+ function applyFontsFrom(ctx) {
360
+ const css = chunkYSL7KJNU_cjs.extractHostFontCss(ctx);
361
+ if (css !== void 0 && typeof document !== "undefined") extApps.applyHostFonts(css);
494
362
  }
495
- function fanOutRaw(method, params) {
363
+ function applyResolvedTheme() {
364
+ const theme = resolveTheme();
365
+ chunkYSL7KJNU_cjs.applyTheme(theme.mode, theme.tokens);
366
+ appliedTheme = theme;
367
+ return theme;
368
+ }
369
+ const handlers = /* @__PURE__ */ new Map();
370
+ const themeCallbacks = /* @__PURE__ */ new Set();
371
+ const hostContextCallbacks = /* @__PURE__ */ new Set();
372
+ function fanOut(method, payload) {
496
373
  const set = handlers.get(method);
497
374
  if (!set) return;
498
- for (const handler of set) handler(params);
375
+ for (const handler of set) handler(payload);
376
+ }
377
+ function sendRaw(method, params) {
378
+ if (destroyed) return;
379
+ void client.notification({ method, params }).catch(() => {
380
+ });
381
+ }
382
+ function requestRaw(method, params) {
383
+ return client.request({ method, params }, types_js.ResultSchema, NO_DEADLINE);
384
+ }
385
+ for (const [event, method] of MAPPED_EVENTS) {
386
+ client.addEventListener(event, (params) => {
387
+ if (destroyed) return;
388
+ fanOut(
389
+ method,
390
+ method === extApps.TOOL_RESULT_METHOD ? parseToolResultParams(params) : params
391
+ );
392
+ });
499
393
  }
500
- transport.onMessage(extApps.HOST_CONTEXT_CHANGED_METHOD, (params) => {
394
+ client.addEventListener("hostcontextchanged", (params) => {
501
395
  if (destroyed) return;
502
- const ctx = params ?? {};
503
- const prevTheme = resolveTheme();
504
- hostContext = { ...hostContext, ...ctx };
505
- fontFaces = chunkLPWEQCZV_cjs.foldFontFaces(fontFaces, ctx);
506
- const nextTheme = resolveTheme();
507
- chunkLPWEQCZV_cjs.applyTheme(nextTheme.mode, nextTheme.tokens, nextTheme.fontFaces);
508
- for (const cb of hostContextCallbacks) cb(hostContext);
509
- fanOutRaw(extApps.HOST_CONTEXT_CHANGED_METHOD, params);
396
+ const prevTheme = appliedTheme;
397
+ applyFontsFrom(params);
398
+ const nextTheme = applyResolvedTheme();
399
+ const merged = currentContext();
400
+ for (const cb of hostContextCallbacks) cb(merged);
401
+ fanOut(extApps.HOST_CONTEXT_CHANGED_METHOD, params);
510
402
  if (!themesEqual(prevTheme, nextTheme)) {
511
403
  for (const cb of themeCallbacks) cb(nextTheme);
512
404
  }
513
405
  });
514
- transport.onMessage(DATA_CHANGED_METHOD, (params) => {
515
- if (destroyed) return;
516
- fanOutRaw(DATA_CHANGED_METHOD, params);
517
- if (!params) return;
518
- const event = {
519
- source: "agent",
520
- server: params.server ?? "",
521
- tool: params.tool ?? ""
522
- };
523
- for (const cb of dataCallbacks) cb(event);
524
- });
525
- transport.onMessage(ACTION_METHOD, (params) => {
406
+ client.onteardown = (params) => {
407
+ if (!destroyed) fanOut(extApps.RESOURCE_TEARDOWN_METHOD, params);
408
+ return {};
409
+ };
410
+ client.fallbackNotificationHandler = async (notification) => {
526
411
  if (destroyed) return;
527
- fanOutRaw(ACTION_METHOD, params);
528
- if (!params || typeof params.type !== "string") return;
529
- const action2 = {
530
- type: params.type,
531
- payload: params.payload ?? {},
532
- requiresConfirmation: params.requiresConfirmation === true,
533
- label: typeof params.label === "string" ? params.label : void 0
534
- };
535
- for (const cb of actionCallbacks) cb(action2);
536
- });
537
- const subscribedMethods = /* @__PURE__ */ new Set([
538
- extApps.HOST_CONTEXT_CHANGED_METHOD,
539
- DATA_CHANGED_METHOD,
540
- ACTION_METHOD
541
- ]);
542
- function ensureTransportSub(method) {
543
- if (subscribedMethods.has(method)) return;
544
- subscribedMethods.add(method);
545
- const isToolResult = method === extApps.TOOL_RESULT_METHOD;
546
- transport.onMessage(method, (params) => {
547
- if (destroyed) return;
548
- const set = handlers.get(method);
549
- if (!set) return;
550
- for (const handler of set) {
551
- if (isToolResult) {
552
- handler(parseToolResultParams(params));
553
- } else {
554
- handler(params);
555
- }
556
- }
557
- });
558
- }
412
+ fanOut(notification.method, notification.params);
413
+ };
559
414
  function subscribe(event, handler) {
560
415
  switch (event) {
561
416
  case "theme-changed":
@@ -568,21 +423,10 @@ async function connect(options) {
568
423
  return () => {
569
424
  hostContextCallbacks.delete(handler);
570
425
  };
571
- case "data-changed":
572
- dataCallbacks.add(handler);
573
- return () => {
574
- dataCallbacks.delete(handler);
575
- };
576
- case "action":
577
- actionCallbacks.add(handler);
578
- return () => {
579
- actionCallbacks.delete(handler);
580
- };
581
426
  default: {
582
- const method = resolveEventMethod(event);
427
+ const method = chunkISV6HFQE_cjs.resolveEventMethod(event);
583
428
  if (!handlers.has(method)) handlers.set(method, /* @__PURE__ */ new Set());
584
429
  handlers.get(method)?.add(handler);
585
- ensureTransportSub(method);
586
430
  return () => {
587
431
  const set = handlers.get(method);
588
432
  if (set) {
@@ -598,8 +442,36 @@ async function connect(options) {
598
442
  if (typeof handler === "function") subscribe(event, handler);
599
443
  }
600
444
  }
601
- transport.send(extApps.INITIALIZED_METHOD, {});
602
- const taskRouter = createTaskStatusRouter(transport);
445
+ await client.connect();
446
+ const hostVersion = client.getHostVersion();
447
+ hostInfo = {
448
+ name: hostVersion?.name ?? "unknown",
449
+ version: hostVersion?.version ?? "unknown"
450
+ };
451
+ isNimbleBrainHost = hostInfo.name === NIMBLEBRAIN_HOST;
452
+ hostCapabilities = client.getHostCapabilities() ?? {};
453
+ hostTasksCapability = readHostTasksCapability(hostCapabilities);
454
+ const initialContext = client.getHostContext();
455
+ if (initialContext) {
456
+ if (initialContext.toolInfo && typeof initialContext.toolInfo === "object") {
457
+ toolInfo = {
458
+ tool: initialContext.toolInfo.tool ?? {}
459
+ };
460
+ }
461
+ if (initialContext.containerDimensions && typeof initialContext.containerDimensions === "object") {
462
+ containerDimensions = initialContext.containerDimensions;
463
+ }
464
+ applyFontsFrom(initialContext);
465
+ applyResolvedTheme();
466
+ }
467
+ if (forwardKeys && hostCapabilities.experimental?.[chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS.keydown.capability] !== void 0) {
468
+ keyboard = new KeyboardForwarder(sendRaw, forwardKeys === true ? void 0 : forwardKeys);
469
+ }
470
+ const resizer = createResizer(sendRaw);
471
+ if (!autoResize) resizer.measureAndSend();
472
+ const taskRouter = createTaskStatusRouter(
473
+ (method, handler) => subscribe(method, handler)
474
+ );
603
475
  const app = {
604
476
  get theme() {
605
477
  return resolveTheme();
@@ -608,7 +480,10 @@ async function connect(options) {
608
480
  return { ...hostInfo };
609
481
  },
610
482
  get hostContext() {
611
- return hostContext;
483
+ return currentContext();
484
+ },
485
+ get hostCapabilities() {
486
+ return hostCapabilities;
612
487
  },
613
488
  get isNimbleBrainHost() {
614
489
  return isNimbleBrainHost;
@@ -633,52 +508,62 @@ async function connect(options) {
633
508
  },
634
509
  openLink(url) {
635
510
  if (destroyed) return;
511
+ if (!hostCapabilities.openLinks) {
512
+ window.open(url, "_blank", "noopener");
513
+ return;
514
+ }
636
515
  const params = { url };
637
- transport.request(extApps.OPEN_LINK_METHOD, params).catch(() => {
516
+ client.openLink(params, NO_DEADLINE).catch(() => {
638
517
  window.open(url, "_blank", "noopener");
639
518
  });
640
519
  },
641
520
  updateModelContext(state, summary) {
642
- if (destroyed) return;
521
+ if (destroyed || !hostCapabilities.updateModelContext) return;
643
522
  const params = {
644
523
  structuredContent: state,
645
524
  ...summary !== void 0 && {
646
525
  content: [{ type: "text", text: summary }]
647
526
  }
648
527
  };
649
- transport.send(UPDATE_MODEL_CONTEXT_METHOD, params);
528
+ client.updateModelContext(params, NO_DEADLINE).catch(() => {
529
+ });
650
530
  },
651
- async callTool(toolName, args, callOptions) {
652
- const server = callOptions?.server ?? (internal ? name : void 0);
531
+ async callTool(toolName, args) {
532
+ if (!hostCapabilities.serverTools) {
533
+ throw new HostCapabilityError("callTool", "serverTools");
534
+ }
653
535
  const params = {
654
536
  name: toolName,
655
- arguments: args ?? {},
656
- ...server !== void 0 && { server }
537
+ arguments: args ?? {}
657
538
  };
658
- const raw = await transport.request(TOOLS_CALL_METHOD, params);
539
+ const raw = await client.request(
540
+ { method: chunkISV6HFQE_cjs.TOOLS_CALL_METHOD, params },
541
+ types_js.CallToolResultSchema,
542
+ NO_DEADLINE
543
+ );
659
544
  return parseToolResult(raw);
660
545
  },
661
546
  async readServerResource(params) {
662
- const raw = await transport.request(
663
- READ_RESOURCE_METHOD,
664
- params
665
- );
666
- return raw;
547
+ if (!hostCapabilities.serverResources) {
548
+ throw new HostCapabilityError("readServerResource", "serverResources");
549
+ }
550
+ return await client.readServerResource(params, NO_DEADLINE);
667
551
  },
668
552
  sendMessage(text, context) {
669
- if (destroyed) return;
553
+ if (destroyed || !hostCapabilities.message) return;
670
554
  const textBlock = {
671
555
  type: "text",
672
556
  text,
673
- // `_meta.context` is a NimbleBrain convention; other hosts ignore it,
557
+ // The chat context is a NimbleBrain host field; other hosts ignore it,
674
558
  // but there is no reason to spend the bytes off one.
675
- ...isNimbleBrainHost && context && { _meta: { context } }
559
+ ...isNimbleBrainHost && context && { _meta: { [CHAT_CONTEXT_META_KEY]: context } }
676
560
  };
677
561
  const params = {
678
562
  role: "user",
679
563
  content: [textBlock]
680
564
  };
681
- transport.send(extApps.MESSAGE_METHOD, params);
565
+ client.sendMessage(params, NO_DEADLINE).catch(() => {
566
+ });
682
567
  },
683
568
  destroy() {
684
569
  if (destroyed) return;
@@ -689,34 +574,28 @@ async function connect(options) {
689
574
  handlers.clear();
690
575
  themeCallbacks.clear();
691
576
  hostContextCallbacks.clear();
692
- dataCallbacks.clear();
693
- actionCallbacks.clear();
694
- transport.destroy();
577
+ void client.close();
695
578
  }
696
579
  };
697
580
  registerInternals(app, {
698
581
  send(method, params) {
699
- if (destroyed) return;
700
- transport.send(method, params);
582
+ sendRaw(method, params);
701
583
  },
702
584
  request(method, params) {
703
- return transport.request(method, params);
585
+ return requestRaw(method, params);
704
586
  },
705
587
  onMessage(method, handler) {
706
- return transport.onMessage(method, handler);
588
+ return subscribe(method, handler);
707
589
  },
708
590
  taskRouter,
709
591
  get hostTasksCapability() {
710
592
  return hostTasksCapability;
711
- },
712
- appName: name,
713
- internalApp: internal
593
+ }
714
594
  });
715
595
  return app;
716
596
  }
717
597
  function themesEqual(a, b) {
718
598
  if (a.mode !== b.mode) return false;
719
- if (chunkLPWEQCZV_cjs.fontFacesKey(a.fontFaces) !== chunkLPWEQCZV_cjs.fontFacesKey(b.fontFaces)) return false;
720
599
  const aKeys = Object.keys(a.tokens);
721
600
  const bKeys = Object.keys(b.tokens);
722
601
  if (aKeys.length !== bKeys.length) return false;
@@ -727,62 +606,62 @@ function themesEqual(a, b) {
727
606
  }
728
607
 
729
608
  // src/extensions.ts
609
+ function hostSupports(app, extension) {
610
+ const { capability } = chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS[extension];
611
+ return app.hostCapabilities.experimental?.[capability] !== void 0;
612
+ }
730
613
  var DEFAULT_MAX_FILE_SIZE = 26214400;
731
614
  function action(app, name, params) {
732
- if (!app.isNimbleBrainHost) return;
733
- internalsFor(app).send(ACTION_METHOD, { action: name, ...params });
734
- }
735
- function downloadFile(app, filename, content, mimeType) {
736
- const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
737
- const blob = content instanceof Blob ? content : new Blob([content], { type: resolvedMime });
738
- internalsFor(app).send(DOWNLOAD_FILE_METHOD, {
739
- data: blob,
740
- filename,
741
- mimeType: resolvedMime
742
- });
615
+ if (!hostSupports(app, "action")) return;
616
+ internalsFor(app).send(chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS.action.method, { action: name, ...params });
743
617
  }
744
618
  async function pickFile(app, options) {
745
- if (!app.isNimbleBrainHost) {
746
- throw new Error("pickFile is not supported in this host");
747
- }
748
- const result = await requestFile(app, options, false);
749
- if (result === null) return null;
750
- return Array.isArray(result) ? result[0] ?? null : result;
619
+ requireRequestFile("pickFile", app);
620
+ const files = await requestFile(app, options, false);
621
+ return files[0] ?? null;
751
622
  }
752
623
  async function pickFiles(app, options) {
753
- if (!app.isNimbleBrainHost) {
754
- throw new Error("pickFiles is not supported in this host");
624
+ requireRequestFile("pickFiles", app);
625
+ return await requestFile(app, options, true);
626
+ }
627
+ function requireRequestFile(feature, app) {
628
+ if (!hostSupports(app, "requestFile")) {
629
+ throw new HostCapabilityError(feature, chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS.requestFile.capability);
755
630
  }
756
- const result = await requestFile(app, options, true);
757
- if (result === null) return [];
758
- return Array.isArray(result) ? result : [result];
759
631
  }
760
632
  async function requestFile(app, options, multiple) {
761
- const result = await internalsFor(app).request(REQUEST_FILE_METHOD, {
633
+ const result = await internalsFor(app).request(chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS.requestFile.method, {
762
634
  accept: options?.accept,
763
635
  maxSize: options?.maxSize ?? DEFAULT_MAX_FILE_SIZE,
764
636
  multiple
765
637
  });
766
- if (result == null) return null;
767
- if (Array.isArray(result)) {
768
- return result.map(validateFileResult);
638
+ const files = result?.files;
639
+ if (files === void 0) {
640
+ throw new Error(
641
+ "ai.nimblebrain/request-file returned no `files`. The host is older than this SDK targets: it answers the picker with a bare array or `null`, which a spec-compliant client cannot parse."
642
+ );
643
+ }
644
+ if (!Array.isArray(files)) {
645
+ throw new Error("ai.nimblebrain/request-file returned a `files` field that is not an array.");
769
646
  }
770
- return validateFileResult(result);
647
+ return files.map(validateFileResult);
771
648
  }
772
649
  function validateFileResult(value) {
773
650
  if (typeof value !== "object" || value === null || typeof value.id !== "string") {
774
651
  throw new Error(
775
- "synapse/request-file returned a result without a string `id`. The host appears to be on a version older than this SDK targets \u2014 @nimblebrain/synapse 0.8.0+ requires a host with POST /v1/resources (NimbleBrain \u2265 the version that ships PR #93)."
652
+ "ai.nimblebrain/request-file returned a result without a string `id`. The host appears to be on a version older than this SDK targets \u2014 @nimblebrain/synapse 0.8.0+ requires a host with POST /v1/resources (NimbleBrain \u2265 the version that ships PR #93)."
776
653
  );
777
654
  }
778
655
  return value;
779
656
  }
780
657
 
658
+ exports.HostCapabilityError = HostCapabilityError;
781
659
  exports.action = action;
782
660
  exports.callToolAsTask = callToolAsTask;
783
661
  exports.connect = connect;
784
- exports.downloadFile = downloadFile;
662
+ exports.hostSupports = hostSupports;
663
+ exports.internalsFor = internalsFor;
785
664
  exports.pickFile = pickFile;
786
665
  exports.pickFiles = pickFiles;
787
- //# sourceMappingURL=chunk-FB7GPBEM.cjs.map
788
- //# sourceMappingURL=chunk-FB7GPBEM.cjs.map
666
+ //# sourceMappingURL=chunk-HQLSVUOR.cjs.map
667
+ //# sourceMappingURL=chunk-HQLSVUOR.cjs.map